Merge pull request #644 from bbsteventill/541-memory-leak-ie11

fixed memory leak while importing html elements in IE
This commit is contained in:
Justin Fagnani
2016-11-21 16:11:00 -08:00
committed by GitHub
9 changed files with 56 additions and 18 deletions

View File

@@ -251,7 +251,10 @@ function takeRecords(node) {
while (node.parentNode) {
node = node.parentNode;
}
var observer = node.__observer;
// The node is a ShadowRoot, an IE will have a memory leak if you put the observer
// directly on the ShadowRoot, so put it on the head so it does not leak
var observer = node.head.__observer;
if (observer) {
handler(node, observer.takeRecords());
takeMutations();
@@ -260,19 +263,29 @@ function takeRecords(node) {
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
// observe a node tree; bail if it's already being observed.
function observe(inRoot) {
if (inRoot.__observer) {
if (inRoot && inRoot.head && inRoot.head.__observer) {
return;
}
// For each ShadowRoot, we create a new MutationObserver, so the root can be
// garbage collected once all references to the `inRoot` node are gone.
// Give the handler access to the root so that an 'in document' check can
// be done.
// originally the observer was on the ShadowRoot (inRoot) (single observer);
// this causes a memory leak within IE. To fix this, we must put a an observer
// on both the head and body nodes on the ShadowRoot
var observer = new MutationObserver(handler.bind(this, inRoot));
observer.observe(inRoot, {childList: true, subtree: true});
inRoot.__observer = observer;
observer.observe(inRoot.head, {childList: true, subtree: true});
observer.observe(inRoot.body, {childList: true, subtree: true});
// this needs to be on head or it will leak in IE
// IE does not like it when you have non-standard attributes on root dom's, so put
// the observer on the head element
// this is used to check if the observer has been attached already (above)
inRoot.head.__observer = observer;
}
// upgrade an entire document and observe it for elements changes.

View File

@@ -40,7 +40,10 @@ Observer.prototype = {
},
observe: function(root) {
this.mo.observe(root, {childList: true, subtree: true});
// IE will leak if you put an observer on a root shadow document
// so observe changes to both the head and body
this.mo.observe(root.head, {childList: true, subtree: true});
this.mo.observe(root.body, {childList: true, subtree: true});
}
};

View File

@@ -74,7 +74,9 @@ var importer = {
// generate an HTMLDocument from data
doc = err ? null : makeDocument(resource, redirectedUrl || url);
if (doc) {
doc.__importLink = elt;
// IE will leak if you put the node directly on the ShadowRoot (doc)
// instead appending to ShadowRoot head for reference
doc.head.__importLink = elt;
// note, we cannot use MO to detect parsed nodes because
// SD polyfill does not report these as mutations.
this.bootDocument(doc);

View File

@@ -161,8 +161,10 @@ var importParser = {
rootImportForElement: function(elt) {
var n = elt;
while (n.ownerDocument.__importLink) {
n = n.ownerDocument.__importLink;
// IE will leak if you put the import link directly on the ownerDocument (shadow root)
// so the link is appended on the head element.
while (n.ownerDocument.head.__importLink) {
n = n.ownerDocument.head.__importLink;
}
return n;
},

View File

@@ -76,6 +76,11 @@
var renderer = scope.getRendererForHost(this);
renderer.invalidate();
// This is needed for IE - if you put elements directly on the root shadow
// IE will leak, create head and body so we can append observers, etc.
newShadowRoot.head = document.createElement('head');
newShadowRoot.body = document.createElement('body');
return newShadowRoot;
},

View File

@@ -46,11 +46,16 @@
test('HTMLImports whenready detail', function(done) {
HTMLImports.whenReady(function(detail) {
chai.assert.equal(detail.allImports.length, 2);
chai.assert.equal(detail.loadedImports.length, 2);
chai.expect(detail.allImports[0].href).to.contain('imports/load-1.html');
chai.expect(detail.allImports[1].href).to.contain('imports/load-2.html');
var allImports = document.querySelectorAll('link[rel="import"]');
chai.assert.equal(detail.allImports.length, allImports.length);
chai.assert.equal(detail.loadedImports.length, allImports.length);
var importsArray = Array.prototype.slice.call(detail.allImports);
chai.expect(importsArray.filter(function(el){ return el.href.indexOf('imports/load-1.html') >= 0;}));
chai.expect(importsArray.filter(function(el){ return el.href.indexOf('imports/load-2.html') >= 0;}));
done()
});

View File

@@ -46,9 +46,10 @@
test('HTMLImports whenready errors', function(done) {
HTMLImports.whenReady(function(detail) {
chai.assert.equal(detail.allImports.length, 2);
var allImports = document.querySelectorAll('link[rel="import"]');
chai.assert.equal(detail.allImports.length, allImports.length);
chai.assert.equal(detail.errorImports.length, 1);
chai.assert.equal(detail.loadedImports.length, 1);
chai.assert.equal(detail.loadedImports.length, allImports.length - 1);
var errorImport = detail.errorImports[0];
chai.expect(errorImport.href).to.contain('imports/load-does-not-exist.html');

View File

@@ -40,7 +40,7 @@
function check() {
clearTimeout(timeout);
chai.assert.equal(document.querySelector('link').import, null, '404\'d link.import is null');
chai.assert.equal(document.querySelector('link[href="imports/404-1.html"]').import, null, '404\'d link.import is null');
chai.assert.equal(errors, 2, '404\'d generate error event');
done();
}

View File

@@ -20,7 +20,9 @@
<x-foo>plain</x-foo>
<button is="x-baz">plain</button>
<script>
var isNativeShadowDomSupported;
test('smoke', function(done) {
isNativeShadowDomSupported = !!unwrap(document.createElement('div')).createShadowRoot;
var p = Object.create(HTMLElement.prototype);
p.createdCallback = function() {
this.textContent = 'custom!';
@@ -57,8 +59,13 @@
done();
}
var ob = new MutationObserver(handler);
ob.observe(root, {childList: true, subtree: true});
root.appendChild(xzot);
if( isNativeShadowDomSupported ) {
ob.observe(root, {childList: true, subtree: true});
root.appendChild(xzot);
} else {
ob.observe(root.body, {childList: true, subtree: true});
root.body.appendChild(xzot);
}
});
</script>
</body>