-
Notifications
You must be signed in to change notification settings - Fork 29.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lib: make the global console [[Prototype]] an empty object #23509
Conversation
Defensively marked as semver-major. cc @nodejs/tsc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d like to measure userland breakage though, in case anyone is depending on console instanceof console.Console
being true. If so maybe we can have a custom Symbol.hasInstance
function to mitigate that.
(Previous CITGM failed because disk on test-osuosl-aix61-ppc64_be-2 was full) |
lib/console.js
Outdated
ConsoleImpl.call(this, ...args); | ||
} | ||
|
||
// Reflect.ownKeys() is used here for for retrieving Symbols |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Double for
lib/console.js
Outdated
function noop() {} | ||
function Console(...args) { | ||
if (!(this instanceof Console)) { | ||
return new Console(...arguments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ...args
would be better. I prefer to avoid arguments
as much as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a copy-paste but yeah probably also makes sense to change it in this patch
I've seen interesting |
lib/console.js
Outdated
// we cannot actually use `new Console` to construct the global console. | ||
// Therefore, the console.Console.prototype is not | ||
// in the global console prototype chain anymore. | ||
const consolePrototype = Object.create(Object.prototype); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't Object.create(Object.prototype)
just {}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have an opinion on this, it just somehow seems more readable to be if we just do exactly what the spec says (but probably {}
is more readable for people who skip the comments).
@jdalton Then it would make sense to include jest in CITGM if it is not there already. cc @nodejs/citgm |
Currently blocked because Jest uses yarn for tests: nodejs/citgm#560 |
lib/console.js
Outdated
function noop() {} | ||
function Console(...args) { | ||
if (!(this instanceof Console)) { | ||
return new Console(...arguments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use Reflect.construct(Console, args)
lib/console.js
Outdated
|
||
function noop() {} | ||
function Console(...args) { | ||
if (!(this instanceof Console)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of !(this instanceof Console)
could use !new.target
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that anybody does that, but these would behave differently if somebody did ES5-style inheritance from this class, right? The current version is also a bit more idiomatic, imo (same for the comment above…)
I'm a little fuzzy on the need for |
I am a bit conflicted on the two approach (adding props to an object v.s. removing props from a |
I think this is overcomplicating the PR. Here's a snippet from my own implementation in a related project. const globalConsole = assignProperties({}, new Console(stdout, stderr)) There is no need to rewire |
nvm i realize that it has to be a plain object. current implementation seems fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please look into simplifying this PR. I believe the gist of the PR can be done in a handful of lines without major refactoring to Console
. See #23509 (comment).
What do you think about #23509 (comment) ? So far 33 contributors have committed in this file, so I believe it's worth the cost to rename the class to hint future contributors that this is an implementation without having them to scroll all the way down to see the comments. |
Class renaming, or other deeper refactoring, is probably better suited for a PR specifically for that. |
@jdalton I don't think the question was if we should do refactoring in this PR. I think the question was what you thought about the argument made in that comment that the approach currently in this PR is more maintainable in that it will be less error-prone for future contributors to modify. Here's the comment:
|
Oh, I don't think the approach in this PR promotes those things. |
Jest team member here 👋 Saw the link from the CITGM PR, and in Jest we do Code: An instance of that subclass is injected into the user code (running as a |
@SimenB Hi! Thanks for the explanation, based on that I don't think this would break Jest, since after this patch the lookups on the global console and other Console instances still arrive to the same stuff - just a matter of through the prototype chain or not. |
I have a module where this will be breaking: https://github.com/trygve-lie/abslog/blob/master/lib/log.js#L8 The module is an abstract log API making it possible for modules to have log statements without depending on a full log library. A consumer of a module using this abstraction are then able to pass in a logger of their choice as long it comply with the API of the abstraction. This is btw a pattern used in fastify.js, though this module differ from the one in fastify.js that this one support passing in
Having something like that would be very handy. |
@trygve-lie Thanks for the explanation! Indeed, we should implement a |
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. Refs: https://console.spec.whatwg.org/#console-namespace Refs: whatwg/console#3
29c4bd0
to
5556e47
Compare
@jdalton I looked into the alternative implementation a bit, and noticed that simply assigning properties of a Console instance to a new object would result in a behavior change - the methods called on the global console will have the temporary Console instance as their contexts since the Console constructor binds all the method to the instance being constructed (so that users can do console._stdout = undefined;
// Should've thrown because we have broken _stdout, but doesn't because log
// actually gets called on another object.
console.log('test'); In the context of the Web, this is expected because console is a namespace and stuff like I've patched that through a condition in the loop to rebind the methods to the global console. |
Another note: I went with CI: https://ci.nodejs.org/job/node-test-pull-request/18145/ |
@joyeecheung Thank you for the update on your findings and the PR! |
Landed in 6223236. |
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 <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Post-mortem: the two new CITGM failures did not appear to be relevant: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/1599/nodes=fedora-last-latest-x64/testReport/junit/(root)/citgm/bluebird_v3_5_2/ |
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: nodejs#23509 Refs: whatwg/console#3 Refs: https://console.spec.whatwg.org/#console-namespace Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Specifically for v11.x. Refs: nodejs#23509
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 <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Specifically for v11.x. PR-URL: #25420 Refs: #23509 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
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: nodejs#23509 Refs: whatwg/console#3 Refs: https://console.spec.whatwg.org/#console-namespace Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Specifically for v11.x. PR-URL: nodejs#25420 Refs: nodejs#23509 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
lib: make the global console [[Prototype]] an empty object
From the WHATWG console spec:
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.
But, we still maintain that
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.
Refs: https://console.spec.whatwg.org/#console-namespace
Refs: whatwg/console#3
make -j4 test
(UNIX), orvcbuild test
(Windows) passes