跨源iframe中的JavaScript对话框alert()、confirm()和prompt()不再起作用

应用程序脚本 Web 应用程序在<iframe>. Chrome 似乎不再支持alert(), confirm(), 在 web app 上推广这些功能。

有什么解决方法吗?

  • Chrome 版本 92.0.4515.107 (Official Build) (64-bit) -- 不工作
  • Edge 版本 91.0.864.71(官方版本)(64 位)——有效

试图替换alert()window.alert(),但仍然无法正常工作。

exec:1 一个不同的源子框架试图创建一个 JavaScript 对话框。这不再被允许并被阻止。有关更多详细信息,请参阅https://www.chromestatus.com/feature/5148698084376576。

回答

谷歌为跨源 iframe 删除 alert()、confirm() 和 prompt() 是荒谬和主观的决定。他们称之为“功能”。理由很差 - 请参阅下面的“动机”。删除如此重要功能的一个非常薄弱的​​理由!社区和开发商应该抗议!

问题

https://www.chromestatus.com/feature/5148698084376576

功能:删除alert()、confirm()和跨源iframes提示

Chrome 允许 iframe 触发 Javascript 对话框,当 iframe 与顶部框架同源时显示“say ...”,当 iframe 跨域时显示“该页面上的嵌入页面说...”。当前的用户体验令人困惑,并且之前曾导致网站假装消息来自 Chrome 或其他网站的欺骗行为。取消对跨源 iframe 触发 UI 的能力的支持将防止这种欺骗,并阻止进一步的 UI 简化。

动机

当前 JS 对话框的 UI(通常,不仅仅是跨域子框架的情况)令人困惑,因为消息看起来像浏览器自己的 UI。这导致了欺骗(尤其是 window.prompt),其中站点假装特定消息来自 Chrome(例如 1,2,3)。Chrome 通过在消息前加上“say...”来缓解这些欺骗行为。然而,当这些警报来自跨域 iframe 时,UI 会更加混乱,因为 Chrome 试图解释对话框不是来自浏览器本身或顶级页面。鉴于跨源 iframe JS 对话框的使用率低,事实上,当使用 JS 对话框时,站点的主要功能通常不需要它们,并且难以可靠地解释对话框的来源,我们建议删除跨域 iframe 的 JS 对话框。这也将取消阻止我们通过删除主机名指示并通过将对话框移动到内容区域的中心使对话框更明显地成为页面(而不是浏览器)的一部分来进一步简化对话框的能力。这些更改在删除对 JS 对话框的跨源支持时被阻止,否则这些子框架可能会假装它们的对话框来自父页面。

解决方案

通过Window.postMessage()从 iframe 向父级发送消息,并通过父级页面显示对话框。这是谷歌上非常优雅的黑客和耻辱,因为在 Chrome 92 版客户端看到警报对话框之前,例如An embedded page iframe.com" says: ...(这是正确的 - 客户端看到调用警报的真实域)但现在使用 postMessage 解决方案客户端将看到谎言,例如The page example.com" says: ...但警报未被示例调用.com。愚蠢的谷歌决定导致他们达到相反的效果 - 客户现在会更加困惑。谷歌的决定是仓促的,他们没有考虑后果。在 prompt() 和 confirm() 的情况下,通过 Window.postMessage() 有点棘手,因为我们需要将结果从顶部发送回 iframe。

谷歌接下来会做什么?禁用 Window.postMessage()?似曾相识。我们又回到了 Internet Explorer 时代……开发人员通过进行愚蠢的黑客攻击来浪费时间。

TL;DR:演示

https://domain-a.netlify.app/parent.html

代码

使用下面的代码,您可以在跨源 iframe 中使用覆盖的本机 alert()、confirm() 和 prompt(),而代码更改最少。alert() 的用法没有变化。我在confirm() 和prompt() 的情况下只是在它之前添加“await”关键字或者随意使用回调方式,以防您无法轻松地将同步功能切换到异步功能。请参阅下面 iframe.html 中的所有使用示例。

一切坏事都伴随着好事 - 现在我通过这个解决方案获得了一个优势,即 iframe 域不会显示(地址栏中的域现在在对话框中使用)。

https://example-a.com/parent.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Parent (domain A)</title>
        <script type="text/javascript" src="dialogs.js"></script>
    </head>
    <body>
        <h1>Parent (domain A)</h1>
        <iframe src="https://example-b.com/iframe.html">
    </body>
</html>

https://example-b.com/iframe.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Iframe (domain B)</title>
        <script type="text/javascript" src="dialogs.js"></script>
    </head>
    <body>
        <h1>Iframe (domain B)</h1>
        <script type="text/javascript">
            alert('alert() forwarded from iframe.html');
            
            confirm('confirm() forwarded from iframe.html via callback', (result) => {
                console.log('confirm() result via callback: ', result);
            });

            prompt('prompt() forwarded from iframe.html via callback', null, (result) => {
                console.log('prompt() result via callback: ', result);
            });
            
            (async () => {
                var result1 = await confirm('confirm() forwarded from iframe.html via promise');
                console.log('confirm() result via promise: ', result1);

                var result2 = await prompt('prompt() forwarded from iframe.html via promise');
                console.log('prompt() result via promise: ', result2);
            })();
        </script>
    </body>
</html>

对话框.js

(function() {

    var id = 1,
        store = {},
        isIframe = (window === window.parent || window.opener) ? false : true;

    // Send message
    var sendMessage = function(windowToSend, data) {
        windowToSend.postMessage(JSON.stringify(data), '*');
    };

    // Helper for overridden confirm() and prompt()
    var processInteractiveDialog = function(data, callback) {
        sendMessage(parent, data);

        if (callback)
            store[data.id] = callback;
        else
            return new Promise(resolve => { store[data.id] = resolve; })
    };

    // Override native dialog functions
    if (isIframe) {
        // alert()
        window.alert = function(message) {
            var data = { event : 'dialog', type : 'alert', message : message };
            sendMessage(parent, data);
        };

        // confirm()
        window.confirm = function(message, callback) {
            var data = { event : 'dialog', type : 'confirm', id : id++, message : message };
            return processInteractiveDialog(data, callback);
        };

        // prompt()
        window.prompt = function(message, value, callback) {
            var data = { event : 'dialog', type : 'prompt', id : id++, message : message, value : value || '' };
            return processInteractiveDialog(data, callback);
        };
    }

    // Listen to messages
    window.addEventListener('message', function(event) {
        try {
            var data = JSON.parse(event.data);
        }
        catch (error) {
            return;
        }

        if (!data || typeof data != 'object')
            return;

        if (data.event != 'dialog' || !data.type)
            return;

        // Initial message from iframe to parent
        if (!isIframe) {
            // alert()
            if (data.type == 'alert')
                alert(data.message)

            // confirm()
            else if (data.type == 'confirm') {
                var data = { event : 'dialog', type : 'confirm', id : data.id, result : confirm(data.message) };
                sendMessage(event.source, data);
            }

            // prompt()
            else if (data.type == 'prompt') {
                var data = { event : 'dialog', type : 'prompt', id : data.id, result : prompt(data.message, data.value) };
                sendMessage(event.source, data);
            }
        }

        // Response message from parent to iframe
        else {
            // confirm()
            if (data.type == 'confirm') {
                store[data.id](data.result);
                delete store[data.id];
            }

            // prompt()
            else if (data.type == 'prompt') {
                store[data.id](data.result);
                delete store[data.id];
            }
        }
    }, false);

})();


以上是跨源iframe中的JavaScript对话框alert()、confirm()和prompt()不再起作用的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>