Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 73c3a3d

Browse filesBrowse files
joyeecheungBridgeAR
authored andcommitted
lib: make the global console [[Prototype]] an empty object
From the WHATWG console spec: > For historical web-compatibility reasons, the namespace object for > console must have as its [[Prototype]] an empty object, created as > if by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. Since in Node.js, the Console constructor has been exposed through require('console'), we need to keep the Console constructor but we cannot actually use `new Console` to construct the global console. This patch changes the prototype chain of the global console object, so the console.Console.prototype is not in the global console prototype chain anymore. ``` const proto = Object.getPrototypeOf(global.console); // Before this patch proto.constructor === global.console.Console // After this patch proto.constructor === Object ``` But, we still maintain that ``` global.console instanceof global.console.Console ``` through a custom Symbol.hasInstance function of Console that tests for a special symbol kIsConsole for backwards compatibility. This fixes a case in the console Web Platform Test that we commented out. PR-URL: #23509 Refs: whatwg/console#3 Refs: https://console.spec.whatwg.org/#console-namespace Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Denys Otrishko <shishugi@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
1 parent 453bd18 commit 73c3a3d
Copy full SHA for 73c3a3d

File tree

Expand file treeCollapse file tree

2 files changed

+98
-37
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+98
-37
lines changed
Open diff view settings
Collapse file

‎lib/console.js‎

Copy file name to clipboardExpand all lines: lib/console.js
+50-6Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,21 @@ let cliTable;
6060

6161
// Track amount of indentation required via `console.group()`.
6262
const kGroupIndent = Symbol('kGroupIndent');
63-
6463
const kFormatForStderr = Symbol('kFormatForStderr');
6564
const kFormatForStdout = Symbol('kFormatForStdout');
6665
const kGetInspectOptions = Symbol('kGetInspectOptions');
6766
const kColorMode = Symbol('kColorMode');
67+
const kIsConsole = Symbol('kIsConsole');
6868

