Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 259
JFtyv_85StvsDpDn Avatar asked Jul 22 '21 23:07

JFtyv_85StvsDpDn


1 Answers

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);  })(); 
like image 92
mikep Avatar answered Sep 21 '22 07:09

mikep