3

I've been trying to figure this out for hours to no avail.

Basically, I'm making a Chrome Extension that loads a bunch of HTML into the page. To prevent a lot of issues, I wanted to use Shadow DOM for encapsulation. CSS encapsulates fine, and after a while I finally managed to load JS into the DOM as well.

Problem is, I'm including 2 JS files:

  • vendor.js (JS libraries I use, compiled into a single file. Notable libraries: jQuery and Angular)
  • extension.js - my own code compiled into a single file (does some inits, bootstraps Angular and defines controllers/directives/etc)

The problem is, after loading the vendor.js file, I can't access things it applied inside the other script.

For the life of me I can't figure out which context it signs into and uses as a "window" element to inject classes into.

Here's my Shadow DOM initial loading script:

// HACK: we need to "bleed" font-faces to outside the shadow dom because of a bug in chrome
document.querySelector('head').innerHTML +=
  '<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" />'

let rootElHtml = `
    <div id="cls-root">
      <template id="cls-template">
      </template>
    </div>
  `

document.body.innerHTML += rootElHtml
let rootEl = document.querySelector('#cls-root'),
  templateEl = rootEl.querySelector('template'),
  shadow = rootEl.attachShadow({
    mode: 'open'
  })

let cssLinks = [
  chrome.extension.getURL('dist/css/vendor.css'),
  chrome.extension.getURL('dist/css/extension.css'),
  '//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'
].map(p => `<link rel="stylesheet" href="${p}" />`).join("\n")

let jsLinks = [
  chrome.extension.getURL('dist/js/vendor.js'),
  chrome.extension.getURL('dist/js/extension.js'),
].map(p => `<script src="${p}" type="text/javascript"></script>`).join("\n")

// inject css/js into template content
let templateNodeContents = document.createElement('div')
templateNodeContents.innerHTML = [cssLinks, jsLinks].join("\n")
for (let node of templateNodeContents.childNodes)
  templateEl.content.appendChild(node)

// load remote HTML template and apply into shadow root template
fetch(chrome.extension.getURL('dist/templates/root.html')).then((data) => {
  data.text().then((body) => {
    let bodyContainer = document.createElement('div')

    shadow.appendChild(document.importNode(templateEl.content, true))
    bodyContainer.innerHTML = body
    shadow.appendChild(bodyContainer.childNodes[0])
  })
})

window._clsExt = shadow

And here's me trying to bootstrap Angular:

const shadow = document.querySelector('#cls-root').shadowRoot

shadow.APP_NAME = 'myApp'
shadow.app = angular.module(APP_NAME, ['ngStorage'])
  .config(['$localStorageProvider', '$httpProvider',
    ($localStorageProvider, $httpProvider) => {
      $localStorageProvider.setKeyPrefix('myApp');
      $httpProvider.interceptors.push(apiInterceptor);
    }
  ])
shadow.app.constant('APP_ENV', APP_SETTINGS.app_env)
shadow.app.constant('API_BASE', APP_SETTINGS.api_base_url)
angular.bootstrap(shadow, [APP_NAME])

However, I keep encountering errors about angular:

extension.js:21 Uncaught ReferenceError: angular is not defined
    at extension.js:21

I tried looking around inside the properties of the shadow element, as well as other window variations and attempting to find the document inside.

Is this possible without combining the files together by force? I'd like to have this separation remain.

casraf
  • 21,085
  • 9
  • 56
  • 91
  • As per my understanding the Javascript code is not scoped or Inert to the shadow tree. The Javascript should be global. – bhantol Jul 11 '17 at 14:43
  • @bhantol Well I was hoping not, because angular usually auses problems when running twice (so if the extension runs on a website with angular in it, I would get init problems). But as it seems, there's nowhere for me to access it, `angular` is not available globally and I couldn't find a reference to it in any other object I could think of – casraf Jul 11 '17 at 14:52
  • Assuming the bootstrap code is in extension.js. Try adding `jsLinks` to the `document.querySelector('head')` or body. I don't have setup to try. – bhantol Jul 11 '17 at 14:58
  • Unfortunately that defeats the purpose as I need it to encapsulate somehow. Should I understand there is no actual JS encapsulation whatsoever in Shadow DOM? – casraf Jul 11 '17 at 15:00
  • According to http://robdodson.me/shadow-dom-javascript/ it seems those objects fall on to window. So I think they are not scoped since there is only one `window`. I am learning this myself - lets hope someone knowleable in shadow DOM answers. A side note - you may also be running into CSP security issue. – bhantol Jul 11 '17 at 15:12
  • @bhantol Thanks. After a bit of fiddling around, seems that although vendor.js is loaded first in order, it finishes loading later (or maybe there is some DOMContentLoaded listener there somewhere). In any case, once I wait for page load, angular and jQuery are definitely available - but now I have to figure out how to load all the directives and controllers only after – casraf Jul 11 '17 at 15:25

1 Answers1

2

I see that you add JS files into the Shadow DOM scope. It doesn't work as accessing the elements in Shadow dom is different and the libraries don't work inside them.

When I say accessing DOM is different

$(#id) //the usual way to access a id using jQuery
parentofShadowtree.shadowRoot.querySelector('#id') //to select a element inside shadow DOM

As you can see accessing DOM elements inside Shadow DOM is different, the usual JS library files can't access the elements and don't work here

Correct me if I am wrong.

vba
  • 526
  • 8
  • 20
  • `jQuery('#id', parentofShadowtree.shadowRoot)` would work just fine – Bergi Jul 13 '19 at 19:08
  • @Bergi I mean to say if I try to use bootstrap.js to manipulate shadow DOM, it will not work because the bootstrap library way of accessing elements will not work here – vba Jul 13 '19 at 19:12