This article was published by Barbara Jr

For those of you who have done monitoring with window.onerror events, you should know that cross-domain scripts will give you “Script Error.” You can’t get specific Error messages and stack information.

Here readers can follow me in an experiment to get a better idea of this. Here’s an experiment to prepare:

app.js

Create a Node APP that acts as a static server only and provides two ports for cross-domain experiments.

const express = require('express');

const app = express();

app.use(express.static('./public'));

app.listen(3000);
app.listen(4000);
Copy the code

public/index.html

Create a static page that listens for window.onerror events and prints the stack of events. Load JS files for both domains at the same time.

<! DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Script  Error Test</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <button id="btn-3000">3000</button> <button id="btn-4000">4000</button> <div> <pre id="info"></pre> </div> </body> <script> window.addEventListener('error', evt => { const info = evt.error ? evt.error.stack : evt.message; document.querySelector('#info').textContent = info; }); < / script > < script SRC = "http://127.0.0.1:3000/at3000.js" > < / script > < script SRC = "http://127.0.0.1:4000/at4000.js" > < / script > < / HTML >Copy the code

public/at3000.js

Create a script that executes on port 3000, listens for the 3000 button click event, and throws an exception:

const btn3k = document.querySelector('#btn-3000');
btn3k.addEventListener('click', () = > {throw new Error('Fail 3000');
});
Copy the code

public/at4000.js

Also, create a script to execute on port 4000:

const btn4k = document.querySelector('#btn-4000');
btn4k.addEventListener('click', () = > {throw new Error('Fail 4000');
});
Copy the code

Retrieval Script Error

At this point, we launch the Node APP: Node app.js and go to http://127.0.0.1:3000.

By clicking on buttons 3000 and 4000 respectively, we find that the exception message can be captured after the 3000 button below the same domain is clicked. The 4000 button, on the other hand, has only one Script Error.

We reproduce “Script Error.”!

Raise your hand, I know, just add a cross-domain header!

Access-Control-Allow-Origin

Yes, we can add cross-domain protocol headers to static file servers:

app.use(express.static('./public', {
  setHeaders(res) {
    res.set('access-control-allow-origin', res.req.get('origin'));
    res.set('access-control-allow-credentials'.'true'); }}));Copy the code

At the same time, when loading JS, add cross-domain declaration:

<script src="http://127.0.0.1:4000/at4000.js" crossorigin="anonymous"></script>
Copy the code

This way, no matter 3000 or 4000 button, we can click to get the exception information.

However, this plan has two fatal weaknesses:

  • If JS declares itcrossorigin="anonymous"But if the response header is not correct, JS willDirectly unable to execute
  • We don’t always have configuration privileges for static servers, and cross-domain headers can’t be added whenever we want

Alternative way of thinking

Would you believe it if I told you that I could just load a “special” JS before loading the JS file without the cross-domain header?

<script src="http://127.0.0.1:3000/inject-event-target.js"></script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>
Copy the code

The magic injection-event-target. js allows us to get the execution exception information of the 4000 button event handler without the cross-domain header.

If you think it’s amazing, please like it and read on. This magic JS is actually very simple:

const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
  const wrappedListener = function (. args) {
    try {
      return listener.apply(this, args);
    }
    catch (err) {
      throwerr; }}return originAddEventListener.call(this, type, wrappedListener, options);
}
Copy the code

The principle is not the author’s original, but from this article to learn.

A quick explanation:

  • Rewrite the addEventListener method of EventTarget;
  • Wrap an incoming listener, return the wrapped listener, and execute a try-catch.
  • Browsers don’t cross-block try-catch exceptions, so when you catch them, there’s a stack of information;
  • When an exception is thrown again, it executes codomain code, so window.onError does not lose stack information.

In fact, we can also “extend the stack” by wrapping the addEventListener:

We know not only the exception stack, but also where the event handler that caused the exception was added. To achieve this effect, it is also very simple:

 ((a)= > {
   const originAddEventListener = EventTarget.prototype.addEventListener;
   EventTarget.prototype.addEventListener = function (type, listener, options) {+// Capture the stack when adding events
+    const addStack = new Error(`Event (${type}) `).stack;
     const wrappedListener = function (. args) {
       try {
         return listener.apply(this, args);
       }
       catch (err) {
+        // When an exception occurs, extend the stack
+        err.stack += '\n' + addStack;
         throwerr; }}return originAddEventListener.call(this, type, wrappedListener, options);
   }
 })();
Copy the code

In the same way, we can intercept setTimeout, setInterval, requestAnimationFrame, or even XMLHttpRequest to get information we wouldn’t otherwise get.

This article has been authorized by the author to Tencent Cloud + community, more original text pleaseClick on the

Search concern public number “cloud plus community”, the first time to obtain technical dry goods, after concern reply 1024 send you a technical course gift package!