├── .gitattributes ├── .gitignore ├── README.md └── src ├── example.html ├── inject.js └── inject.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inject.js 2 | 3 | ### What is it? 4 | A javascript file that will pull html from other webpages, and inject that html into your document, similar to an iframe. 5 | 6 | ### When do I use this? 7 | If you want control over the html being imported into your webpage, iframes don't play nicely with the parent's css and javascript. 8 | For example you can use your own javascript or css to target the injected html which is otherwise hard if you don't have access to the requested html's source. 9 | 10 | ### Remarks 11 | Under the hood inject uses [YQL](https://developer.yahoo.com/yql/) to get the requested page's html, any style or script tags from the source are stripped out before being injected into your page. 12 | This means it's the caller's responsibility to add css etc for the page's layout (if desired). 13 | Works with any [CORS enabled browser](http://caniuse.com/#feat=cors). 14 | The same-origin policy is circumvented by using Yahoo! as a proxy, so you can do more then AJAX can, and by extension [jQuery's .load()](https://api.jquery.com/load/) which are limited by having extra security. 15 | 16 | ### Show me the code! 17 | ```html 18 |
19 | ``` 20 | [Example](https://rawgit.com/Matthew-Dove/Inject/master/src/example.html) 21 | 22 | When the attribute **data-inject-src** is put on any element, inject will download the url specified in the value, and will dump the content between the opening, and closing body tags. 23 | The inline height style can be omitted, but it's recommended so the layout of the page doesn't change once the external html is injected. 24 | If the element doesn't take the full screen width, then adding a width to the element is also recommended. Of course the height and width could be defined in a css class. 25 | 26 | ### Warning 27 | In general you should trust the source that your loading from. While script, and styles tags are removed, inline script (such as onlick events) and styles remain. 28 | The scripts tags aren't removed to prevent [XSS](https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29), rather they're removed because the framework doesn't support them. 29 | 30 | ### Alternative frameworks 31 | 32 | * [html-imports-content](https://github.com/AndersDJohnson/html-imports-content) - Enables simple client-side page composition. 33 | * [wm-html-include](https://github.com/al-scvorets/wm-html-include.js) - Include external HTML page(s) into a comprising HTML page. 34 | 35 | This software is released under the [MIT License](http://opensource.org/licenses/MIT). 36 | -------------------------------------------------------------------------------- /src/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Inject 7 | 8 | 9 |

Below this heading the world's first website will be injected

10 | 11 |
12 | 13 |

Above this heading the world's first website will be injected

14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/inject.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | /* Build the url for each injection element to get the source's html. */ 3 | var createApiUrl = (function () { 4 | var protocol = window.location.protocol === 'https:' ? 'https:' : 'http:'; 5 | var baseUrl = '//query.yahooapis.com/v1/public/yql?q='; 6 | var yql = encodeURIComponent('select * from html where url = '); 7 | 8 | return function (queryUrl) { 9 | /* The single quote isn't encoded correctly, so the safe encoded value is hard coded. */ 10 | return protocol + baseUrl + yql + '%27' + encodeURIComponent(queryUrl) + '%27'; 11 | }; 12 | })(); 13 | 14 | /* Get the browser's XML parser. */ 15 | var createXmlParser = (function () { 16 | if (typeof window.DOMParser !== 'undefined') { 17 | return function (xml) { 18 | return (new DOMParser()).parseFromString(xml, 'text/xml'); 19 | }; 20 | } else if (typeof ActiveXObject !== 'undefined' && new ActiveXObject('Microsoft.XMLDOM')) { 21 | return function (xml) { 22 | var xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); 23 | xmlDoc.async = 'false'; 24 | xmlDoc.loadXML(xml); 25 | return xmlDoc; 26 | }; 27 | } else { 28 | console.log('inject - no xml parser found.'); 29 | return function (xml) { 30 | return null; 31 | }; 32 | } 33 | })(); 34 | 35 | var createXhr = (function () { 36 | var xmlRequest = null; 37 | 38 | function isCorsEnabled() { 39 | var xhr = new XMLHttpRequest(); 40 | return 'withCredentials' in xhr; 41 | } 42 | 43 | if (typeof XMLHttpRequest !== 'undefined' && isCorsEnabled()) { 44 | xmlRequest = function () { 45 | return new XMLHttpRequest(); 46 | }; 47 | } 48 | else if (typeof XDomainRequest !== 'undefined') { 49 | xmlRequest = function () { 50 | return new XDomainRequest(); 51 | }; 52 | } 53 | else { 54 | console.log('inject - cors isn\'t supported.'); 55 | } 56 | 57 | return xmlRequest; 58 | })(); 59 | 60 | /* Use the browser's xml request object to get the source's html. */ 61 | var getHtml = function (url, callback) { 62 | var xhr = createXhr(); 63 | if (xhr !== null) { 64 | xhr.open('GET', url, true); 65 | xhr.onerror = function () { console.log('inject - error making a request for a source\'s HTML.'); }; 66 | xhr.onload = function () { callback(xhr.responseText); }; 67 | xhr.send(null); 68 | } 69 | }; 70 | 71 | /* The browser's way of selecting elements by their attributes. */ 72 | var elementSelector = (function () { 73 | if (typeof document.querySelectorAll !== 'undefined') { 74 | return function (query) { 75 | return document.querySelectorAll('[' + query + ']'); 76 | }; 77 | } else { 78 | return function (query) { 79 | var matchingElements = []; 80 | var allElements = document.getElementsByTagName('*'); 81 | for (var i = 0, n = allElements.length; i < n; i++) { 82 | if (allElements[i].getAttribute(query) !== null) { 83 | matchingElements.push(allElements[i]); 84 | } 85 | } 86 | return matchingElements; 87 | }; 88 | } 89 | }()); 90 | 91 | var removeNodes = function (xmlDocument, trash, name) { 92 | var garbage = xmlDocument.getElementsByTagName(name); 93 | 94 | for (var i = 0, n = garbage.length; i < n; i++) { 95 | trash.push(garbage[i]); 96 | } 97 | 98 | return trash; 99 | }; 100 | 101 | /* Remove unwanted nodes, and put the rest as HTML into the element that requested the HTML injection. */ 102 | var injectResponse = function (response, injectee) { 103 | var parser = createXmlParser(response); 104 | if (parser !== null) { 105 | var bodyMatch = parser.getElementsByTagName('body'); 106 | if (bodyMatch.length === 1) { 107 | var body = bodyMatch[0]; 108 | var trash = []; 109 | 110 | trash = removeNodes(parser, trash, 'script'); 111 | trash = removeNodes(parser, trash, 'style'); 112 | 113 | /* Remove any nodes we won't want injected. */ 114 | for (var i = 0, n = trash.length; i < n; i++) { 115 | trash[i].parentNode.removeChild(trash[i]); 116 | } 117 | 118 | /* Inject the html. */ 119 | injectee.innerHTML = body.innerHTML || body.xml || response; 120 | } else { 121 | console.log('inject - no body tag found.'); 122 | } 123 | } 124 | }; 125 | 126 | /* The attribue to look for when finding elements that require injection. */ 127 | var injectSrcAttr = 'data-inject-src'; 128 | 129 | /* Get the source's html, and inject it into the element that requested it. */ 130 | var injectHtml = function (injectee) { 131 | var queryUrl = injectee.getAttribute(injectSrcAttr); 132 | getHtml(createApiUrl(queryUrl), function (response) { injectResponse(response, injectee); }); 133 | }; 134 | 135 | setTimeout(function () { 136 | /* Get all elements marked with the inject attribute, and inject them with the requested source. */ 137 | var injectees = elementSelector(injectSrcAttr); 138 | for (var i = 0, n = injectees.length; i < n; i++) { 139 | injectHtml(injectees[i]); 140 | } 141 | }, 0); 142 | })(); -------------------------------------------------------------------------------- /src/inject.min.js: -------------------------------------------------------------------------------- 1 | !function(){var e=function(){var e="https:"===window.location.protocol?"https:":"http:",n="//query.yahooapis.com/v1/public/yql?q=",t=encodeURIComponent("select * from html where url = ");return function(o){return e+n+t+"%27"+encodeURIComponent(o)+"%27"}}(),n=function(){return"undefined"!=typeof window.DOMParser?function(e){return(new DOMParser).parseFromString(e,"text/xml")}:"undefined"!=typeof ActiveXObject&&new ActiveXObject("Microsoft.XMLDOM")?function(e){var n=new ActiveXObject("Microsoft.XMLDOM");return n.async="false",n.loadXML(e),n}:(console.log("inject - no xml parser found."),function(){return null})}(),t=function(){function e(){var e=new XMLHttpRequest;return"withCredentials"in e}var n=null;return"undefined"!=typeof XMLHttpRequest&&e()?n=function(){return new XMLHttpRequest}:"undefined"!=typeof XDomainRequest?n=function(){return new XDomainRequest}:console.log("inject - cors isn't supported."),n}(),o=function(e,n){var o=t();null!==o&&(o.open("GET",e,!0),o.onerror=function(){console.log("inject - error making a request for a source's HTML.")},o.onload=function(){n(o.responseText)},o.send(null))},r=function(){return"undefined"!=typeof document.querySelectorAll?function(e){return document.querySelectorAll("["+e+"]")}:function(e){for(var n=[],t=document.getElementsByTagName("*"),o=0,r=t.length;r>o;o++)null!==t[o].getAttribute(e)&&n.push(t[o]);return n}}(),u=function(e,n,t){for(var o=e.getElementsByTagName(t),r=0,u=o.length;u>r;r++)n.push(o[r]);return n},i=function(e,t){var o=n(e);if(null!==o){var r=o.getElementsByTagName("body");if(1===r.length){var i=r[0],c=[];c=u(o,c,"script"),c=u(o,c,"style");for(var l=0,f=c.length;f>l;l++)c[l].parentNode.removeChild(c[l]);t.innerHTML=i.innerHTML||i.xml||e}else console.log("inject - no body tag found.")}},c="data-inject-src",l=function(n){var t=n.getAttribute(c);o(e(t),function(e){i(e,n)})};setTimeout(function(){for(var e=r(c),n=0,t=e.length;t>n;n++)l(e[n])},0)}(); --------------------------------------------------------------------------------