JavaScript 跨源 iframe 中的对话框 alert()、confirm() 和 prompt() 不再有效

JavaScript dialogs alert(), confirm() and prompt() in cross origin iframe does not work any longer

Apps 脚本网络应用程序在 <iframe> 中运行。 Chrome似乎不​​再支持alert()confirm(),在网页端推广这些功能

有什么解决方法吗?

尝试用 window.alert() 替换 alert(),但仍然无效。

exec:1 A different origin subframe tried to create a JavaScript dialog. This is no longer allowed and was blocked. See https://www.chromestatus.com/feature/5148698084376576 for more details.

到目前为止,唯一的 'solution' 方法是将以下内容添加到您的 Chrome/Edge 浏览器快捷方式:

--disable-features="SuppressDifferentOriginSubframeJSDialogs"

或者降级浏览器。显然,这些都不是理想的。 Google 非常努力地把我们从这里拯救出来。

提交功能请求:

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

我要么请求为 Apps 脚本网络应用程序创建一个例外,要么请求 alert and confirm are added, similar to the existing alert and prompt 对话框的内置方法,目前在 Google 编辑器上工作。

错误提交:

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

我会考虑 starring it 以跟踪它。

解决方法:

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

--disable-features="SuppressDifferentOriginSubframeJSDialogs"

  • Chrome SuppressDifferentOriginSubframeJSDialogs setting override using JS?

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

问题

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

Feature: Remove alert(), confirm(), and prompt for cross origin iframes

Chrome allows iframes to trigger Javascript dialogs, it shows “ says ...” when the iframe is the same origin as the top frame, and “An embedded page on this page says...” when the iframe is cross-origin. The current UX is confusing, and has previously led to spoofs where sites pretend the message comes from Chrome or a different website. Removing support for cross origin iframes’ ability to trigger the UI will prevent this kind of spoofing, and unblock further UI simplifications.

Motivation

The current UI for JS dialogs (in general, not just for the cross-origin subframe case) is confusing, because the message looks like the browser’s own UI. This has led to spoofs (particularly with window.prompt) where sites pretend that a particular message is coming from Chrome (e.g. 1,2,3). Chrome mitigates these spoofs by prefacing the message with “ says...”. However, when these alerts are coming from a cross-origin iframe, the UI is even more confusing because Chrome tries to explain the dialog is not coming from the browser itself or the top level page. Given the low usage of cross-origin iframe JS dialogs, the fact that when JS dialogs are used they are generally not required for the site’s primary functionality, and the difficulty in explaining reliably where the dialog is coming from, we propose removing JS dialogs for cross-origin iframes. This will also unblock our ability to further simplify the dialog by removing the hostname indication and making the dialog more obviously a part of the page (and not the browser) by moving it to the center of the content area. These changes are blocked on removing cross-origin support for JS dialogs, since otherwise these subframes could pretend their dialog is coming from the parent page.

解决方案

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

接下来 Google 会做什么?禁用 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, 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);

})();