├── demo ├── page-purple.css ├── page-violet.css ├── page.css ├── poc │ ├── mod-page-dep.js │ ├── mod-page.js │ ├── mod-page.html │ ├── x-origin.html │ └── global-scope.html ├── listing-search-shim.css ├── appendTag.js ├── html-events-globals-sample1.js ├── listing-search-shim.js ├── postMessage-app.html ├── page-storage.js ├── current-script-microapp.html ├── postMessage-app.js ├── sw.js ├── current-script.html.js ├── html-include.html.test.js ├── html-as-component.html ├── appsList-microapp.html.js ├── inline-demo-component.js ├── window-scope.html.test.js ├── appsList-microapp.html ├── page.js ├── code-view.js ├── dynamic-binding.html ├── demo-menu.html ├── scriptlets │ ├── change-bind.js │ └── number-format.js ├── page-violet.html ├── page-purple.html ├── microapp-LunchTime.html ├── html-events-globals.html.test.js ├── page-storage.html ├── script-as-component.html ├── postMessage.html.test.js ├── polymer-view.html ├── storage-view.html ├── poc-dynamic-binding.html ├── current-script.html.test.js ├── window-scope.html ├── html-include.html ├── storage.html.test.js ├── single-instance.html ├── postMessage.html ├── scriptlets.html ├── storage.html ├── index.html ├── html-events-globals.html ├── link-form-navigation.html.test.js ├── appsList.html ├── inline-content.html ├── link-form-navigation.html ├── appList.html.test.js └── current-script.html ├── .gitignore ├── bower.json ├── test ├── page-dom-api.js ├── EpaParser.html ├── index.html ├── page-dom-api.html └── EpaParser.test.js ├── forms.md ├── index.html ├── events.md ├── z-notes.md ├── security.md ├── EpaParser.js ├── polymer.json ├── package.json ├── TODO.md ├── README.md └── LICENSE /demo/page-purple.css: -------------------------------------------------------------------------------- 1 | a{ color: purple; } -------------------------------------------------------------------------------- /demo/page-violet.css: -------------------------------------------------------------------------------- 1 | a{ color: violet; } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .idea/ 3 | package-lock.json 4 | build 5 | *.iml -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "embed-page", 3 | "license": "LICENSE" 4 | } -------------------------------------------------------------------------------- /demo/page.css: -------------------------------------------------------------------------------- 1 | textarea{width: 100%; height: 4em;} 2 | fieldset{display: inline-block;} 3 | -------------------------------------------------------------------------------- /demo/poc/mod-page-dep.js: -------------------------------------------------------------------------------- 1 | export default function abc( text ) 2 | { 3 | return `Hello ${text}` 4 | } -------------------------------------------------------------------------------- /demo/listing-search-shim.css: -------------------------------------------------------------------------------- 1 | footer,.nav-wrapper,.widget-get-in-touch,.pl_compliance-wrapper{ display:none!important} 2 | -------------------------------------------------------------------------------- /demo/poc/mod-page.js: -------------------------------------------------------------------------------- 1 | import dep from 'mod-page-depo'; 2 | export default function abc( text ) 3 | { 4 | return dep( text ) 5 | } -------------------------------------------------------------------------------- /demo/appendTag.js: -------------------------------------------------------------------------------- 1 | export function appendTag(tag, text, parentNode ) 2 | { let t = document.createElement(tag); 3 | t.innerHTML = text; 4 | (parentNode || document.body ).appendChild(t); 5 | } 6 | -------------------------------------------------------------------------------- /demo/html-events-globals-sample1.js: -------------------------------------------------------------------------------- 1 | byId.value="0";// 2 | global1 = 1; // same as declared with var 3 | var global2 = 2; 4 | const global3 = 3; 5 | let global4 = 4; 6 | function globalFunc(){} 7 | -------------------------------------------------------------------------------- /demo/listing-search-shim.js: -------------------------------------------------------------------------------- 1 | (function ( $ ) 2 | { 3 | $(function() 4 | { 5 | $('footer,.nav-wrapper,.widget-get-in-touch,.pl_compliance-wrapper').css('display','none!important'); 6 | }) 7 | })(jQuery); -------------------------------------------------------------------------------- /demo/poc/mod-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cross-domain js module dependency load 5 |

baseUrl

6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /demo/postMessage-app.html: -------------------------------------------------------------------------------- 1 | postMessage API test 2 |

postMessage API test

3 | 4 | 5 | 6 | 7 |
message event contentorigin url
8 | 9 | -------------------------------------------------------------------------------- /demo/page-storage.js: -------------------------------------------------------------------------------- 1 | var url = location.href.substring(location.href.lastIndexOf('/')+1); 2 | localStorage.setItem( 'a',"localStorage " + url); 3 | window.localStorage.setItem( 'b',"window.localStorage " + url); 4 | sessionStorage.setItem( 'a',"sessionStorage " + url); 5 | window.sessionStorage.setItem( 'b',"window.sessionStorage " + url); -------------------------------------------------------------------------------- /test/page-dom-api.js: -------------------------------------------------------------------------------- 1 | let cb = document.getElementById("external"); 2 | cb.checked=true; 3 | [...document.getElementsByTagName('button')].forEach( b => b.onclick = ()=>ToggleCb(b) ); 4 | 5 | function ToggleCb( b ) 6 | { let a = document.getElementsByClassName( b.getAttribute('for') ); 7 | for( let x of a ) 8 | x.checked = !x.checked; 9 | } -------------------------------------------------------------------------------- /test/EpaParser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/current-script-microapp.html: -------------------------------------------------------------------------------- 1 | expected ABCD
2 | expected ABCD 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /forms.md: -------------------------------------------------------------------------------- 1 | # Forms handling by embed-page 2 | 3 | ## On click 4 | 5 | 1. for A or FORM dom tree ancestor 6 | * target set to embed-page IFRAME name 7 | * blank form.action set to this.src 8 | * other action is kept intact to preserve the actual submit into IFRAME 9 | 2. browser submits(get/post) into IFRAME 10 | 3. IFRAME onload handler (on before load TBD) 11 | this.src = frame.src 12 | * which triggers content load 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | embed-page, redirecting... 7 | 8 | 9 | Visit demo/index.html to see live examples of your element running. 10 | This page will automatically redirect you there when run in the browser 11 | with `polymer serve`. 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/postMessage-app.js: -------------------------------------------------------------------------------- 1 | const url = location.href.substring(location.href.lastIndexOf('/')+1) 2 | , dopost = x=>(window.parent ||window.opener).postMessage(url,'*'); 3 | 4 | window.addEventListener('message', ev=> 5 | document.querySelector('tbody').innerHTML += ` 6 | ${ev.data}${ev.origin}` ); 7 | 8 | window.addEventListener("load", x=> 9 | { 10 | document.querySelector('button').addEventListener('click', dopost ); 11 | dopost(); 12 | }); -------------------------------------------------------------------------------- /events.md: -------------------------------------------------------------------------------- 1 | # storage 2 | The `storage` event is propagated as usual window event in a context `` with common scope. 3 | Depend of **scope** attribute either **src** URL or **name** value is used to define `` common 4 | scope instances. 5 | 6 | Each `` instance listens for `storage` window event. 7 | If event **key** matches the own prefix (made out of name, uid, or URL depend of **scope** ) 8 | , the event is dispatched by own EpaWindow instance triggering the listeners within `` context. 9 | -------------------------------------------------------------------------------- /z-notes.md: -------------------------------------------------------------------------------- 1 | # temp notes on dev process 2 | Have a sense along with commits. Content would be cleared on commit following 3 | the annotated here commit. 4 | # build process 5 | ## Goal 6 | * `esm-unbundled` build with demo files. 7 | * `esm-bundled/embed-page.js` bundled with Polymer for 1-file embedding into page. 8 | 9 | ## esm-bundled 10 | 89Kb - too much 11 | 50Kb - src 12 | 40Kb - compiled( unbundled ) 13 | way to reduce: 14 | * remove PolymerElement as parent 15 | * remove comments 16 | * ? replace regex-ex with parsing algorithms 17 | 18 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/sw.js: -------------------------------------------------------------------------------- 1 | // The SW will be shutdown when not in use to save memory, 2 | // be aware that any global state is likely to disappear 3 | console.log("SW startup"); 4 | 5 | self.addEventListener('install', function(event) { 6 | console.log("SW installed"); 7 | }); 8 | 9 | self.addEventListener('activate', function(event) { 10 | console.log("SW activated"); 11 | }); 12 | 13 | self.addEventListener('fetch', function(event) { 14 | console.log("Caught a fetch!"); 15 | debugger; 16 | event.respondWith(new Response("console.log('document.location.protocol',document.location.protocol)")); 17 | }); 18 | -------------------------------------------------------------------------------- /test/page-dom-api.html: -------------------------------------------------------------------------------- 1 | 2 |

