跨源 iframe 中的 JavaScript 对话框 alert()、confirm() 和 prompt() 不再起作用 提交功能请求:提交的错误:解决方法:相关问题:

问题描述

Apps 脚本网络应用程序在 <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

解决方法

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

问题

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

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

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 对话框时,站点的主要功能通常不需要它们,并且难以可靠地解释对话框的来源,我们建议删除 JS 对话框跨域 iframe。这也将取消阻止我们通过删除主机名指示并通过将对话框移动到内容区域的中心使对话框更明显地成为页面(而不是浏览器)的一部分来进一步简化对话框的能力。这些更改在移除对 JS 对话框的跨源支持时被阻止,否则这些子框架可能会假装它们的对话框来自父页面。

解决方案

通过 Window.postMessage() 从 iframe 向父级发送消息并通过父级页面显示对话框。这是谷歌上非常优雅的黑客和耻辱,因为在 Chrome 92 版客户端看到警报对话框之前,例如An embedded page iframe.com" says: ...(这是正确的 - 客户端看到调用警报的真实域)但现在使用 postMessage 解决方案客户端将看到一个谎言,例如The page example.com" says: ... 但 example.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>

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,type : 'prompt',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',id : data.id,result : confirm(data.message) };
                sendMessage(event.source,data);
            }

            // prompt()
            else if (data.type == 'prompt') {
                var data = { event : 'dialog',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);

})();
,

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

--disable-features="SuppressDifferentOriginSubframeJSDialogs"

或降级您的浏览器。显然,这两者都不是理想的。谷歌真的很努力地把我们从这里拯救出来。

,

提交功能请求:

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

我要么请求为 Apps Script Web 应用程序设置例外,要么添加 alertconfirm 的内置方法,类似于现有的 alertprompt 对话框,目前适用于 Google 编辑器。

提交的错误:

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

为了跟踪它,我会考虑 starring it

解决方法:

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

--disable-features="SuppressDifferentOriginSubframeJSDialogs"