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

29

Apps Script Web App可以在<iframe>中工作。看起来Chrome不再支持alert()confirm()函数,需要将这些函数在Web应用程序中推广使用。

有什么解决方法吗?

  • Chrome版本92.0.4515.107(官方版本)(64位)- 不起作用
  • Edge版本91.0.864.71(官方版本)(64位)- 起作用

尝试将alert()替换为window.alert(),但仍然不起作用。

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


如果您正在使用侧边栏或自定义对话框,则可以使用Apps Script方法alarmprompt。否则,建议您在问题跟踪器中提交功能请求。 - Iamblichus
1
似乎它们已经修补了它。我正在使用92.0.4515.131,这个问题不再像在92.0.4515.107中那样存在了。 - Trisped
1
@Trisped 是的,你说得对。它又可以工作了,在我的情况下是比92.0.4515.131版本稍旧一点。谷歌真是太失败了。我刚刚更新到了92.0.4515.131版本,也可以工作了。我的答案中提供的解决方案 https://dev59.com/OVEG5IYBdhLWcg3wS5d2#68557341 有一个优点,即iframe域不会被揭示(现在对话框中使用地址栏中的域)。我已经匆忙地在几个项目中实施了它,现在我将继续使用它。 - mikep
来自Chromium团队的消息:“我们已经决定将此废弃推迟至2022年1月至少...” - Déjà vu
显示剩余4条评论
3个回答

25

谷歌决定删除在跨域iframe中触发alert()、confirm()和prompt()的功能,这是一个荒谬而主观的决定,他们称之为“特性”,并且解释非常粗糙——请参见下面的“动机”。删除如此重要的功能的理由非常微弱!社区和开发者应该抗议!

问题

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

特性:移除跨域iframe中的alert()、confirm()和prompt()

Chrome允许iframes触发JavaScript对话框,当iframe与顶级框架相同时,它会显示“ says ...”,当iframe跨源时,它会显示“嵌入式页面在该页面上说...”。当前的UX很不清晰,并且以前曾导致过虚伪,其中网站假装消息来自Chrome或另一个网站。删除跨域iframe触发UI的支持将防止这种欺骗,以及阻止进一步的UI简化。

动机

当前的JS对话框UI(不仅限于跨域子框架情况)很不清晰,因为消息看起来像浏览器自己的UI。这导致了虚伪(尤其是使用window.prompt时),其中网站假装特定消息来自Chrome(例如1,2,3)。 Chrome通过在消息前加上“ says…”来减轻这些虚伪。但是,当这些警报来自跨域iframe时,UI甚至更加混乱,因为Chrome试图解释对话框不是来自浏览器本身或顶层页面。鉴于跨域iframe JS对话框的低使用率,当使用JS对话框时,通常不需要用于站点的主要功能,并且难以可靠地解释对话框的来源,因此我们建议删除跨域iframe的JS对话框。这也将解除我们进一步简化对话框的能力,例如删除主机名指示并使对话框更明显地成为页面(而不是浏览器)的一部分,方法是将其移到内容区域的中心。这些变化被阻止,因为我们需要先删除JS对话框的跨域支持,否则这些子框架可以假装他们的对话框来自父页面。

解决方案

通过Window.postMessage() 在 iframe 中向父页面发送消息并通过父页面显示对话框,这是一种非常优雅的解决方法。谷歌的做法很可耻,因为在 Chrome 92 版本之前,客户端会看到正确的警告对话框,例如:“嵌入式页面iframe.com说:...”(客户端看到了调用警告的真实域),但现在使用 postMessage 方式,客户端将会看到一个谎言,例如:“页面example.com说:...”,但警报不是由 example.com 调用的。愚蠢的谷歌决定导致他们取得了相反的效果——客户端现在会更加困惑。谷歌的决定过于草率,没有考虑后果。对于 prompt()confirm() ,通过 Window.postMessage() 实现有点棘手,因为我们需要从顶部将结果发送回 iframe。

谷歌接下来会怎么做?禁用 Window.postMessage() 吗?似曾相识。我们又回到了 IE 时代......开发人员浪费时间进行愚蠢的黑客行为。

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>

dialogs.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);

})();

谢谢mikep!我的当前解决方案是禁用这些功能...我会尽快测试!但最终,我希望Chrome可以再次启用该功能。它本可以成为一行代码,但现在需要花费很多时间来解决。 - JFtyv_85StvsDpDn
1
我想指出:Google Chrome正在运行所谓的“起源试验”,以测试这里突出的行为变化。它被称为“禁用不同起源子框架对话框抑制”。任何关心此行为变化的人都可以注册参加起源试验并提供反馈。 - Peter O.
即使父页面和 iframe 页面在同一个根域名下,例如 parent.example.comiframe.example.com,我仍然会收到错误提示。 - lunr
@lunr,你可能做错了什么,因为正如你所看到的这个演示 https://domain-a.netlify.app/parent.html 运行良好,而且父页面和iframe都在同一个根域名 netlify.app 上(只是子域名不同 domain-a.netlify.app vs domain-b.netlify.app)。 - mikep
@mikep 在这个例子里,他们覆盖了alert等方法,并使用window.postMessage来显示来自父窗口的提示。 - lunr
显示剩余3条评论

4

提交功能请求:

考虑使用此问题跟踪器模板提交功能请求。

我要么请求为Apps Script Web应用程序提供一个例外,要么添加内置方法以类似于现有的Google编辑器上工作的警报提示对话框中的alertconfirm进行操作。

已提交错误:

顺便说一下,这种行为已在问题跟踪器中(作为错误)报告:

我建议将其标记为关注以便跟踪。

解决方法:

与此同时,正如其他人所说的那样,考虑降级或更改浏览器,或使用以下命令行标记执行它:

--disable-features="SuppressDifferentOriginSubframeJSDialogs"


此外,Google Chrome正在运行所谓的“起源试验”,以测试这里突出的行为变化。它被称为“禁用不同起源子框架对话框抑制”。任何关心此行为变化的人都可以注册参加起源试验并提供反馈。 - Peter O.

4
到目前为止,唯一的“解决方案”是将以下内容添加到您的Chrome/Edge浏览器快捷方式:
--disable-features="SuppressDifferentOriginSubframeJSDialogs"

或者降低您的浏览器版本。显然,这两种方法都不理想。谷歌在努力拯救我们自己。


6
这不是解决办法。开发者无法在客户端上影响这个问题。 - mikep
使用嵌套的跨源 iframe 是许多具有传统代码的企业常用的方法。通常这些企业都有一个 IT 部门,可以选择他们希望员工使用的浏览器以及启动参数。这种方法可能并不适用于所有人,但对某些人来说是可行的。 - Robert Gaum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接