diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index d8434f2311a375..9fb1d449a579e4 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -28,6 +28,7 @@ const { SafeArrayIterator, SafeMap, SafeWeakMap, + SafeSet, StringPrototypeIncludes, StringPrototypePadStart, StringPrototypeRepeat, @@ -687,6 +688,32 @@ Console.prototype.groupCollapsed = Console.prototype.group; function initializeGlobalConsole(globalConsole) { globalConsole[kBindStreamsLazy](process); globalConsole[kBindProperties](true, 'auto'); + + const { + addSerializeCallback, + isBuildingSnapshot, + } = require('v8').startupSnapshot; + + if (!internalBinding('config').hasInspector || !isBuildingSnapshot()) { + return; + } + const { console: consoleFromVM } = internalBinding('inspector'); + const nodeConsoleKeys = ObjectKeys(Console.prototype); + const vmConsoleKeys = ObjectKeys(consoleFromVM); + const originalKeys = new SafeSet(vmConsoleKeys.concat(nodeConsoleKeys)); + const inspectorConsoleKeys = new SafeSet(); + for (const key of ObjectKeys(globalConsole)) { + if (!originalKeys.has(key)) { + inspectorConsoleKeys.add(key); + } + } + // During deserialization these should be reinstalled to console by + // V8 when the inspector client is created. + addSerializeCallback(() => { + for (const key of inspectorConsoleKeys) { + globalConsole[key] = undefined; + } + }); } module.exports = { diff --git a/test/fixtures/snapshot/console.js b/test/fixtures/snapshot/console.js new file mode 100644 index 00000000000000..fc209e0c6cebca --- /dev/null +++ b/test/fixtures/snapshot/console.js @@ -0,0 +1,9 @@ +const { + setDeserializeMainFunction, +} = require('v8').startupSnapshot; + +console.log(JSON.stringify(Object.keys(console), null, 2)); + +setDeserializeMainFunction(() => { + console.log(JSON.stringify(Object.keys(console), null, 2)); +}); diff --git a/test/parallel/test-snapshot-console.js b/test/parallel/test-snapshot-console.js new file mode 100644 index 00000000000000..781b088ab25315 --- /dev/null +++ b/test/parallel/test-snapshot-console.js @@ -0,0 +1,62 @@ +'use strict'; + +// TODO(joyeecheung): remove the flag when it is turned on by default in V8. +// Flags: --experimental-async-stack-tagging-api +// This tests the console works in the deserialized snapshot. + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const path = require('path'); +const fs = require('fs'); + +tmpdir.refresh(); +const blobPath = path.join(tmpdir.path, 'snapshot.blob'); +const entry = fixtures.path('snapshot', 'console.js'); + +{ + const child = spawnSync(process.execPath, [ + '--experimental-async-stack-tagging-api', + '--snapshot-blob', + blobPath, + '--build-snapshot', + entry, + ], { + cwd: tmpdir.path + }); + const stdout = child.stdout.toString(); + if (child.status !== 0) { + console.log(stdout); + console.log(child.stderr.toString()); + assert.strictEqual(child.status, 0); + } + assert.deepStrictEqual(Object.keys(console), JSON.parse(stdout)); + const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob')); + assert(stats.isFile()); +} + +{ + const child = spawnSync(process.execPath, [ + '--experimental-async-stack-tagging-api', + '--snapshot-blob', + blobPath, + ], { + cwd: tmpdir.path, + env: { + ...process.env, + } + }); + + const stdout = child.stdout.toString(); + if (child.status !== 0) { + console.log(stdout); + console.log(child.stderr.toString()); + assert.strictEqual(child.status, 0); + } + assert.deepStrictEqual(Object.keys(console), JSON.parse(stdout)); + assert.strictEqual(child.status, 0); +}