Purple header

3 | Embedded and external script inclusion sample 4 |
  • 5 | checked by embedded script.
  • 6 |
  • 7 | checked by external script
  • 8 |

9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/current-script.html.js: -------------------------------------------------------------------------------- 1 | var cs = document.currentScript; 2 | 3 | if( cs ) 4 | { scriptTitle2Input('.current-script-related', cs.parentNode ); 5 | // console.log( 'executed',cs ); 6 | appendText( 'called ' + cs.outerHTML.substring(0,47).replace( / has given a flexibility of embedded DOM and IFRAME kind of browsing 9 | context insulation. 10 | 11 | Unlike direct injection of 3rd party script EPA executes JS in host page with 12 | insulation layer preventing access to document, window and major APIs. 13 | 14 | \ at this stage yet a proof of concept for 15 | [Embeddable Progressive Application](https://github.com/EPA-WG/EPA-concept) and potentially 16 | the polyfill for standard-to-be implemented natively by browser with all security concerns addressed. 17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/html-include.html.test.js: -------------------------------------------------------------------------------- 1 | 2 | suite('embed-page HTML include', () => 3 | { 4 | test('initial src set, loaded event fired & content ready', function(done) 5 | { 6 | const E = document.querySelector('#html-include-test'); 7 | assert.equal(E.src, "demo-menu.html?inline"); 8 | assert.equal( E.getAttribute('src'), "demo-menu.html?inline" ); 9 | if( E.readyState === "complete") 10 | { 11 | const m = document.querySelector('nav'); 12 | assert.equal(m.id, 'menu'); 13 | done() 14 | } 15 | else E.addEventListener('load', x=> 16 | { 17 | const m = document.querySelector('nav'); 18 | assert.equal(m.id, 'menu'); 19 | done() 20 | }); 21 | }); 22 | }); -------------------------------------------------------------------------------- /demo/html-as-component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML as Component 5 | 9 | 10 | 11 | Set background to: 12 | 13 | green 14 | 15 | 16 | yellow 17 | 18 | 19 | blue 20 | 21 | 22 | reload page 23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/poc/x-origin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cross-domain js module dependency 6 | 7 | 8 | cross-domain js module dependency load 9 |

baseUrl

10 | 11 | 39 | 40 | -------------------------------------------------------------------------------- /demo/appsList-microapp.html.js: -------------------------------------------------------------------------------- 1 | const $ = css=>document.querySelector(css) 2 | , setClick = (css,cb)=> $(css).addEventListener('click',cb); 3 | window.addEventListener('load', renderAppsList ); 4 | setClick( '.app-list-refresh' , renderAppsList ); 5 | setClick( '.app-list-close' , x=> window.close() ); 6 | setClick( '.app-list-open' , x=> 7 | { const name = $('.app-list-name').value 8 | , url = $('.app-list-url' ).value; 9 | window.open(`${url}?name=${name}`, name, {target:window.target}); 10 | renderAppsList(); 11 | }); 12 | 13 | function renderAppsList() 14 | { 15 | document.querySelector('h1').innerText = location.search || location.pathname; 16 | const el = document.createElement('div') 17 | , encode = t => (el.innerText=t) && el.innerHTML 18 | , format = ( w, name )=> // as TR 19 | `${ [ name, w.name, w.target, w.location.pathname + w.location.search ]// 20 | .map( t=>''+encode(t)+'').join('') }`; 21 | let p = format( window.parent, 'parent'); 22 | let fr = frames; 23 | for( let i = 0; i < fr.length; i++ ) 24 | p += format( fr[i], `frames[${i}]` ); 25 | fr = parent.frames; 26 | for( let i = 0; i < fr.length; i++ ) 27 | p += format( fr[i], `parent.frames[${i}]` ); 28 | document.querySelector('.apps-list').innerHTML = p ; 29 | } -------------------------------------------------------------------------------- /demo/inline-demo-component.js: -------------------------------------------------------------------------------- 1 | import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; 2 | import '@polymer/paper-listbox/paper-listbox.js'; 3 | import '@polymer/paper-item/paper-item.js'; 4 | import '@polymer/iron-ajax/iron-ajax.js'; 5 | import '@vaadin/vaadin-combo-box/vaadin-combo-box.js'; 6 | // import '../embed-page.js'; 7 | 8 | class InlineDemoComponentHtml extends PolymerElement 9 | { 10 | static get is() { return 'inline-demo-component'; } 11 | static get template() 12 | { 13 | return html` 14 | 17 | violet 18 | purple 19 | 20 | selected:[[url]] 21 | 22 | 25 | 26 | 27 | 28 | 29 | `; 30 | } 31 | } 32 | customElements.define(InlineDemoComponentHtml.is, InlineDemoComponentHtml); 33 | -------------------------------------------------------------------------------- /EpaParser.js: -------------------------------------------------------------------------------- 1 | import {LooseParser} from "acorn-loose"; 2 | export default function parse( code ) /** throws SyntaxError exception */ 3 | { 4 | const ret = { imports:[], vars:[], consts:[], lets:[], funcs: [] }; 5 | const ast = LooseParser.parse( code, {sourceType:"module"} ); 6 | ast.body.forEach( n=> 7 | { 8 | switch( n.type ) 9 | { 10 | case "ImportDeclaration": // ret.imports.push( n.source.value ); 11 | break; 12 | case "FunctionDeclaration": 13 | ret.funcs.push( n.id.name ); 14 | break; 15 | case "VariableDeclaration": 16 | n.declarations.forEach( d=> 17 | { 18 | ({ Identifier : x=> ret[ n.kind+'s'].push(d.id.name) 19 | , ObjectPattern : x=> d.id.properties.forEach( e=> ret[ n.kind+'s'].push( e.value.name ) ) 20 | , ArrayPattern : x=> d.id.elements .forEach( e=> ret[ n.kind+'s'].push( e.name ) ) 21 | })[ d.id.type ]() 22 | }); 23 | break; 24 | } 25 | }); 26 | return ret; 27 | } 28 | /* goal 29 | * replace imports & apply URL mapping - for now by regEx 30 | * find var, const, let, function 31 | * find undeclared vars assignment, including internal in function( need to track local vars to find undeclared ) 32 | */ -------------------------------------------------------------------------------- /polymer.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm": true, 3 | "lint": { 4 | "rules": [ 5 | "polymer-3" 6 | ] 7 | }, 8 | "entrypoint": "demo/index.html", 9 | "shell": "embed-page.js", 10 | "sources": [ 11 | "demo/**/*", 12 | "demo/*.*", 13 | "test/**/*" 14 | ], 15 | "extraDependencies": [ 16 | "node_modules/@webcomponents/webcomponentsjs/*.js", 17 | "node_modules/wct-browser-legacy/*.js", 18 | "node_modules/codemirror/lib/*.js", 19 | "node_modules/@polymer/iron-demo-helpers/*.js", 20 | "node_modules/@polymer/iron-demo-helpers/node_modules/@polymer/font-roboto/*.*", 21 | "node_modules/@polymer/test-fixture/*.*", 22 | "node_modules/**/*", 23 | "!node_modules/@webcomponents/webcomponentsjs/gulpfile.js", 24 | "node_modules/@webcomponents/webcomponentsjs/bundles/*.js" 25 | ], 26 | "builds": [ 27 | { 28 | "name": "esm-unbundled", 29 | "browserCapabilities": [ 30 | "es2015", 31 | "modules" 32 | ], 33 | "js" : { "minify": false }, 34 | "css" : { "minify": false }, 35 | "html": { "minify": false }, 36 | "bundle": false, 37 | "addServiceWorker": false 38 | }, 39 | { 40 | "name": "esm-bundled", 41 | "browserCapabilities": [ 42 | "es2015", 43 | "modules" 44 | ], 45 | "js" : { "minify": true }, 46 | "css" : { "minify": true }, 47 | "html": { "minify": true }, 48 | "bundle": true, 49 | "addServiceWorker": true 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /demo/window-scope.html.test.js: -------------------------------------------------------------------------------- 1 | suite('embed-page inline content', () => 2 | { 3 | let e0, e1, $e0, $e1, $$ = css => document.querySelector(css); 4 | 5 | let AllReady; 6 | 7 | setup( ()=> AllReady || (AllReady = wait4all().then( args => 8 | { [ e0, e1 ] = args; 9 | [ $e0, $e1 ] = args.map( epa => ( css => epa.shadowRoot.querySelector(css) ) ); 10 | })) 11 | ); 12 | const getEpaInputValue = ( epaId, cl ) => document.getElementById(epaId).shadowRoot.querySelector( 'input.' + cl ).value; 13 | 14 | test('1. epa0 window.abc=ABC', function() 15 | { 16 | const v = getEpaInputValue( "e0", "w"); 17 | assert.equal( v, "ABC" ); 18 | }); 19 | test('2. epa0 abc==ABC', function() 20 | { 21 | const v = getEpaInputValue( "e0", "g"); 22 | assert.equal( v, "ABC" ); 23 | }); 24 | test('3. epa1 window.abc=XYZ', function() 25 | { 26 | const v = getEpaInputValue( "e1", "w"); 27 | assert.equal( v, "XYZ" ); 28 | }); 29 | test('4. epa1 abc==XYZ', function() 30 | { 31 | const v = getEpaInputValue( "e1", "g"); 32 | assert.equal( v, "XYZ" ); 33 | }); 34 | 35 | function 36 | wait4all() 37 | { 38 | return Promise.all( ["e0","e1"].map( wait4load ) ); 39 | } 40 | function 41 | wait4load( id ) 42 | { 43 | const E = document.getElementById( id ); 44 | assert.notEqual( E, null ); 45 | return E.promiseNext; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /demo/appsList-microapp.html: -------------------------------------------------------------------------------- 1 | 2 |

appsList-microapp.html

3 | 4 | 5 | 6 | 7 |
8 | link=A no target | 9 | target="_self" | 10 | target="_parent" | 11 | target="_top" | 12 | link=A target="A" | 13 | link=B target="B" | 14 | link=B target="C"
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 |
windownametargeturl
23 | 24 | 25 | -------------------------------------------------------------------------------- /demo/page.js: -------------------------------------------------------------------------------- 1 | document.getElementById("external").checked=true; 2 | [...document.getElementsByTagName('button')].forEach( b => b.onclick = ()=>ToggleCb(b) ); 3 | 4 | function ToggleCb( b ) 5 | { let a = document.getElementsByClassName( b.getAttribute('for') ); 6 | for( let x of a ) 7 | x.checked = !x.checked; 8 | } 9 | const $ = css => document.querySelector(css) 10 | , getApiText = ()=> $('input[name=l]:checked').value 11 | , locationText = $('textarea'); 12 | 13 | $('*[value=get]').onclick = ()=> locationText.value = eval( getApiText() ); 14 | $('*[value=set]').onclick = ()=> eval( getApiText() + '=locationText.value' ); 15 | 16 | locationText.value = location; 17 | 18 | document.querySelector('*[value="other properties"]').onclick = x => locationText.value = JSON.stringify( 19 | { protocol : window.location.protocol 20 | , host : window.location.host 21 | , hostname : window.location.hostname 22 | , port : window.location.port 23 | , pathname : window.location.pathname 24 | , search : window.location.search 25 | , hash : window.location.hash 26 | , username : window.location.username 27 | , password : window.location.password 28 | , origin : window.location.origin 29 | }); 30 | 31 | $('*[value="assign()"]' ).onclick = x => window.location.assign ( locationText.value ); 32 | $('*[value="replace()"]').onclick = x => window.location.replace( locationText.value ); 33 | $('*[value="reload()"]' ).onclick = x => window.location.reload() ; 34 | $('*[value=location-win-doc]').onclick = x=> locationText.value = 35 | location === window.location && location === document.location ; 36 | $('*[value="this===window"]' ).onclick = x=> locationText.value = window === this; 37 | -------------------------------------------------------------------------------- /demo/code-view.js: -------------------------------------------------------------------------------- 1 | (function( document, window ) 2 | { 3 | const cssText=` 4 | `; 9 | const arr = [ "../node_modules/codemirror/lib/codemirror.js" 10 | , "../node_modules/codemirror/addon/selection/selection-pointer.js" 11 | , "../node_modules/codemirror/mode/xml/xml.js" 12 | , "../node_modules/codemirror/mode/javascript/javascript.js" 13 | , "../node_modules/codemirror/mode/css/css.js" 14 | , "../node_modules/codemirror/mode/vbscript/vbscript.js" 15 | , "../node_modules/codemirror/mode/htmlmixed/htmlmixed.js" 16 | ]; 17 | loadScript(); 18 | 19 | function loadScript() 20 | { var t = arr.shift(); 21 | if( !t ) 22 | return renderCode(); 23 | 24 | var s = document.createElement( 'script' ); 25 | s.src = t; 26 | s.onload = loadScript; 27 | document.head.appendChild( s ); 28 | } 29 | 30 | function renderCode() 31 | { 32 | var s = document.createElement( 'div' ); 33 | s.innerHTML = cssText; 34 | document.body.appendChild(s); 35 | [...document.getElementsByTagName('code') ].forEach( ta => 36 | { const src = ta.getAttribute('src'); 37 | src && fetch(src) 38 | .then( r=>r.text() ) 39 | .then( txt => CodeMirror( ta, { mode: src.includes('.js') ? "javascript" : "htmlmixed" 40 | , value:txt, theme:'abcdef' 41 | } ) ); 42 | }) 43 | } 44 | })(document, window); 45 | -------------------------------------------------------------------------------- /demo/dynamic-binding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | embed-page demo - dynamic binding 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 |

embed-page demo - dynamic Polymer binding

21 |

Binding via src attribute is shown on demo home page.

22 | 23 | 24 |

Web Component inline-demo-component.js shows dynamic iron-ajax {{ }} 25 | and html attribute [[ ]] binding.

26 |

Select violet/purple page to switch the URL to load content by iron-ajax

27 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /demo/demo-menu.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/scriptlets/change-bind.js: -------------------------------------------------------------------------------- 1 | // Binds one dom element text or attribute to another element text or attribute 2 | // 3 | // script parameters passed as attributes 4 | // data-src-select, data-dst-select - css selector for source and destination node 5 | // data-src-attribute - the attribute name from source to watch for populating into destination node. 6 | // if omitted the innerText of source node is used. 7 | // data-dst-attribute - attribute name of destination node which will be set on change of source node. 8 | // if omitted the innerText of destination node is used. 9 | 10 | const scr = document.currentScript; 11 | 12 | const bind = ( srcEl, srcAttr ) => 13 | { 14 | const config = { attributes: !!srcAttr 15 | , childList: !srcAttr 16 | , subtree: !srcAttr 17 | }; 18 | 19 | const update = txt => 20 | { [ ...document.querySelectorAll( scr.getAttribute('data-dst-select') ) ] 21 | .map( dst => 22 | { const attr = scr.getAttribute('data-dst-attribute'); 23 | attr ? dst.setAttribute( attr, txt ) 24 | : dst.innerText = txt; 25 | }) 26 | } 27 | , callback = mutationsList => 28 | { for( let mutation of mutationsList ) 29 | if( srcAttr ) 30 | { if( mutation.type === 'attributes' && mutation.attributeName === srcAttr ) 31 | update( el.getAttribute( srcAttr ) ); 32 | }else 33 | if( mutation.type === 'childList' ) 34 | update( el.innerText ); 35 | }; 36 | 37 | if( 'value' === srcAttr ) 38 | srcEl.addEventListener( 'input', function(){ update( this.value )} ); 39 | const observer = new MutationObserver( callback ); 40 | observer.observe( srcEl, config ); 41 | return observer; // Later, you can stop observing by calling observer.disconnect(); 42 | }; 43 | 44 | [ ...document.querySelectorAll( scr.getAttribute('data-src-select') ) ] 45 | .map( el => bind( el, scr.getAttribute('data-src-attribute') ) ); 46 | 47 | -------------------------------------------------------------------------------- /demo/page-violet.html: -------------------------------------------------------------------------------- 1 | 2 |

Violet header

3 | Embedded and external script inclusion sample 4 |
  • 5 | UNchecked by embedded script.
  • 6 |
  • 7 | checked by external script
  • 8 |
9 | Link to page-purple.html
10 | 11 |
12 | Form 13 |
14 | 15 | 16 |
17 |
18 |
19 | Form 20 |
21 | 22 | 23 |
24 |
25 | 26 |

location API

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | • 37 | 38 | 39 | • 40 | 41 | 42 | 43 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /demo/page-purple.html: -------------------------------------------------------------------------------- 1 | 2 |

Purple header

3 | Embedded and external script inclusion sample 4 |
  • 5 | checked by embedded script.
  • 6 |
  • 7 | checked by external script
  • 8 |
9 | Link to page-violet.html
10 | 11 |
12 | Form 13 |
14 | 15 | 16 |
17 |
18 |
19 | Form 20 |
21 | 22 | 23 |
24 |
25 | 26 |

location API

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | • 37 | 38 | 39 | • 40 | 41 | 42 | 43 | 44 | 45 | 48 |

Extra line for seamless embedding demo

-------------------------------------------------------------------------------- /demo/scriptlets/number-format.js: -------------------------------------------------------------------------------- 1 | // exposes Intl.NumberFormat API 2 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat 3 | // script parameters passed as attributes 4 | // data-locales, data-options matches locales and options parameter 5 | // other data-XXX attribute matches parameter `options.XXX` 6 | // value attribute as a source 7 | // script injects SPAN with formatted text before SCRIPT element unless data-no-inject attribute defined 8 | 9 | const scr = document.currentScript; 10 | scr.format = format; 11 | 12 | if( !scr.hasAttribute('data-no-inject') ) 13 | { const span = document.createElement('span'); 14 | span.innerText = format( scr.getAttribute('value') ); 15 | scr.parentElement.insertBefore( span, scr ); 16 | 17 | new MutationObserver( mutations => 18 | { 19 | mutations.forEach( mutation => 20 | { 21 | if( mutation.type === "attributes" && mutation.attributeName === 'value' ) 22 | { 23 | span.innerText = format( scr.getAttribute( 'value' ) ) 24 | } 25 | }); 26 | }).observe( scr, { subtree : false, attributes: true } ); 27 | } 28 | 29 | function format( value ) 30 | { 31 | const locales = scr.getAttribute('data-locales') 32 | , options = eval( scr.getAttribute('data-options') || '' ) || {} 33 | , attr2f = {} 34 | , f2Type = { localeMatcher:String 35 | , style: String, currency: String, currencyDisplay:String 36 | , useGrouping: Boolean 37 | , minimumIntegerDigits: Number, minimumFractionDigits: Number, maximumFractionDigits: Number, minimumSignificantDigits: Number, maximumSignificantDigits: Number 38 | }; 39 | Object.keys(f2Type).map( k=> attr2f['data-'+k.toLowerCase()] = k ); 40 | 41 | for( let attr of scr.attributes ) 42 | { let k = attr2f[ attr.name ]; 43 | if( k ) 44 | options[ k ] = f2Type[k] === Boolean 45 | ? attr.value === 'true' 46 | : f2Type[k] === Number ? attr.value*1 : attr.value; 47 | } 48 | return ( locales 49 | ? Intl.NumberFormat( locales, options ) 50 | : Intl.NumberFormat( options ) 51 | ).format( value ); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /demo/microapp-LunchTime.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | embed-page demo - inline html 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 |

embed-page demo - external microapplication

21 | embed-page is a microapplication container. Microapplication could be served as from same as by external server. 22 |

Lunch Time microapplication resides in 23 | cdn.xml4jquery.com. 25 |

26 | 27 | 28 |

1. This H3 element has default styles, but H3 within this embed-page content should have a 29 | chocolate text color

30 | 39 |
40 | 41 |

Visual demo steps

42 |
43 | CSS
44 |     1. The H3 header above should be intact(default) in color and text size
45 |         AND h3 within embed-page use chocolate text color.
46 |     2. The H3 from embedded content should be in monospace font.
47 | JS  3. Click on 'make blue' the embedded H3 should be blue colored.
48 |     4. After 'innerHTML' click the H3 from embedded content should become green.
49 | 
50 | 51 | 52 | -------------------------------------------------------------------------------- /demo/html-events-globals.html.test.js: -------------------------------------------------------------------------------- 1 | suite('embed-page basics IFRAME test ', () => 2 | { 3 | let epa0, $0, epa1, $1; 4 | 5 | let AllReady; 6 | 7 | setup( ()=> AllReady || (AllReady = wait4all().then( args => 8 | { [ epa0, epa1 ] = args; 9 | [ $0, $1 ] = args.map( epa => ( css => epa.shadowRoot.querySelector(css) ) ); 10 | })) 11 | ); 12 | 13 | test('1. initial set', function() 14 | { 15 | assert.equal( $0('input').value, '' ); 16 | }); 17 | test('2. 18 | { 19 | SimClick( $0( '.green' ) ); 20 | assert.equal( $0( 'input' ).value, 'green' ); 21 | assert.equal( $1('input').value, '' ); 22 | SimClick( $1( '.green' ) ); 23 | assert.equal( $1( 'input' ).value, 'green' ); 24 | }); 25 | test('3. ', ()=> 40 | { 41 | SimClick( $0( 'button' ) ); 42 | assert.equal( $0( 'input' ).value, 'blue' ); 43 | }); 44 | test('6. reload, final set ', function() 45 | { 46 | let ret = epa0.promiseNext; 47 | SimClick( $0('#reload') ); 48 | return ret.then( x=> 49 | { 50 | assert.equal( $0('input').value, '' ); 51 | }); 52 | }); 53 | 54 | function 55 | SimClick( el ){ el.dispatchEvent( new MouseEvent( "click" )); } 56 | 57 | function 58 | wait4all() 59 | { 60 | return Promise.all([ wait4load( "epa-0" ), wait4load( "epa-1" ) ]); 61 | } 62 | function 63 | wait4load( id, url ) 64 | { 65 | const E = document.getElementById( id ); 66 | assert.notEqual( E, null ); 67 | if( "complete" === E.readyState ) 68 | return Promise.resolve(E); 69 | return new Promise( function( resolve, reject ) 70 | { 71 | E.addEventListener( 'load' , x=> resolve(E) ); 72 | E.addEventListener( 'error', x=> reject (E) ); 73 | } ); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /test/EpaParser.test.js: -------------------------------------------------------------------------------- 1 | import EpaParser from '../EpaParser'; 2 | suite('embed-page postMessage test ', () => 3 | { 4 | 5 | setup( ()=>{} ); 6 | 7 | // test('Blank scripts', function() 8 | // { 9 | // const scriptData = EpaParser("" ); 10 | // assert.deepEqual( scriptData.imports, [] ); 11 | // }); 12 | // test('import url', function() 13 | // { 14 | // const scriptData = EpaParser('import "abc.js";' ); 15 | // assert.deepEqual( scriptData.imports, ["abc.js"] ); 16 | // }); 17 | 18 | // test('import ABC from url', function() 19 | // { 20 | // const scriptData = EpaParser('import ABC from "abc.js";' ); 21 | // assert.deepEqual( scriptData.imports, ["abc.js"] ); 22 | // }); 23 | 24 | [ [ "function f(){}" , ['f'] ] 25 | , [ "function f(){ function z(){} }" , ['f'] ] 26 | ].forEach( T => test( T[0], function() 27 | { 28 | const scriptData = EpaParser( T[0] ); 29 | assert.deepEqual( scriptData.funcs, T[1] ); 30 | })); 31 | 32 | [ [ "var zz;" , ['zz'] ] 33 | , [ "var a,b=0,c" , ['a','b','c'] ] 34 | , [ "var [a, b] = [1,2]" , ['a','b'] ] 35 | , [ "var [x, y]=[1,2],z;" , ['x','y','z'] ] 36 | , [ "var a,{b},[c]=[1];" , ['a','b','c'] ] 37 | ].forEach( T => test( T[0], function() 38 | { 39 | const scriptData = EpaParser( T[0] ); 40 | assert.deepEqual( scriptData.vars, T[1] ); 41 | })); 42 | 43 | [ [ "let zz;" , ['zz'] ] 44 | , [ "let a,b=0,c" , ['a','b','c'] ] 45 | , [ "let [a, b] = [1,2]" , ['a','b'] ] 46 | , [ "let [x, y]=[1,2],z;" , ['x','y','z'] ] 47 | , [ "let a,{b},[c]=[1];" , ['a','b','c'] ] 48 | ].forEach( T => test( T[0], function() 49 | { 50 | const scriptData = EpaParser( T[0] ); 51 | assert.deepEqual( scriptData.lets, T[1] ); 52 | })); 53 | 54 | [ [ "const zz;" , ['zz'] ] 55 | , [ "const a,b=0,c" , ['a','b','c'] ] 56 | , [ "const [a, b] = [1,2]" , ['a','b'] ] 57 | , [ "const [x, y]=[1,2],z;" , ['x','y','z'] ] 58 | , [ "const a,{b},[c]=[1];" , ['a','b','c'] ] 59 | ].forEach( T => test( T[0], function() 60 | { 61 | const scriptData = EpaParser( T[0] ); 62 | assert.deepEqual( scriptData.consts, T[1] ); 63 | })); 64 | 65 | // import 66 | // dyn import 67 | // var 68 | // let/const 69 | // function 70 | }); 71 | -------------------------------------------------------------------------------- /demo/page-storage.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 |
14 |

15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |

"storage" event notifications

29 | 30 | 31 | 32 | 33 |
Storage key value url
34 |
35 | 36 | 59 | 60 | -------------------------------------------------------------------------------- /demo/script-as-component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Use the script tag as a component - embed-page demo 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 |

script type module demo

19 |
20 | A show case of treating the script as HTML component. Script tag is used for
21 | * passing the component parameters
22 | * defining the position of component in DOM( right after SCRIPT tag )
23 | 
24 | The default script type="module" behavior
25 |     * prevents to run script type=module more than once
26 |     * does not set document.currentScript preventing the parameters passing and locating it in the DOM
27 | 
28 | embed-page enables document.currentScript use for above purposes as in non-module as in module type of scripts.
29 | 
30 | 
31 | 32 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "embed-page", 3 | "description": "Proof of concept for Embeddable Progressive Application - a microapplication container, a WebComponent acting as seamless IFRAME or html include", 4 | "version": "0.0.21", 5 | "browser": "embed-page.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/EPA-WG/embed-page.git" 9 | }, 10 | "author": { 11 | "name": "Sasha Firsov", 12 | "email": "suns@simulationworks.com", 13 | "url": "https://blog.firsov.net/" 14 | }, 15 | "license": "Apache-2.0", 16 | "homepage": "https://github.com/EPA-WG/embed-page", 17 | "bugs": "https://github.com/EPA-WG/embed-page/issues", 18 | "keywords": [ 19 | "JS", 20 | "Polymer", 21 | "IFRAME", 22 | "seamless", 23 | "microapplication", 24 | "EPA-WG", 25 | "Web Component", 26 | "WebComponent" 27 | ], 28 | "dependencies": { 29 | "@polymer/polymer": "^3.3.1" 30 | }, 31 | "devDependencies": { 32 | "@polymer/app-layout": "^3.1.0", 33 | "@polymer/app-route": "^3.0.2", 34 | "@polymer/iron-ajax": "^3.0.1", 35 | "@polymer/iron-collapse": "^3.0.1", 36 | "@polymer/iron-demo-helpers": "^3.1.0", 37 | "@polymer/iron-icons": "^3.0.1", 38 | "@polymer/iron-iconset": "^3.0.1", 39 | "@polymer/iron-image": "^3.0.2", 40 | "@polymer/iron-pages": "^3.0.1", 41 | "@polymer/iron-scroll-threshold": "^3.0.1", 42 | "@polymer/iron-test-helpers": "^3.0.1", 43 | "@polymer/iron-validator-behavior": "^3.0.1", 44 | "@polymer/marked-element": "^3.0.1", 45 | "@polymer/paper-badge": "^3.1.0", 46 | "@polymer/paper-button": "^3.0.1", 47 | "@polymer/paper-card": "^3.0.1", 48 | "@polymer/paper-checkbox": "^3.1.0", 49 | "@polymer/paper-fab": "^3.0.1", 50 | "@polymer/paper-icon-button": "^3.0.2", 51 | "@polymer/paper-input": "^3.0.2", 52 | "@polymer/paper-item": "^3.0.1", 53 | "@polymer/paper-listbox": "^3.0.1", 54 | "@polymer/paper-menu-button": "^3.0.1", 55 | "@polymer/paper-progress": "^3.0.1", 56 | "@polymer/paper-spinner": "^3.0.2", 57 | "@polymer/paper-tabs": "^3.1.0", 58 | "@polymer/paper-toggle-button": "^3.0.1", 59 | "@vaadin/vaadin-combo-box": "^5.0.9", 60 | "@webcomponents/webcomponentsjs": "^2.4.0", 61 | "codemirror": "^5.49.2", 62 | "growl": "^1.10.5", 63 | "lodash": "^4.17.15", 64 | "marked": "^4.0.10", 65 | "mocha": "^3.5.3", 66 | "mochawesome": "^4.1.0", 67 | "polymer-cli": "^1.9.11", 68 | "wct-browser-legacy": "^1.0.2" 69 | }, 70 | "directories": { 71 | "test": "test", 72 | "demo": "demo" 73 | }, 74 | "scripts": { 75 | "start": "polymer serve", 76 | "build": "polymer build", 77 | "lint": "polymer lint", 78 | "test": "polymer test", 79 | "test:integration": "polymer build # test that psk builds without error with the CLI" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /demo/postMessage.html.test.js: -------------------------------------------------------------------------------- 1 | suite('embed-page postMessage test ', () => 2 | { 3 | let e0, e1, $e0, $e1, $$ = css => document.querySelector(css); 4 | 5 | const fr = document.querySelector('iframe') 6 | , FR_URL = "postMessage-app.html?from=iframe" 7 | , E0_URL = "postMessage-app.html?from=epa0" 8 | , E1_URL = "postMessage-app.html?from=epa1" ; 9 | 10 | let AllReady; 11 | 12 | setup( ()=> AllReady || (AllReady = wait4all().then( args => 13 | { [ e0, e1 ] = args; 14 | [ $e0, $e1 ] = args.map( epa => ( css => epa.shadowRoot.querySelector(css) ) ); 15 | })) 16 | ); 17 | 18 | test('1. initial src set', function() 19 | { 20 | assert.equal( fr.getAttribute('src'), FR_URL ); 21 | assert.equal( e0.src, E0_URL ); assert.equal( e0.getAttribute('src'), E0_URL ); 22 | assert.equal( e1.src, E1_URL ); assert.equal( e1.getAttribute('src'), E1_URL ); 23 | }); 24 | 25 | test('2. from IFRAME', function() 26 | { 27 | assert.equal( 1, $$('table').innerText.match( /from=iframe/g ).length ); 28 | }); 29 | test('3. from epa0 & epa1', function() 30 | { 31 | assert.equal( 2, $$('table').innerText.match( /from=epa0/g ).length );// both(data & origin) include URL 32 | assert.equal( 2, $$('table').innerText.match( /from=epa1/g ).length ); 33 | 34 | SimClick( $e0('button') ); 35 | assert.equal( 4, $$('table').innerText.match( /from=epa0/g ).length ); 36 | assert.equal( 2, $$('table').innerText.match( /from=epa1/g ).length ); 37 | 38 | SimClick( $e1('button') ); 39 | assert.equal( 4, $$('table').innerText.match( /from=epa0/g ).length ); 40 | assert.equal( 4, $$('table').innerText.match( /from=epa1/g ).length ); 41 | }); 42 | test('4. to epa0 & epa1', function() 43 | { 44 | assert.isNull( $e0('table').innerText.match( /from=demo/g ) ); 45 | assert.isNull( $e1('table').innerText.match( /from=demo/g ) ); 46 | 47 | SimClick( $$('.to-epa0') ); 48 | assert.equal( 1, $e0('table').innerText.match( /from=demo/g ).length ); 49 | assert.equal( 1, $e0('table').innerText.match( /from=demo/g ).length ); 50 | assert.isNull( $e1('table').innerText.match( /from=demo/g ) ); 51 | 52 | SimClick( $$('.to-epa1') ); 53 | assert.equal( 1, $e0('table').innerText.match( /from=demo/g ).length ); 54 | assert.equal( 1, $e1('table').innerText.match( /from=demo/g ).length ); 55 | }); 56 | 57 | 58 | 59 | function 60 | SimClick( el ){ el.dispatchEvent( new MouseEvent( "click" )); } 61 | 62 | function 63 | absUrl( rel ) 64 | { const A = document.createElement('a'); 65 | A.setAttribute( 'href', rel ); 66 | return A.href; 67 | } 68 | function 69 | wait4all() 70 | { 71 | return Promise.all( ["e0","e1"].map( wait4load ) ); 72 | } 73 | function 74 | wait4load( id ) 75 | { 76 | const E = document.getElementById( id ); 77 | assert.notEqual( E, null ); 78 | return E.promiseNext; 79 | } 80 | }); 81 | -------------------------------------------------------------------------------- /demo/polymer-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 46 | 47 | 66 | -------------------------------------------------------------------------------- /demo/storage-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 46 | 47 | 66 | -------------------------------------------------------------------------------- /demo/poc-dynamic-binding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | embed-page POC dynamic binding 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

embed-page POC dynamic binding

21 | 22 | 23 | 24 | 71 | 72 | -------------------------------------------------------------------------------- /demo/current-script.html.test.js: -------------------------------------------------------------------------------- 1 | suite('embed-page postMessage test ', () => 2 | { 3 | let e1, e2, e3, e4, e5, e6, e7, $e1, $e2, $e3, $e4, $e5, $e6, $e7 //, $$e0, $$e1 4 | , $p = css => document.querySelector(css) 5 | , $$p = css => document.querySelectorAll(css); 6 | let AllReady; 7 | 8 | setup( ()=> AllReady || (AllReady = wait4all().then( args => 9 | { [ e1, e2, e3, e4, e5, e6, e7 ] = args; 10 | [ $e1, $e2, $e3, $e4, $e5, $e6, $e7 ] = args.map( epa => ( css => epa.shadowRoot.querySelector (css) ) ); 11 | // [ $$e0, $$e1 ] = args.map( epa => ( css => epa.shadowRoot.querySelectorAll(css) ) ); 12 | })) 13 | ); 14 | 15 | test('Initial scripts set', function() 16 | { 17 | assert.equal( 14, $$p('script[title]').length ); 18 | let titles = [...document.querySelectorAll('script[title]')].map( x=>x.title).sort().join(''); 19 | assert.equal( titles, "ABCDFKLMNUWXYZ" );// scripts in page scope or withing embed-page scope="none" 20 | }); 21 | test('0. Browser standard behavior. Running 4 scripts on page level.', function() 22 | { 23 | assert.equal( "WX", $p('.current-script-related').value ); 24 | assert( $p('.document-selected').value.startsWith("WX?U") ); 25 | }); 26 | test('1. embed-page with inline scripts, 1st instance.', function() 27 | { 28 | assert.equal( "0123", $e1('.current-script-related').value ); 29 | assert.equal( "0123", $e1('.document-selected' ).value ); 30 | }); 31 | test('2. embed-page with inline scripts, 2nd instance', function() 32 | { 33 | assert.equal( "4567", $e2('.current-script-related').value ); 34 | assert.equal( "4567", $e2('.document-selected' ).value ); 35 | }); 36 | test('3. embed-page with scripts in current-script-microapp.html', function() 37 | { 38 | assert.equal( "ABCD", $e3('.current-script-related').value ); 39 | assert.equal( "ABCD", $e3('.document-selected' ).value ); 40 | }); 41 | test('4. unscoped embed-page with inline scripts', function() 42 | { 43 | assert.equal( "KLMN", $p('#e4 .current-script-related').value ); 44 | }); 45 | test('5. unscoped embed-page with scripts in current-script-microapp.html', function() 46 | { 47 | assert.equal( "ABCD", $p('#e5 .current-script-related').value ); 48 | assert.equal( "", $p('#e5 .document-selected' ).value ); 49 | }); 50 | test('6. embed-page with inline script type=module', function() 51 | { 52 | assert.equal( "E", $e6('.current-script-related').value ); 53 | assert.equal( "E", $e6('.document-selected' ).value ); 54 | }); 55 | test('7. unscoped embed-page with inline script type=module', function() 56 | { 57 | assert.equal( "F", $p('#e7 .current-script-related').value ); 58 | }); 59 | 60 | function 61 | SimClick( el ){ el.dispatchEvent( new MouseEvent( "click" )); } 62 | 63 | function 64 | absUrl( rel ) 65 | { const A = document.createElement('a'); 66 | A.setAttribute( 'href', rel ); 67 | return A.href; 68 | } 69 | function 70 | wait4all() 71 | { 72 | return Promise.all( ["e1","e2","e3","e4","e5","e6","e7"].map( wait4load ) ); 73 | } 74 | function 75 | wait4load( id ) 76 | { 77 | const E = document.getElementById( id ); 78 | assert.notEqual( E, null ); 79 | return E.promise; 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /demo/window-scope.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | embed-page demo - window scope 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 |

embed-page demo - window scope

21 | embed-page populates window object members as "global" to microapplication variable. 22 |

NOTE: the scope variables are populated in beginning of <script> section. Hence the window object members are not immediately available after assignment.

23 | 24 | 25 |

1. first SCRIPT sets window.abc to "ABC", second SCRIPT reads abc as global variable.

26 | 41 |
42 | 43 | 44 |

2. first SCRIPT sets window.abc to "XYZ", second SCRIPT reads abc as global variable.

45 | 59 |
60 |

Visual demo steps

61 |
62 | CSS
63 |     1. The H3 header above should be intact(default) in color and text size
64 |         AND h3 within embed-page use chocolate text color.
65 |     2. The H3 from embedded content should be in monospace font.
66 | JS  3. Click on 'make blue' the embedded H3 should be blue colored.
67 |     4. After 'innerHTML' click the H3 from embedded content should become green.
68 | 
69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /demo/html-include.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HTML include - embed-page demo 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 |

HTML include - embed-page demo

21 | 22 |

src attribute along with scope="none" 23 | makes HTML include similar to server side. Dependent JS and CSS resources will be embedded 24 | into scope of host page. 25 |

26 |

HTML include pattern ( also known as seamless iframe ) is an alternative to the use of WebComponent. 27 | Good as for beginners with limited to HTML skill set as for pros wanted to keep the KISS principle in web app. 28 |

29 |

Microapplication vs. include

30 |

31 | With scope="none" the content of embed-page has no insulation from home page which could cause the 32 | conflict on JS and CSS levels. It is good to use include pattern when the sources of 33 | embedded HTML sections belong to same application with common architecture and release schedule. 34 |
35 | The good samples of such use will be a navigation menu or common CSS theme components. 36 | 37 | 38 |

39 |

Limitations

40 |
    41 |
  1. Performance For single use of external content on the page the include pattern 42 | is more efficient than WebComponent making the load lighter. With multiple inclusions 43 | of same content, the WebComponent does it more efficiently by loading and parsing content only once. 44 |
  2. 45 |
  3. External content ( from independent apps ) better to be served as microapplication 46 | to prevent the interference with host app and other apps on same page. 47 |
  4. 48 |
49 | 50 |

embed-page demo uses menu from demo-menu.html 51 | which holds as menu content as common styling for demo pages as inner shadow for embed-page

52 | 55 | 56 |

Visual demo steps

57 |
58 | The menu should be shown on page right top side and in demo snipplet above.
59 | CSS
60 |     1. The menu should have gray inner shadow.
61 |     2. The page menu should be located on the right side.
62 | JS  3. current page( HTML include ) on menu should be in bold.
63 |     4. Links on menu should open the page in same browser page.
64 | 
65 |

More

66 |
    67 |
  1. Dynamic url and content binding for examples of URL change.
  2. 68 |
  3. Demo home for other scope options.
  4. 69 |
70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | demo/appsList.html unoptimized test time 1100 ms 2 | * load optimisation: all vars marshalling is not needed, it is sufficient to marshall only vars from particular script 3 | * use currentScript.EPA_vars in sync code 4 | * load optimisation: container window sanitizing is not needed if insulation done properly. Make it optional for hacky or suspicious code. Paranoid security mode. 5 | * load optimisation: simultaneous injection of scripts delays whole page until all scripts are executed 6 | 7 | * serviceWorker support 8 | * URL mapping unit test 9 | * async import 10 | 11 | # Release MVP 12 | * release bundle build 13 | * CDN deployment 14 | * JSFiddle/Plunkr publishing, https://www.sitepoint.com/7-code-playgrounds 15 | 16 | * fix SCRIPT type!=module : keep original,do not create wrap, unit test 17 | * unit test 4 failed to load JS(incorrect syntax & r/t exception) 18 | * URL mapping and relative URLs resolving 19 | 20 | * use view-source: http schema ( instead of page execution) for fetching targetFrame and onTargetLoad(), only in browsers which support view-source:(Chrome,FF, does not work in Edge) 21 | * console methods test 22 | * window.customElements API 23 | * move demo to CDN, wc.org does not serve URL params 24 | * document.head implementation (as script injection node in microapp) 25 | * disable 'serviceWorker' in navigator until implemented 26 | * 12 dwarfs demo 27 | * script nomodule support 28 | * support integrity attribute 29 | * target attribute in content ( A & FORM ) 30 | * epa.window.parent.location.href 31 | * cookies, scope impl. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#A_complete_traps_list_example 32 | * window.self 33 | * navigator.serviceWorker.register 34 | * insulation layers - define dynamic change logic and restrictions. 35 | * scope 36 | * docs for insulation layers 37 | * window.* insulation 38 | * history 39 | * Web Components 40 | * wrap window.customElements to use CustomElementRegistry for Web Components scope insulation 41 | * sample with same component tag but different implementation 42 | * CustomElementRegistry use for loading WC via **link rel="import"** 43 | * separate sample on each aspect 44 | * clipboard protection & insulation 45 | * microapplications 46 | * intro page, menu & view-source samples, reference to registry 47 | * view-source app pages: view & form ( source url, theme ) 48 | * [Extend HTMLElement](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) instead of Polymer.Element 49 | * use embed-page.js as entry point 50 | * bower & npm publish 51 | * instructions to use 52 | * form post 53 | * event handlers re-evaluation for embedded content 54 | 55 | * compatibility with Polymer, Ionic, Stencil, React, Angular,?... 56 | * series of examples in [Plunker](http://plnkr.co/), add examples into ApiFusion, 57 | back links from Plunker to AF as "other examples" link. 58 | 59 | Release 1 60 | 61 | * remove Polymer dependencies 62 | * remove the use of cancelled XHRs. 63 | 64 | WishList 65 | * browser-in-browser show case 66 | * onerror event 67 | * "noscript" attribute or scope value to serve content with JS completely disabled 68 | * include embed-script-legacy.js in package as embed-page.js could be served only via type=module, still eill be handy 69 | to serve via usual JS script tag. 70 | * embed-page as es6 module ( now fails in strict mode due to 'with' operator ) 71 | , use static import instead of dynamic 72 | * evaluate the use of DOMImplementation.createHTMLDocument|DOMImplementation.createDocument|new Document() 73 | * populate title attribute into document.head.title (via doc constructor param) & sync head.title to @title from 74 | external doc. 75 | * embed-page.promise getter to avoid registering/release "load"/"error" event handlers. 76 | 77 | ## Test cases coverage 78 | * error event 79 | * cross-domain test: load, JS insulation, scope 80 | * epa.document.origin === window.top.document.origin 81 | 82 | ## APIs 83 | * BroadcastChannel 84 | * documentElement, firstChild, firstElementChild, lastChild, lastElementChild 85 | * forms, images, anchors, links, ... 86 | * window.focus(), window.blur(), window.closed flag 87 | * window.print() 88 | * network control and management layer. Could be implemented by service workers or wrapping ajax APIs. 89 | -------------------------------------------------------------------------------- /demo/storage.html.test.js: -------------------------------------------------------------------------------- 1 | suite('embed-page basics IFRAME test ', () => 2 | { 3 | let e0, a0, a1, b0, b1, $e0, $a0, $a1,$b0, $b1, $$ = css => document.querySelector(css); 4 | 5 | const FR_URL = "page-storage.html?src=iframe" 6 | , E0_URL = "page-storage.html?name=sample" 7 | , A0_URL = "page-storage.html?name=a&instance=0" 8 | , A1_URL = "page-storage.html?name=a&instance=1" 9 | , B0_URL = "page-storage.html?name=b&instance=0" 10 | , B1_URL = "page-storage.html?name=b&instance=1" 11 | , DEMO_URL = absUrl('../demo/'); 12 | 13 | let AllReady; 14 | 15 | setup( ()=> AllReady || (AllReady = wait4all().then( args => 16 | { [ e0, a0, a1, b0, b1 ] = args; 17 | [ $e0, $a0, $a1, $b0, $b1 ] = args.map( epa => ( css => epa.shadowRoot.querySelector(css) ) ); 18 | })) 19 | ); 20 | 21 | test('1. initial src set', function() 22 | { 23 | assert.equal( e0.src, E0_URL ); assert.equal( e0.getAttribute('src'), E0_URL ); 24 | assert.equal( a0.src, A0_URL ); assert.equal( a0.getAttribute('src'), A0_URL ); 25 | assert.equal( a1.src, A1_URL ); assert.equal( a1.getAttribute('src'), A1_URL ); 26 | assert.equal( b0.src, B0_URL ); assert.equal( b0.getAttribute('src'), B0_URL ); 27 | assert.equal( b1.src, B1_URL ); assert.equal( b1.getAttribute('src'), B1_URL ); 28 | }); 29 | 30 | test('2. localStorage set&get insulated', function() 31 | { 32 | SimClick( $e0('.onGetLocal' ) ); assert.equal( $e0('.value').value, "localStorage " + E0_URL); 33 | SimClick( $e0('.onGetSession') ); assert.equal( $e0('.value').value, "sessionStorage " + E0_URL); 34 | assert.notEqual( localStorage.getItem('a'), "localStorage " + E0_URL ); // not changed in container window 35 | }); 36 | 37 | test('3. localStorage set&get&event in named scope', function() 38 | { const V = "localStorage from " + A0_URL; 39 | 40 | assert.notInclude( $a0('table').innerText, V ); 41 | assert.notInclude( $a1('table').innerText, V ); 42 | $a0('.value').value = V; SimClick( $a0('.onSetLocal' ) ); 43 | assert.notInclude( $a0('table').innerText, V ); // does not fire event on itself 44 | assert. include( $a1('table').innerText, V ); // but fires in own named scope 45 | assert.notInclude( $e0('table').innerText, V ); // not fires in other scopes 46 | 47 | SimClick( $a0('.onGetLocal' ) ); assert.equal( $a0('.value').value, V ); // preserved in a0 48 | SimClick( $a1('.onGetLocal' ) ); assert.equal( $a1('.value').value, V ); // and also populated in a1 49 | assert.notEqual( $e0('.value').value, V ); // not changed in e0 50 | assert.notEqual( localStorage.getItem('a'), V ); // not changed in container window 51 | }); 52 | 53 | test('4. sessionStorage set&get&event in named scope', function() 54 | { const V = "sessionStorage from " + A0_URL; 55 | 56 | assert.notInclude( $a0('table').innerText, V ); 57 | assert.notInclude( $a1('table').innerText, V ); 58 | $a0('.value').value = V; SimClick( $a0('.onSetSession' ) ); 59 | assert.notInclude( $a0('table').innerText, V ); // does not fire event on itself 60 | assert. include( $a1('table').innerText, V ); // but fires in own named scope 61 | assert.notInclude( $e0('table').innerText, V ); // not fires in other scopes 62 | 63 | SimClick( $a0('.onGetSession' ) ); assert.equal( $a0('.value').value, V ); // preserved in a0 64 | SimClick( $a1('.onGetSession' ) ); assert.equal( $a1('.value').value, V ); // and also populated in a1 65 | assert.notEqual( $e0('.value').value, V ); // not changed in e0 66 | assert.notEqual( sessionStorage.getItem('a'), V ); // not changed in container window 67 | }); 68 | 69 | 70 | function 71 | SimClick( el ){ el.dispatchEvent( new MouseEvent( "click" )); } 72 | 73 | function 74 | absUrl( rel ) 75 | { const A = document.createElement('a'); 76 | A.setAttribute( 'href', rel ); 77 | return A.href; 78 | } 79 | function 80 | wait4all() 81 | { 82 | return Promise.all( ["e0","a0","a1","b0","b1"].map( wait4load ) ); 83 | } 84 | function 85 | wait4load( id ) 86 | { 87 | const E = document.getElementById( id ); 88 | assert.notEqual( E, null ); 89 | return E.promiseNext; 90 | } 91 | }); 92 | -------------------------------------------------------------------------------- /demo/single-instance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | embed-page demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 |
35 |

Basic embed-page demo, Embeddable Progressive Applications Working Group

36 |

The <embed-page/> web component is acting as IFRAME. 37 | Its content is insulated on JS, DOM, CSS and browsing context level( A links and FORM get/post ).

38 |

Unlike IFRAME it is embedded inline into parent page DOM and automatically resizing parent node.

39 | 45 | 46 | 49 | 50 | 51 | 52 |
53 |
54 | Demo(this) page 55 |
56 | 57 |

Visual demo

58 |
 59 | CSS
 60 |     1. The header above should be intact(default) in color and text size.
 61 |     2. Colors in demo components and IFRAMEs should match the page name: purple, violet.
 62 | JS
 63 |     1. Checkboxes on page should not be affected by embed-page content.
 64 |     2. Operating the page content here should not affect the component` intestines.
 65 | Links
 66 |     1. Click on link will replace the component content with page from href attribute.
 67 | Form
 68 |     1. GET and POST will replace content according to FORM action attribute.
 69 | location & window.location & document.location
 70 |     1. page in component populates text box with value matching SRC attribute of component.
 71 |     2. clear the text box, click on GET. The full URL matching SRC attribute
 72 |        should be placed in text box
 73 |     3. change text box to page-purple.html, click on SET. The content of component
 74 |        should load the page
 75 |     4. repeat step 1-3 with 'href-get', 'href-set', 'location get', 'location set'
 76 |     5. add URL properties like hash, port#, query parameters, user, password.
 77 |        'other properties' should place matching key-value in text box.
 78 |     6. change URL in text box, press 'assign()' or 'replace(), matching content
 79 |        should be fetched.
 80 |     7. 'reload()' should re-fetch and re-render content. See it in network and UI.
 81 |     8. 'location-win-doc' should give true for identical 'location', 'window.location'
 82 |        , and 'document.location'
 83 |     9. 'this===window' should give true
 84 | Cookies
 85 |     1. set cookies a=b, set cookies c=d
 86 |     2. get cookie should have both key/values presented
 87 |     3. refresh page, get cookies should have both key/values presented
 88 | 
 89 | 
 90 | 
91 |
92 |
93 | page.js 94 | 95 | When used from embed-page, the document is wrapped to reflect component instance scope.
96 | The click will trigger just own instance checkbox. 97 |
98 |
99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /demo/postMessage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | postMessage API - embed-page demo 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 |
31 | 32 |

postMessage() API for <embed-page/> demo, 33 | Embeddable Progressive Applications Working Group

34 | 35 |

<embed-page/> mimicks the window object in context of postMessage() API. 36 | See postMessage for details. 37 |

38 | 39 |

epa 0

40 | 43 |
44 |

epa 1

45 | 48 |
49 |
50 | postMessage events log 51 | 52 | 53 | 54 |
message event contentorigin url
55 |
56 |
57 | Manual test 58 |
 59 | postMessage-app.html listens to "message" event and appends data to log matrix.
 60 | It is served within IFRAME and two embed-page instances.
 61 | 
 62 | The common load sequence is
 63 | 1. postMessage-app.js listens for "message" event.
 64 | 2. postMessage-app.js will send to opener message with own URL.
 65 | 3. "message" event handler appends data to log table
 66 | 
 67 | IFRAME
 68 |     1-3. as in common part.
 69 |     4. ?from=iframe added to page log matrix
 70 |     5.  to post to IFRAME, observe appearance of ?from=demo inside of IFRAME log matrix
 71 | 
 72 | embed-page - main
 73 |     1-3. as in common part.
 74 |     4. observe ?from=epa0 in page log matrix
 75 |     5.  to post to epa, observe appearance of ?from=demo within epa log matrix
 76 | 
 77 | embed-page - second
 78 |     1-4. as in embed-page - main part.
 79 |     5.  to post to epa, observe appearance of ?from=demo within epa log matrix
 80 | 
 81 | 
82 | 83 |
84 | postMessage-app.js 85 | 86 | When used from embed-page, behaves same way as from IFRAME.
87 |
88 |
89 | 90 |
91 | Demo IFRAME postMessage-app.html 92 | 93 |
94 | 95 |
96 | 97 | 98 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # \ 2 | Proof of concept for 3 | [Embeddable Progressive Application](https://github.com/EPA-WG/EPA-concept) 4 | - a microapplication container, a WebComponent acting as seamless IFRAME and html include 5 | 6 | [![git](https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg) GitHub](https://github.com/EPA-WG/embed-page) 7 | | [demo](https://cdn.xml4jquery.com/ajax/libs/embed-page/0.0.20/build/esm-unbundled/demo/index.html) 8 | [![NPM version][npm-image]][npm-url] 9 | [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/embed-page) 10 | 11 | ## Where to use? 12 | <embed-page/> covers 2 extreme cases. 13 | 14 | 1. Super-simple development with library of pre-made microapplications and plain html codebase. 15 | It assumes no web component knowledge and development. 16 | 2. Super-complex apps where on same page need to mix UX made with different frameworks and their incompatible otherwise revisions.
17 | The JS Context insulation of embed-page provides "evolutionary architecture" support to web page. 18 | 19 | ## Security 20 | * General browser and application [security improvements overview](security.md). 21 | 22 | Briefly, increases security by jailing 3rd party content and JS, a secure alternative to directly including of 3rd party 23 | JS into page. 24 | 25 | The scope insulation for DOM and CSS is done by WebComponet shadow dom, API for JS 26 | are insulated by closure for global objects with wrappers limiting the dom access root 27 | to component content. Similar approach is applied for url, storage, cookies, etc. 28 | 29 | ## Use 30 | 1. Add to project via npm, bower, or simply placing `embed-page.js` into project tree 31 | 2. Import into page/module either by ES6 import, simple SCRIPT tag, webcomponent `link rel="import"`, or AMD require 32 | 3. Develop your reusable widgets as insulated HTML and include into page by `````` or 33 | 34 | Add some useful 3rd party [microapplication](https://github.com/EPA-WG/EPA-concept/blob/master/microapplication.md) 35 | into your page same way. 36 | 37 | The content could be set either by **src** attribute or by Polymer {{data}} binding of content; 38 | including the insulated content in TEMPLATE; or binding content via **html** attribute. 39 | ```html 40 | 41 | 42 | 43 | 44 | 45 | 46 |