diff --git a/.changeset/bright-cities-taste.md b/.changeset/bright-cities-taste.md new file mode 100644 index 0000000000..43aaf3990a --- /dev/null +++ b/.changeset/bright-cities-taste.md @@ -0,0 +1,5 @@ +--- +"@rrweb/rrweb-plugin-console-record": patch +--- + +Fix wrapped console methods being called with the wrong `this`, which could throw "Illegal invocation" in strict contexts such as extension content scripts diff --git a/packages/plugins/rrweb-plugin-console-record/src/index.ts b/packages/plugins/rrweb-plugin-console-record/src/index.ts index 986d7115a6..485401b888 100644 --- a/packages/plugins/rrweb-plugin-console-record/src/index.ts +++ b/packages/plugins/rrweb-plugin-console-record/src/index.ts @@ -188,7 +188,12 @@ function initLogObserver( level, (original: (...args: Array) => void) => { return (...args: Array) => { - original.apply(this, args); + // `this` here is not the logger (this arrow fn's lexical `this` is + // whatever `replace` was called with, not the console instance). + // Native console methods can throw "Illegal invocation" when + // called with the wrong receiver (observed in extension content + // scripts), so bind explicitly to `_logger`. + original.apply(_logger, args); if (level === 'assert' && !!args[0]) { // assert does not log if the first argument evaluates to true @@ -230,7 +235,8 @@ function initLogObserver( }); } } catch (error) { - original('rrweb logger error:', error, ...args); + // same `this`-binding requirement as above + original.apply(_logger, ['rrweb logger error:', error, ...args]); } finally { inStack = false; } diff --git a/packages/plugins/rrweb-plugin-console-record/test/this-binding.test.ts b/packages/plugins/rrweb-plugin-console-record/test/this-binding.test.ts new file mode 100644 index 0000000000..5a4864f7ec --- /dev/null +++ b/packages/plugins/rrweb-plugin-console-record/test/this-binding.test.ts @@ -0,0 +1,39 @@ +/** + * @vitest-environment jsdom + */ +import { describe, it, expect } from 'vitest'; +import { getRecordConsolePlugin } from '../src'; +import type { IWindow } from '@rrweb/types'; + +describe('rrweb-plugin-console-record this-binding', () => { + it('invokes the original console method with the logger as `this`', () => { + // Native console implementations in some contexts (observed in Chrome + // extension content scripts) throw "Illegal invocation" if the method + // is applied with an incorrect receiver. Simulate that here: if the + // wrapper ever calls through with the wrong `this`, this logger throws. + let capturedThis: unknown; + const fakeLogger = { + log(...args: unknown[]) { + capturedThis = this; + }, + }; + + const plugin = getRecordConsolePlugin({ + level: ['log'], + logger: fakeLogger, + }); + + const stop = plugin.observer( + () => { + // + }, + window as unknown as IWindow, + plugin.options, + ); + + fakeLogger.log('hello'); + + expect(capturedThis).toBe(fakeLogger); + stop(); + }); +});