6969
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
70-
if (!(this instanceof Console)) {
70+
// We have to test new.target here to see if this function is called
71+
// with new, because we need to define a custom instanceof to accommodate
72+
// the global console.
73+
if (!new.target) {
7174
return new Console(...arguments);
7275
}
7376

77+
this[kIsConsole] = true;
7478
if (!options || typeof options.write === 'function') {
7579
options = {
7680
stdout: options,
@@ -128,7 +132,7 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
128132
var keys = Object.keys(Console.prototype);
129133
for (var v = 0; v < keys.length; v++) {
130134
var k = keys[v];
131-
this[k] = this[k].bind(this);
135+
this[k] = Console.prototype[k].bind(this);
132136
}
133137
}
134138

@@ -470,10 +474,50 @@ Console.prototype.table = function(tabularData, properties) {
470474
return final(keys, values);
471475
};
472476

473-
module.exports = new Console({
477+
function noop() {}
478+
479+
// See https://console.spec.whatwg.org/#console-namespace
480+
// > For historical web-compatibility reasons, the namespace object
481+
// > for console must have as its [[Prototype]] an empty object,
482+
// > created as if by ObjectCreate(%ObjectPrototype%),
483+
// > instead of %ObjectPrototype%.
484+
485+
// Since in Node.js, the Console constructor has been exposed through
486+
// require('console'), we need to keep the Console constructor but
487+
// we cannot actually use `new Console` to construct the global console.
488+
// Therefore, the console.Console.prototype is not
489+
// in the global console prototype chain anymore.
490+
const globalConsole = Object.create({});
491+
const tempConsole = new Console({
474492
stdout: process.stdout,
475493
stderr: process.stderr
476494
});
477-
module.exports.Console = Console;
478495

479-
function noop() {}
496+
// Since Console is not on the prototype chain of the global console,
497+
// the symbol properties on Console.prototype have to be looked up from
498+
// the global console itself.
499+
for (const prop of Object.getOwnPropertySymbols(Console.prototype)) {
500+
globalConsole[prop] = Console.prototype[prop];
501+
}
502+
503+
// Reflect.ownKeys() is used here for retrieving Symbols
504+
for (const prop of Reflect.ownKeys(tempConsole)) {
505+
const desc = { ...(Reflect.getOwnPropertyDescriptor(tempConsole, prop)) };
506+
// Since Console would bind method calls onto the instance,
507+
// make sure the methods are called on globalConsole instead of
508+
// tempConsole.
509+
if (typeof Console.prototype[prop] === 'function') {
510+
desc.value = Console.prototype[prop].bind(globalConsole);
511+
}
512+
Reflect.defineProperty(globalConsole, prop, desc);
513+
}
514+
515+
globalConsole.Console = Console;
516+
517+
Object.defineProperty(Console, Symbol.hasInstance, {
518+
value(instance) {
519+
return instance[kIsConsole];
520+
}
521+
});
522+
523+
module.exports = globalConsole;
Collapse file

‎test/parallel/test-console-instance.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-console-instance.js
+48-31Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
const common = require('../common');
2424
const assert = require('assert');
2525
const Stream = require('stream');
26-
const Console = require('console').Console;
26+
const requiredConsole = require('console');
27+
const Console = requiredConsole.Console;
2728

2829
const out = new Stream();
2930
const err = new Stream();
@@ -35,6 +36,11 @@ process.stdout.write = process.stderr.write = common.mustNotCall();
3536
// Make sure that the "Console" function exists.
3637
assert.strictEqual(typeof Console, 'function');
3738

39+
assert.strictEqual(requiredConsole, global.console);
40+
// Make sure the custom instanceof of Console works
41+
assert.ok(global.console instanceof Console);
42+
assert.ok(!({} instanceof Console));
43+
3844
// Make sure that the Console constructor throws
3945
// when not given a writable stream instance.
4046
common.expectsError(
@@ -62,46 +68,57 @@ common.expectsError(
6268

6369
out.write = err.write = (d) => {};
6470

65-
const c = new Console(out, err);
71+
{
72+
const c = new Console(out, err);
73+
assert.ok(c instanceof Console);
6674

67-
out.write = err.write = common.mustCall((d) => {
68-
assert.strictEqual(d, 'test\n');
69-
}, 2);
75+
out.write = err.write = common.mustCall((d) => {
76+
assert.strictEqual(d, 'test\n');
77+
}, 2);
7078

71-
c.log('test');
72-
c.error('test');
79+
c.log('test');
80+
c.error('test');
7381

74-
out.write = common.mustCall((d) => {
75-
assert.strictEqual(d, '{ foo: 1 }\n');
76-
});
82+
out.write = common.mustCall((d) => {
83+
assert.strictEqual(d, '{ foo: 1 }\n');
84+
});
7785

78-
c.dir({ foo: 1 });
86+
c.dir({ foo: 1 });
7987

80-
// Ensure that the console functions are bound to the console instance.
81-
let called = 0;
82-
out.write = common.mustCall((d) => {
83-
called++;
84-
assert.strictEqual(d, `${called} ${called - 1} [ 1, 2, 3 ]\n`);
85-
}, 3);
88+
// Ensure that the console functions are bound to the console instance.
89+
let called = 0;
90+
out.write = common.mustCall((d) => {
91+
called++;
92+
assert.strictEqual(d, `${called} ${called - 1} [ 1, 2, 3 ]\n`);
93+
}, 3);
8694

87-
[1, 2, 3].forEach(c.log);
95+
[1, 2, 3].forEach(c.log);
96+
}
8897

89-
// Console() detects if it is called without `new` keyword.
90-
Console(out, err);
98+
// Test calling Console without the `new` keyword.
99+
{
100+
const withoutNew = Console(out, err);
101+
assert.ok(withoutNew instanceof Console);
102+
}
91103

92-
// Extending Console works.
93-
class MyConsole extends Console {
94-
hello() {}
104+
// Test extending Console
105+
{
106+
class MyConsole extends Console {
107+
hello() {}
108+
}
109+
const myConsole = new MyConsole(process.stdout);
110+
assert.strictEqual(typeof myConsole.hello, 'function');
111+
assert.ok(myConsole instanceof Console);
95112
}
96-
const myConsole = new MyConsole(process.stdout);
97-
assert.strictEqual(typeof myConsole.hello, 'function');
98113

99114
// Instance that does not ignore the stream errors.
100-
const c2 = new Console(out, err, false);
115+
{
116+
const c2 = new Console(out, err, false);
101117

102-
out.write = () => { throw new Error('out'); };
103-
err.write = () => { throw new Error('err'); };
118+
out.write = () => { throw new Error('out'); };
119+
err.write = () => { throw new Error('err'); };
104120

105-
assert.throws(() => c2.log('foo'), /^Error: out$/);
106-
assert.throws(() => c2.warn('foo'), /^Error: err$/);
107-
assert.throws(() => c2.dir('foo'), /^Error: out$/);
121+
assert.throws(() => c2.log('foo'), /^Error: out$/);
122+
assert.throws(() => c2.warn('foo'), /^Error: err$/);
123+
assert.throws(() => c2.dir('foo'), /^Error: out$/);
124+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.