📜 ⬆️ ⬇️

Asynchronous loading of arbitrary html

In connection with the latest initiatives of Google, taking into account the page load time it is becoming more and more important to load parts of web pages asynchronously after loading the main minimum. Advertising is one of the applicants for deferred loading, but a simple ajax will not help here, because In the general case, in the loaded piece it may occur, for example, document.write, and if the document is already loaded and closed, then this method opens it again, nulling it.

Google does not help much in this regard, because The main method described is the substitution of document.write with its own method, which stupidly adds an argument to the end of the document, but if the call is not at the end and you need to write somewhere in the middle, then a problem arises. On Habré , the Fullajax framework was described , which seems to cope with this, but how exactly - I haven’t yet looked.

I had another idea, maybe a bicycle, but the desire to try was too strong. Namely, load the deferred code into a hidden iframe, and after loading transfer its contents to where it should be. Moreover, in order not to make unnecessary requests to the server, the data URI is used . This approach works in FF and Opera but does not work in IE and Chrome.
')
The problem with IE is that it doesn’t support the data URI at all until version 8, and in version 8 it only allows you to encode images and styles, but not html. In Chrome, the problem is in security, it does not allow reading the contents of the frame with the data URI, considering that it is loaded from another domain. Surely downloading the iframe from the server and not embedding will work in all browsers, but I have not checked it yet.

Of course, DOM chunks cannot be copied between frames. Some browsers have the document.importNode method, but, as Anthony Holdener writes , it does not copy event handlers, so it’s best to always copy manually. In addition, the script with document.write is also copied at the same time, which also clears the page, just like the innerHTML blunt copy, so you have to copy it manually anyway.

But if you cut the scripts - why are they needed, if they have already worked and you can copy the resulting DOM as a result of their work? - then methods and global variables used in event handlers can be lost. As a solution, I copy all the window properties (window) from the iframe, which the main window does not have. Perhaps there will be problems with closures (closure), not tested. When trying to read some of the properties, exceptions occur, so the copy block needs to be wrapped in try / catch.

There are styles, because the loaded html-code may well contain CSS, which must somehow be copied. I honestly did not find how to do this correctly, and used the window.getComputedStyle method (it is not cross-browser, so IE will have to be done differently), which already contains finite element styles after calculating all classes and explicitly specified properties . When copying the DOM hierarchy, I look at them in a hidden frame, where the html-code is loaded, and I explicitly prescribe the created elements. But copying all that is is also not an option, so I had to make a “white list” of properties, and similarly wrap it in try / catch.

As a result, for deferred loading, you need to register at the end of the page:

<iframe style='display:none' onLoad='l("...",this)' src='data:text/html;base64,...'></iframe>

where the first ellipsis is the ID of the element into which the code is to be loaded, the second is the code itself encoded in base64. Naturally, such an iframe can be created dynamically at any time, for example, with ready or load events. The bootloader looks like this:

  1. var allowedStyles = {
  2. color: true
  3. cursor: true
  4. backgroundColor: true
  5. backgroundImage: true
  6. borderTopWidth: true
  7. borderRightWidth: true
  8. borderBottomWidth: true
  9. borderLeftWidth: true
  10. display: true
  11. fontFamily: true
  12. fontSize: true
  13. fontSizeAdjust: true
  14. fontStretch: true
  15. fontStyle: true
  16. fontVariant: true
  17. fontWeight: true
  18. paddingTop: true
  19. paddingRight: true
  20. paddingBottom: true
  21. paddingLeft: true
  22. textAlign: true
  23. textDecoration: true
  24. };
  25. function im (node, rec, w2) {
  26. switch (node.nodeType) {
  27. case document .ELEMENT_NODE:
  28. if (node.nodeName == 'SCRIPT' ) return false ;
  29. if (node.nodeName == 'IFRAME' ) return document .importNode (node, true );
  30. var newNode = document .createElement (node.nodeName);
  31. // doesn’t have any attributes to add?
  32. if (node.attributes && node.attributes.length> 0)
  33. for ( var i = 0, il = node.attributes.length; i <il; i ++) {
  34. var attrName = node.attributes [i] .nodeName;
  35. newNode.setAttribute (attrName, node.getAttribute (attrName));
  36. }
  37. // are we going?
  38. if (rec && node.childNodes && node.childNodes.length> 0)
  39. for ( var i = 0, il = node.childNodes.length; i <il; i ++) {
  40. var newChild = im (node.childNodes [i], rec, w2);
  41. if (newChild) newNode.appendChild (newChild);
  42. }
  43. //
  44. var styles = w2.getComputedStyle (node, null );
  45. for ( var s in styles) try {
  46. if (allowedStyles [s]) newNode.style [s] = styles [s];
  47. } catch (e) {}
  48. return newNode;
  49. case document .TEXT_NODE:
  50. case document .CDATA_SECTION_NODE:
  51. return document .createTextNode (node.nodeValue);
  52. }
  53. };
  54. function l (name, iframe) {
  55. for ( var i in iframe.contentWindow)
  56. try {
  57. if (window [i] === undefined)
  58. window [i] = iframe.contentWindow [i];
  59. } catch (e) {}
  60. var d = document .getElementById (name);
  61. var children = iframe.contentDocument.body.childNodes;
  62. for ( var i = 0, l = children.length; i <l; i ++) {
  63. var clone = im (children [i], true , iframe.contentWindow);
  64. if (clone) d.appendChild (clone);
  65. }
  66. }
* This source code was highlighted with Source Code Highlighter .


The approach is quite working, at the moment, as I already wrote, it works only in FF and Opera, and quite crude, but I wanted to quickly share the idea and read the comments of smart people before finishing further. One of the problems that is not solved is what to do if the loaded code in turn also contains an iframe. For advertising is not uncommon. Now the iframe element is copied with src, but the frame content is loaded again, and it turns out that the frame is loaded twice. Copy its contents through DOM or take innerHTML, encode base64 and write to src = "data: ..." is not an option, since iframe can be loaded from another domain, and the browser will not allow access to its content for security reasons. Therefore, the code containing the iframe is better not to load.

Source: https://habr.com/ru/post/78174/


All Articles