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

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


This is absurd and subjective decision of Google to remove alert(), confirm(), and prompt() for cross origin iframes. And they called it "feature". And justification is very poor - see "motivation" bellow. A very weak reason for removing such an important feature! Community and developers should protest!

Problem

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.

Solution

Send message via Window.postMessage() from iframe to parent and show dialog via parent page. It is very elegant hack and shame on Google because before Chrome version 92 client saw alert dialog e.g. An embedded page iframe.com" says: ... (which was correct - client see real domain which invoked alert) but now with postMessage solution client will see a lie e.g. The page example.com" says: ... but alert was not invoked by example.com. Stupid google decision caused them to achieve the opposite effect - client will be much more confused now. Google's decision was hasty and they didn't think about the consequences. In case of prompt() and confirm() it is a little bit tricky via Window.postMessage() because we need to send result from top back to iframe.

What Google will do next? Disable Window.postMessage()? Déjà vu. We are back in Internet Explorer era... developers waste time by doing stupid hacks.

TL;DR: Demo

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

Code

With code bellow you can use overridden native alert(), confirm() and prompt() in cross origin iframe with minimum code change. There is no change for alert() usage. I case of confirm() and prompt() just add "await" keyword before it or feel free to use callback way in case you can not switch easily your sync functions to async functions. See all usage examples in iframe.html bellow.

Everything bad comes with something good - now I gained with this solution an advantage that iframe domain is not revealed (domain from address bar is now used in dialogs).

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);})();


File a feature request:

Consider filing a feature request using this Issue Tracker template.

I'd either request that an exception is made for Apps Script web apps, or that built-in methods for alert and confirm are added, similar to the existing alert and prompt dialogs, which currently work on Google editors.

Bug filed:

By the way, this behavior has been reported in Issue Tracker (as a bug):

I'd consider starring it in order to keep track of it.

Workaround:

In the meanwhile, as others said, consider downgrading or changing the browser, or executing it with the following command line flag:

--disable-features="SuppressDifferentOriginSubframeJSDialogs"


So far, the only 'solution' for this is to add the following to your Chrome/Edge browser shortcut:

--disable-features="SuppressDifferentOriginSubframeJSDialogs"

Or downgrade your browser. Obviously neither of these are ideal. Google trying really hard to save us from ourselves here.