Shadow DOM: is it possible to encapsulate JS?
Asked Answered
I

4

13

I'm trying to find a way to encapsulate Javascript without using iframes. Ideally, I'd like a way to load external HTML components (widgets) on a parent page, without the two-step loading process, which comes with using iframes (first the host page loads, and only then does the iframe content is loaded).

Is it possible to accomplish that with some of the new web components technologies - shadow DOM / templates / imports? I was able to come close with adding HTML to shadow DOM and encapsulating CSS, but couldn't confirm whether it's possible to get a separate document for the component's javascript execution.

Inchworm answered 14/2, 2015 at 16:4 Comment(0)
A
6

Web-components, being used through HTML imports, encapsulate both Shadow DOM HTML and related script.

To narrow the terminology, let’s consider we have a Polymer component core-ajax. Here is the code. As you might see, it does not provide any HTML markup at all, encapsulating the script only.

Once imported on the host webpage as:

<link 
  rel="import"
  href="https://www.polymer-project.org/components/core-ajax/core-ajax.html"> 

this component provides an ability to perform AJAX calls without any javascript coding:

<core-ajax
  auto
  url="http://gdata.youtube.com/feeds/api/videos/"
  params='{"alt":"json", "q":"chrome"}'
  handleAs="json"
  response='{{response}}'
</core-ajax>

The above will load (since auto attribute is set) the content of the specified URL and put the response into bound response variable. Another way to communicate with this component is to supply handler instead of binding template variable to response:

  - response='{{response}}'
  + on-core-response="{{handleResponse}}"

One might implement handleResponse function in the page’s javascript and that’s it.

UPD Although currently there is no ability to distinguish main document and one used by shadow DOM, this feature is being discussed for almost three years in w3c mailgroups. The discussion is far from conclusion, though, even in aspects like “whether we never entirely enable them in author space.”

Apotropaic answered 14/2, 2015 at 18:42 Comment(6)
Thanks @mudasobwa! Does the script which is loaded via the polymer have a separate document? I'm particularly interested in separating the context of the scripts, similarly to running scripts in different frames.Inchworm
I would suggest you to read the W3C spec on imports. No, the document would be the same (and there is no ability to create another embedded document besides iframe, and I bet there will not be such an ability forever.)Apotropaic
Thanks! This answer seems to imply that this will be supported via Shadow DOM in the future, but I am curious whether there is already a solution with other methods.Inchworm
@Inchworm I have the discussion read and it seems that there is a possibility for us to see the feature you requested in the future. Will update my answer.Apotropaic
I have a document that I am trying to turn into a Shadow DOM widget now. I make references to document.body and would like to turn those references into the outermost container of my widget. I make references to document.body for event handling and it has to be possible to have several instances of my component on a page and have an instance of the component inside itself. (My component is a container.) It would help in a big way to be able to refer to root of my shadow DOM with embedded JS.Pseudo
Note that 9 years later, HTML imports are completely deprecated.Nirvana
K
3

This isn't related to the shadow dom, but it is related to encapsulating components entirely using javascript: https://benfrain.com/sandbox-local-htmlcss-code-snippets-inside-iframe-style-guidespattern-libraries/

Basically you create an iframe node and inject css and javascript into it:

var newIframe = document.createElement('iframe')
newIframe.contentWindow.document.open('text/html', 'replace')
var content = '<!DOCTYPE html><html><head>'+CSS+'</head><body>'+HTML+JS+'</body></html>'
newIframe.contentWindow.document.write(content)
newIframe.contentWindow.document.close()

To completely isolate the javascript from accessing its parent page, I believe you can modify the parent page's document.domain with some kind of randomly generated domain so that, to the new iframe, the parent page will look like its in a different domain and has no way of changing its domain to match. This would come with all the usual security constraints. Then you can safely talk to the child iframe via postmessage.

Theoretically you could also implement some communication to automatically resize the iframe element depending on its content, simulating a non-iframe element in-flow.

This is something I want to experiment with in the future, but haven't tried out yet.

Kathaleenkatharevusa answered 30/4, 2016 at 0:25 Comment(0)
G
2

Yes, you can add <style> and <script> tags inside a template (snippet only runs in browsers that support Shadow DOM):

// Create the shadow DOM
var shadow = document.querySelector('#testOutput').attachShadow({mode: 'open'});

// Get the template fragment and add it to the shadow DOM
shadow.appendChild(document.querySelector('#testTemplate').content);
<template id="testTemplate">
  <script>
    alert('Hello from the component');
  </script>
</template>

<div id="testOutput">shadow</div>

Or you can add them direct to the Shadow DOM.

However, this is currently broken by content security policy and is a high XSS risk.

Grundy answered 7/9, 2016 at 9:27 Comment(0)
S
0

I've noticed that scripts running in the element, attached to the Shadow DOM are interfering with the scripts in the main page. Which was something I was trying to avoid. The scripts from the main page are not accessing the Shadow DOM, but as I noted, the shadow Dom scripts are messing up with the scripts from the main App. This unlike Iframes is not full encapsulation.

Semiconscious answered 9/4 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.