29

Apps script web app works in <iframe>. It seems Chrome is no longer supporting alert(), confirm(), Promote these functions on the web app.

Any workaround to this?

  • Chrome Version 92.0.4515.107 (Official Build) (64-bit) -- does not work
  • Edge Version 91.0.864.71 (Official build) (64-bit) -- works

Tried replacing alert() with window.alert(), but still does not work.

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.

Pawel Veselov
  • 3,996
  • 7
  • 44
  • 62
JFtyv_85StvsDpDn
  • 318
  • 1
  • 3
  • 7
  • If you were using a sidebar or a custom dialog you could use the Apps Script methods [alarm](https://developers.google.com/apps-script/guides/dialogs#alert_dialogs) and [prompt](https://developers.google.com/apps-script/guides/dialogs#prompt_dialogs). Otherwise, I'd suggest you to file a feature request in Issue Tracker. – Iamblichus Jul 23 '21 at 12:51
  • I believe the methods you suggested is used for Spreadsheet. However, in my case the script is deployed as web app. So on the client side, you can not call alert, confirm functions. Maybe modal can be a way to work around. – JFtyv_85StvsDpDn Jul 23 '21 at 15:26
  • Yes that's why I specified a sidebar or custom dialog, which work on editors (Sheets, Docs, etc.). Since that's not the case, I'd suggest filing a feature request in Issue Tracker. – Iamblichus Jul 26 '21 at 07:04
  • Is this an audit? Bad English, a link to a URL which might be malicious, ...? No, this should not be an audit! – Dominique Jul 28 '21 at 06:45
  • I want to point out: Google Chrome is running a so-called "[origin trial](https://developer.chrome.com/blog/origin-trials/)" on the behavior change highlighted here. It's called "[Disable Different Origin Subframe Dialog Suppression](https://developer.chrome.com/origintrials/#/view_trial/2541156089743802369)". Anyone concerned about this behavior change can register for the origin trial and give feedback. – Peter O. Jul 29 '21 at 20:53
  • I am also facing same issue . Such sudden change in chrome has become a showstopper for one of my client's business . Is there any quick small workaround that can be added in the code without asking end-user to make any change in their browser ? – Sudeshna Bhattacharya Jul 30 '21 at 17:50
  • 1
    It looks like they patched it. I am using 92.0.4515.131 and it is no longer an issue like it was in 92.0.4515.107. – Trisped Aug 02 '21 at 22:24
  • 1
    @Trisped Yes, you are right. It is working again, in my case in little bit older version then 92.0.4515.131. Nice fail of Google. Just updated to 92.0.4515.131 and working too. Solution in my answer https://stackoverflow.com/a/68557341/3826175 has advantage, that iframe domain is not revealed (domain from address bar is now used in dialogs). I have implemented it on several projects in hurry and now I will keep using it. – mikep Aug 03 '21 at 10:18
  • News from the [Chromium team](https://groups.google.com/a/chromium.org/g/blink-dev/c/hTOXiBj3D6A), "*We have decided to postpone this deprecation until at least January 2022* (...)" – Déjà vu Sep 27 '21 at 12:41

3 Answers3

24

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

})();
mikep
  • 5,880
  • 2
  • 30
  • 37
  • Thank you mikep! My current solution was disabled these functions... I will test is out lately! But ultimately, I hope chrome just turn on the feature again. It can be made just one line code, but it's now take so many time to work around. – JFtyv_85StvsDpDn Jul 28 '21 at 21:14
  • 1
    I want to point out: Google Chrome is running a so-called "[origin trial](https://developer.chrome.com/blog/origin-trials/)" on the behavior change highlighted here. It's called "[Disable Different Origin Subframe Dialog Suppression](https://developer.chrome.com/origintrials/#/view_trial/2541156089743802369)". Anyone concerned about this behavior change can register for the origin trial and give feedback. – Peter O. Jul 29 '21 at 20:53
  • I am getting an error even if the parent and iframe are on the same root domain, for example `parent.example.com` and `iframe.example.com`. – lunr Aug 01 '21 at 13:45
  • @lunr you are probably doing something wrong because as you can see this demo https://domain-a.netlify.app/parent.html works well and there is also parent and iframe on the same root domain netlify.app (just subdomains are different domain-a.netlify.app vs domain-b.netlify.app) – mikep Aug 02 '21 at 08:01
  • @mikep In the example they have overridden the `alert` etc. methods and use `window.postMessage` to display the alert from parent window. – lunr Aug 03 '21 at 08:39
  • @lunr I do not understand you what do you mean. Demo uses the same code from this answer on stackoverflow. Both demo and code here overrides native dialogs and uses window.postMessage. You can provide links where you have implemented it and I can check it for you. My solution is simple and must work, you have probably some mistake. Did you correctly included dialogs.js in both parent and iframe? – mikep Aug 03 '21 at 10:01
  • @mikep Sorry, I wasn't clear. I wasn't talking about your examples. I mean these functions are blocked even if the parent and iframe are on the same root domain but have different subdomains (unless you implement a work-around like yours). – lunr Aug 04 '21 at 10:52
  • @lunr of course for cross-origin in case of subdomains does not matter that root domain is the same (cross origin policy is applied) – mikep Aug 04 '21 at 13:04
4

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.

Robert Gaum
  • 129
  • 1
  • 10
  • 6
    This is not solution. Developers can not affect this on client side. – mikep Jul 28 '21 at 10:23
  • Using nested x-origin iframes are commonly used by businesses with legacy code. Usually those businesses have an IT department who can choose which browsers they want their employees to use, as well as startup parameters. This might not work for everyone, but it could work for some. – Robert Gaum Jul 28 '21 at 12:36
4

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"

Iamblichus
  • 18,540
  • 2
  • 11
  • 27
  • Also: Google Chrome is running a so-called "[origin trial](https://developer.chrome.com/blog/origin-trials/)" on the behavior change highlighted here. It's called "[Disable Different Origin Subframe Dialog Suppression](https://developer.chrome.com/origintrials/#/view_trial/2541156089743802369)". Anyone concerned about this behavior change can register for the origin trial and give feedback. – Peter O. Jul 29 '21 at 21:00