Repository: webpack-contrib/mini-css-extract-plugin (js)

Issue:
onerror memory leak
<!--
  Issues are so 🔥

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  👉🏽 Need support, advice, or help? Don't open an issue!
  Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

- Operating System: Mac
- Node Version: 12
- NPM Version: 12.18.2
- webpack Version: 4.44.2
- mini-css-extract-plugin Version: 1.2.0
- browser:  chrome 86.0.4240.111

### Expected Behavior

<!-- Remove this section if not reporting a bug or modification request. -->

should unref onerror after onload

### Actual Behavior

<!-- Remove this section if not reporting a bug or modification request. -->

onerror remains in the memory

![截屏2020-10-26 下午7 25 22](https://user-images.githubusercontent.com/2224764/97167022-18f88800-17c1-11eb-84f7-e146fd3ed428.png)


### Code

https://github.com/dgeibi/mini-css-extract-plugin-mem-leak-demo

### How Do We Reproduce?

```
git clone https://github.com/dgeibi/mini-css-extract-plugin-mem-leak-demo
cd mini-css-extract-plugin-mem-leak-demo
npm i
npm run build
# open dist/index.html with chrome
```


Pull request:
fix: onerror mem leak
<!--
  HOLY CRAP a Pull Request. We ❤️ those!

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [x] **bugfix**
- [ ] new **feature**
- [ ] **code refactor**
- [x] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

<!--
  Please explain the motivation or use-case for your change.
  What existing problem does the PR solve?
  If this PR addresses an issue, please link to the issue.
-->

Fixes #639 

### Breaking Changes

<!--
  If this PR introduces a breaking change, please describe the impact and a
  migration path for existing applications.
-->

No

### Additional Info

No

Patch:
diff --git a/src/CssLoadingRuntimeModule.js b/src/CssLoadingRuntimeModule.js
index be0ff24..1063351 100644
--- a/src/CssLoadingRuntimeModule.js
+++ b/src/CssLoadingRuntimeModule.js
@@ -60,17 +60,23 @@ module.exports = class CssLoadingRuntimeModule extends RuntimeModule {
           this.runtimeOptions.linkType
             ? `linkTag.type = ${JSON.stringify(this.runtimeOptions.linkType)};`
             : '',
-          'linkTag.onload = resolve;',
-          'linkTag.onerror = function(event) {',
-          Template.indent([
-            'var request = event && event.target && event.target.href || fullhref;',
-            'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
-            'err.code = "CSS_CHUNK_LOAD_FAILED";',
-            'err.request = request;',
-            'linkTag.parentNode.removeChild(linkTag)',
-            'reject(err);',
-          ]),
-          '};',
+          `var onLinkComplete = ${runtimeTemplate.basicFunction('event', [
+            '// avoid mem leaks.',
+            'linkTag.onerror = linkTag.onload = null;',
+            "if (event.type === 'load') {",
+            Template.indent(['resolve();']),
+            '} else {',
+            Template.indent([
+              'var request = event && event.target && event.target.href || fullhref;',
+              'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
+              'err.code = "CSS_CHUNK_LOAD_FAILED";',
+              'err.request = request;',
+              'linkTag.parentNode.removeChild(linkTag)',
+              'reject(err);',
+            ]),
+            '}',
+          ])}`,
+          'linkTag.onerror = linkTag.onload = onLinkComplete;',
           'linkTag.href = fullhref;',
           crossOriginLoading
             ? Template.asString([
diff --git a/src/index.js b/src/index.js
index b4a975c..b00b060 100644
--- a/src/index.js
+++ b/src/index.js
@@ -405,18 +405,26 @@ class MiniCssExtractPlugin {
                           this.runtimeOptions.linkType
                         )};`
                       : '',
-                    'linkTag.onload = resolve;',
-                    'linkTag.onerror = function(event) {',
+                    'var onLinkComplete = function (event) {',
                     Template.indent([
-                      'var request = event && event.target && event.target.href || fullhref;',
-                      'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
-                      'err.code = "CSS_CHUNK_LOAD_FAILED";',
-                      'err.request = request;',
-                      'delete installedCssChunks[chunkId]',
-                      'linkTag.parentNode.removeChild(linkTag)',
-                      'reject(err);',
+                      '// avoid mem leaks.',
+                      'linkTag.onerror = linkTag.onload = null;',
+                      "if (event.type === 'load') {",
+                      Template.indent(['resolve();']),
+                      '} else {',
+                      Template.indent([
+                        'var request = event && event.target && event.target.href || fullhref;',
+                        'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
+                        'err.code = "CSS_CHUNK_LOAD_FAILED";',
+                        'err.request = request;',
+                        'delete installedCssChunks[chunkId]',
+                        'linkTag.parentNode.removeChild(linkTag)',
+                        'reject(err);',
+                      ]),
+                      '}',
                     ]),
                     '};',
+                    'linkTag.onerror = linkTag.onload = onLinkComplete;',
                     'linkTag.href = fullhref;',
                     crossOriginLoading
                       ? Template.asString([


Test patch:
diff --git a/test/cases/hmr/expected/webpack-5/main.js b/test/cases/hmr/expected/webpack-5/main.js
index fea4f8c..b746eb6 100644
--- a/test/cases/hmr/expected/webpack-5/main.js
+++ b/test/cases/hmr/expected/webpack-5/main.js
@@ -827,15 +827,21 @@ module.exports = function (urlString) {
 /******/ 		
 /******/ 			linkTag.rel = "stylesheet";
 /******/ 			linkTag.type = "text/css";
-/******/ 			linkTag.onload = resolve;
-/******/ 			linkTag.onerror = function(event) {
-/******/ 				var request = event && event.target && event.target.href || fullhref;
-/******/ 				var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 				err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 				err.request = request;
-/******/ 				linkTag.parentNode.removeChild(linkTag)
-/******/ 				reject(err);
-/******/ 			};
+/******/ 			var onLinkComplete = (event) => {
+/******/ 				// avoid mem leaks.
+/******/ 				linkTag.onerror = linkTag.onload = null;
+/******/ 				if (event.type === 'load') {
+/******/ 					resolve();
+/******/ 				} else {
+/******/ 					var request = event && event.target && event.target.href || fullhref;
+/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 					err.request = request;
+/******/ 					linkTag.parentNode.removeChild(linkTag)
+/******/ 					reject(err);
+/******/ 				}
+/******/ 			}
+/******/ 			linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 			linkTag.href = fullhref;
 /******/ 		
 /******/ 			document.head.appendChild(linkTag);
diff --git a/test/cases/insert-function/expected/webpack-4/main.js b/test/cases/insert-function/expected/webpack-4/main.js
index a23de8c..45c2e3d 100644
--- a/test/cases/insert-function/expected/webpack-4/main.js
+++ b/test/cases/insert-function/expected/webpack-4/main.js
@@ -104,16 +104,22 @@
 /******/
 /******/ 				linkTag.rel = "stylesheet";
 /******/ 				linkTag.type = "text/css";
-/******/ 				linkTag.onload = resolve;
-/******/ 				linkTag.onerror = function(event) {
-/******/ 					var request = event && event.target && event.target.href || fullhref;
-/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 					err.request = request;
-/******/ 					delete installedCssChunks[chunkId]
-/******/ 					linkTag.parentNode.removeChild(linkTag)
-/******/ 					reject(err);
+/******/ 				var onLinkComplete = function (event) {
+/******/ 					// avoid mem leaks.
+/******/ 					linkTag.onerror = linkTag.onload = null;
+/******/ 					if (event.type === 'load') {
+/******/ 						resolve();
+/******/ 					} else {
+/******/ 						var request = event && event.target && event.target.href || fullhref;
+/******/ 						var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 						err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 						err.request = request;
+/******/ 						delete installedCssChunks[chunkId]
+/******/ 						linkTag.parentNode.removeChild(linkTag)
+/******/ 						reject(err);
+/******/ 					}
 /******/ 				};
+/******/ 				linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 				linkTag.href = fullhref;
 /******/
 /******/ 				(function (linkTag) {
diff --git a/test/cases/insert-function/expected/webpack-5/main.js b/test/cases/insert-function/expected/webpack-5/main.js
index 12c5ade..55507ce 100644
--- a/test/cases/insert-function/expected/webpack-5/main.js
+++ b/test/cases/insert-function/expected/webpack-5/main.js
@@ -162,15 +162,21 @@
 /******/ 		
 /******/ 			linkTag.rel = "stylesheet";
 /******/ 			linkTag.type = "text/css";
-/******/ 			linkTag.onload = resolve;
-/******/ 			linkTag.onerror = function(event) {
-/******/ 				var request = event && event.target && event.target.href || fullhref;
-/******/ 				var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 				err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 				err.request = request;
-/******/ 				linkTag.parentNode.removeChild(linkTag)
-/******/ 				reject(err);
-/******/ 			};
+/******/ 			var onLinkComplete = (event) => {
+/******/ 				// avoid mem leaks.
+/******/ 				linkTag.onerror = linkTag.onload = null;
+/******/ 				if (event.type === 'load') {
+/******/ 					resolve();
+/******/ 				} else {
+/******/ 					var request = event && event.target && event.target.href || fullhref;
+/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 					err.request = request;
+/******/ 					linkTag.parentNode.removeChild(linkTag)
+/******/ 					reject(err);
+/******/ 				}
+/******/ 			}
+/******/ 			linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 			linkTag.href = fullhref;
 /******/ 		
 /******/ 			(function (linkTag) {
diff --git a/test/cases/insert-string/expected/webpack-4/main.js b/test/cases/insert-string/expected/webpack-4/main.js
index 3da28c6..fa47844 100644
--- a/test/cases/insert-string/expected/webpack-4/main.js
+++ b/test/cases/insert-string/expected/webpack-4/main.js
@@ -104,16 +104,22 @@
 /******/
 /******/ 				linkTag.rel = "stylesheet";
 /******/ 				linkTag.type = "text/css";
-/******/ 				linkTag.onload = resolve;
-/******/ 				linkTag.onerror = function(event) {
-/******/ 					var request = event && event.target && event.target.href || fullhref;
-/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 					err.request = request;
-/******/ 					delete installedCssChunks[chunkId]
-/******/ 					linkTag.parentNode.removeChild(linkTag)
-/******/ 					reject(err);
+/******/ 				var onLinkComplete = function (event) {
+/******/ 					// avoid mem leaks.
+/******/ 					linkTag.onerror = linkTag.onload = null;
+/******/ 					if (event.type === 'load') {
+/******/ 						resolve();
+/******/ 					} else {
+/******/ 						var request = event && event.target && event.target.href || fullhref;
+/******/ 						var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 						err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 						err.request = request;
+/******/ 						delete installedCssChunks[chunkId]
+/******/ 						linkTag.parentNode.removeChild(linkTag)
+/******/ 						reject(err);
+/******/ 					}
 /******/ 				};
+/******/ 				linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 				linkTag.href = fullhref;
 /******/
 /******/ 				var target = document.querySelector("script[src='1.js']");
diff --git a/test/cases/insert-string/expected/webpack-5/main.js b/test/cases/insert-string/expected/webpack-5/main.js
index 54d848c..9d78db3 100644
--- a/test/cases/insert-string/expected/webpack-5/main.js
+++ b/test/cases/insert-string/expected/webpack-5/main.js
@@ -162,15 +162,21 @@
 /******/ 		
 /******/ 			linkTag.rel = "stylesheet";
 /******/ 			linkTag.type = "text/css";
-/******/ 			linkTag.onload = resolve;
-/******/ 			linkTag.onerror = function(event) {
-/******/ 				var request = event && event.target && event.target.href || fullhref;
-/******/ 				var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 				err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 				err.request = request;
-/******/ 				linkTag.parentNode.removeChild(linkTag)
-/******/ 				reject(err);
-/******/ 			};
+/******/ 			var onLinkComplete = (event) => {
+/******/ 				// avoid mem leaks.
+/******/ 				linkTag.onerror = linkTag.onload = null;
+/******/ 				if (event.type === 'load') {
+/******/ 					resolve();
+/******/ 				} else {
+/******/ 					var request = event && event.target && event.target.href || fullhref;
+/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 					err.request = request;
+/******/ 					linkTag.parentNode.removeChild(linkTag)
+/******/ 					reject(err);
+/******/ 				}
+/******/ 			}
+/******/ 			linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 			linkTag.href = fullhref;
 /******/ 		
 /******/ 			var target = document.querySelector("script[src='1.js']");
diff --git a/test/cases/insert-undefined/expected/webpack-4/main.js b/test/cases/insert-undefined/expected/webpack-4/main.js
index 4f296ab..4ad5b42 100644
--- a/test/cases/insert-undefined/expected/webpack-4/main.js
+++ b/test/cases/insert-undefined/expected/webpack-4/main.js
@@ -104,16 +104,22 @@
 /******/
 /******/ 				linkTag.rel = "stylesheet";
 /******/ 				linkTag.type = "text/css";
-/******/ 				linkTag.onload = resolve;
-/******/ 				linkTag.onerror = function(event) {
-/******/ 					var request = event && event.target && event.target.href || fullhref;
-/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 					err.request = request;
-/******/ 					delete installedCssChunks[chunkId]
-/******/ 					linkTag.parentNode.removeChild(linkTag)
-/******/ 					reject(err);
+/******/ 				var onLinkComplete = function (event) {
+/******/ 					// avoid mem leaks.
+/******/ 					linkTag.onerror = linkTag.onload = null;
+/******/ 					if (event.type === 'load') {
+/******/ 						resolve();
+/******/ 					} else {
+/******/ 						var request = event && event.target && event.target.href || fullhref;
+/******/ 						var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 						err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 						err.request = request;
+/******/ 						delete installedCssChunks[chunkId]
+/******/ 						linkTag.parentNode.removeChild(linkTag)
+/******/ 						reject(err);
+/******/ 					}
 /******/ 				};
+/******/ 				linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 				linkTag.href = fullhref;
 /******/
 /******/ 				document.head.appendChild(linkTag);
diff --git a/test/cases/insert-undefined/expected/webpack-5/main.js b/test/cases/insert-undefined/expected/webpack-5/main.js
index d105a4b..5a4b5b2 100644
--- a/test/cases/insert-undefined/expected/webpack-5/main.js
+++ b/test/cases/insert-undefined/expected/webpack-5/main.js
@@ -162,15 +162,21 @@
 /******/ 		
 /******/ 			linkTag.rel = "stylesheet";
 /******/ 			linkTag.type = "text/css";
-/******/ 			linkTag.onload = resolve;
-/******/ 			linkTag.onerror = function(event) {
-/******/ 				var request = event && event.target && event.target.href || fullhref;
-/******/ 				var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
-/******/ 				err.code = "CSS_CHUNK_LOAD_FAILED";
-/******/ 				err.request = request;
-/******/ 				linkTag.parentNode.removeChild(linkTag)
-/******/ 				reject(err);
-/******/ 			};
+/******/ 			var onLinkComplete = (event) => {
+/******/ 				// avoid mem leaks.
+/******/ 				linkTag.onerror = linkTag.onload = null;
+/******/ 				if (event.type === 'load') {
+/******/ 					resolve();
+/******/ 				} else {
+/******/ 					var request = event && event.target && event.target.href || fullhref;
+/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
+/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
+/******/ 					err.request = request;
+/******/ 					linkTag.parentNode.removeChild(linkTag)
+/******/ 					reject(err);
+/******/ 				}
+/******/ 			}
+/******/ 			linkTag.onerror = linkTag.onload = onLinkComplete;
 /******/ 			linkTag.href = fullhref;
 /******/ 		
 /******/ 			document.head.appendChild(linkTag);