NexusDuck NexusDuck - 4 months ago 34
Javascript Question

Polyfill window.showModalDialog in webview

So I am creating a Chrome app with an embedded

<webview>
element. The embedded webapp contains a lot of legacy code such as
window.showModalDialog
calls (which Chrome no longer supports).

The thing I'm trying to do now is to polyfill those calls. I've created a simple test example:

<webview src="http://legacyCodeIsAwesome.com/" style="width:100%; height:100%"></webview>


Code operating on the webview element:

webview.addEventListener('contentload', function() {
webview.executeScript(
{
code: 'window.showModalDialog = function() { console.log ("Hello, world");}',
runAt: 'document_start'
}
);
});


The above code runs (adding a debug
console.log
works), only it doesn't do what it's supposed to do, which is overwrite the
showModalDialog
function.

Is there some sort of restriction on overwriting functions on the
window
object in webviews? Is there a way around it?

Xan Xan
Answer

When you call webview.executeScript, you essentially create a Content Script.

One of the core principles of content scripts is isolated world: a content script sees a separate copy of window object.

Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.

So, your override works, but only in the content script context.

To perform the override in the page context itself, we need to go deeper. We need to go deeper

A content script can create a <script> element on a page; code in said element will execute in page's context. You can read about this technique, called page-level scripting or injected scripts, in this canonical question.

// Content script
var script = document.createElement('script');
script.textContent = `
  window.showModalDialog = function() {
    console.log("Hello, world");
  }
`;
(document.head||document.documentElement).appendChild(script);
script.remove();

That said, you're injecting your code far too late. On contentload the offending JS likely executed already, and adding "document_start" isn't going to rewind time.

Fortunately, you can declare in advance the content scripts you want to have:

webview.addContentScripts([
  {
    name: "showModalDialogPolyfill",
    matches: ["https://webapp.example.com/*"],
    js: { files: ['content.js'] },
    run_at: 'document_start'
  }
]);
webview.src = "https://webapp.example.com/";
Comments