├── .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)}();
--------------------------------------------------------------------------------