Skip to content
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

feat: Add support for Node’s [util.inspect.custom] symbol method #165

Closed
wants to merge 4 commits into from

Conversation

ExE-Boss
Copy link
Contributor

@ExE-Boss ExE-Boss commented Feb 9, 2020

Currently, web browsers display web platform objects in the DevTools console in a nice‑ish way:

> console.log(window)
Window data:text/html;charset=utf-8,<body id="someId" class="a b c"/>

> console.log(document.body)
<body id="someId" class="a b c">

Trying the same in JSDOM results in the rather unhelpful:

Long <pre> block

> console.log(window)
<ref *1> Window {
	// Interface constructors redacted for brevity
	// Event handlers redacted for brevity
	_registeredHandlers: Set(0) {},
	_eventHandlers: [Object: null prototype] {},
	_globalObject: [Circular *1],
	_resourceLoader: NoOpResourceLoader {
		_strictSSL: true,
		_proxy: undefined,
		_userAgent: 'Mozilla/5.0 (win32) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.1.0'
	},
	_globalProxy: [Circular *1],
	_document: Document {
		location: [Getter/Setter],
		[Symbol(DOM SymbolTree)]: SymbolTreeNode {
			parent: null,
			previousSibling: null,
			nextSibling: null,
			firstChild: null,
			lastChild: null,
			childrenVersion: 0,
			childIndexCachedUpTo: null,
			cachedIndex: -1,
			cachedIndexVersion: NaN
		}
	},
	_origin: 'null',
	_sessionHistory: SessionHistory {
		_window: [Circular *1],
		_windowImpl: EventTargetImpl {
			_globalObject: [Circular *1],
			_eventListeners: [Object: null prototype] {},
			[Symbol(wrapper)]: [Getter]
		},
		_historyTraversalQueue: Set(0) {},
		_entries: [ [Object] ],
		_currentIndex: 0
	},
	_virtualConsole: VirtualConsole {
		_events: [Object: null prototype] {
			error: [Array],
			log: [Function: onMethodCall],
			warn: [Function: onMethodCall],
			dir: [Function: onMethodCall],
			time: [Function: onMethodCall],
			timeEnd: [Function: onMethodCall],
			timeLog: [Function: onMethodCall],
			trace: [Function: onMethodCall],
			assert: [Function: onMethodCall],
			clear: [Function: onMethodCall],
			count: [Function: onMethodCall],
			countReset: [Function: onMethodCall],
			group: [Function: onMethodCall],
			groupEnd: [Function: onMethodCall],
			table: [Function: onMethodCall],
			debug: [Function: onMethodCall],
			info: [Function: onMethodCall],
			dirxml: [Function: onMethodCall],
			groupCollapsed: [Function: onMethodCall],
			Console: [Function: onMethodCall],
			profile: [Function: onMethodCall],
			profileEnd: [Function: onMethodCall],
			timeStamp: [Function: onMethodCall],
			context: [Function: onMethodCall],
			jsdomError: [Function (anonymous)]
		},
		_eventsCount: 25,
		_maxListeners: undefined,
		[Symbol(kCapture)]: false
	},
	_runScripts: undefined,
	_top: [Circular *1],
	_parent: [Circular *1],
	_frameElement: null,
	_length: 0,
	_pretendToBeVisual: false,
	_storageQuota: 5000000,
	_commonForOrigin: {
		null: {
			localStorageArea: Map(0) {},
			sessionStorageArea: Map(0) {},
			windowsInSameOrigin: [Array]
		}
	},
	_currentOriginData: {
		localStorageArea: Map(0) {},
		sessionStorageArea: Map(0) {},
		windowsInSameOrigin: [ [Circular *1] ]
	},
	_localStorage: Storage {},
	_sessionStorage: Storage {},
	_selection: SelectionImpl {
		_range: null,
		_direction: 0,
		_globalObject: [Circular *1],
		[Symbol(wrapper)]: Selection {}
	},
	// Property getters and methods redacted for brevity
	console: {
		assert: [Function (anonymous)],
		clear: [Function (anonymous)],
		count: [Function (anonymous)],
		countReset: [Function (anonymous)],
		debug: [Function (anonymous)],
		dir: [Function (anonymous)],
		dirxml: [Function (anonymous)],
		error: [Function (anonymous)],
		group: [Function (anonymous)],
		groupCollapsed: [Function (anonymous)],
		groupEnd: [Function (anonymous)],
		info: [Function (anonymous)],
		log: [Function (anonymous)],
		table: [Function (anonymous)],
		time: [Function (anonymous)],
		timeLog: [Function (anonymous)],
		timeEnd: [Function (anonymous)],
		trace: [Function (anonymous)],
		warn: [Function (anonymous)]
	},
	[Symbol([webidl2js]  constructor registry)]: [Object: null prototype] {
		// Interface constructors redacted for brevity
	},
	[Symbol(named property tracker)]: NamedPropertiesTracker {
		object: [Circular *1],
		objectProxy: [Circular *1],
		resolverFunc: [Function: bound namedPropertyResolver],
		trackedValues: Map(0) {}
	}
}

> console.log(document.body)
HTMLBodyElement {}

With runScripts enabled, Window becomes even worse:

Very long <pre> block

> console.log(window)
<ref *2> Window {
	// Interface constructors redacted for brevity
	// Event handlers redacted for brevity
	_registeredHandlers: Set(0) {},
	_eventHandlers: [Object: null prototype] {},
	_globalObject: <ref *1> Window {
		// Interface constructors redacted for brevity
		// Event handlers redacted for brevity
		_registeredHandlers: Set(0) {},
		_eventHandlers: [Object: null prototype] {},
		_globalObject: [Circular *1],
		_resourceLoader: NoOpResourceLoader {
			_strictSSL: true,
			_proxy: undefined,
			_userAgent: 'Mozilla/5.0 (win32) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.1.0'
		},
		_globalProxy: [Circular *2],
		_document: Document {
			location: [Getter/Setter],
			[Symbol(DOM SymbolTree)]: [SymbolTreeNode]
		},
		_origin: 'null',
		_sessionHistory: SessionHistory {
			_window: [Circular *1],
			_windowImpl: [EventTargetImpl],
			_historyTraversalQueue: Set(0) {},
			_entries: [Array],
			_currentIndex: 0
		},
		_virtualConsole: VirtualConsole {
			_events: [Object: null prototype],
			_eventsCount: 25,
			_maxListeners: undefined,
			[Symbol(kCapture)]: false
		},
		_runScripts: 'outside-only',
		_top: [Circular *2],
		_parent: [Circular *2],
		_frameElement: null,
		_length: 0,
		_pretendToBeVisual: false,
		_storageQuota: 5000000,
		_commonForOrigin: { null: [Object] },
		_currentOriginData: {
			localStorageArea: Map(0) {},
			sessionStorageArea: Map(0) {},
			windowsInSameOrigin: [Array]
		},
		_localStorage: Storage {},
		_sessionStorage: Storage {},
		_selection: SelectionImpl {
			_range: null,
			_direction: 0,
			_globalObject: [Circular *1],
			[Symbol(wrapper)]: Selection {}
		},
		// Property getters and methods redacted for brevity
		console: {
			assert: [Function (anonymous)],
			clear: [Function (anonymous)],
			count: [Function (anonymous)],
			countReset: [Function (anonymous)],
			debug: [Function (anonymous)],
			dir: [Function (anonymous)],
			dirxml: [Function (anonymous)],
			error: [Function (anonymous)],
			group: [Function (anonymous)],
			groupCollapsed: [Function (anonymous)],
			groupEnd: [Function (anonymous)],
			info: [Function (anonymous)],
			log: [Function (anonymous)],
			table: [Function (anonymous)],
			time: [Function (anonymous)],
			timeLog: [Function (anonymous)],
			timeEnd: [Function (anonymous)],
			trace: [Function (anonymous)],
			warn: [Function (anonymous)]
		},
		[Symbol([webidl2js]  constructor registry)]: [Object: null prototype] {
			// Interface constructors redacted for brevity
		},
		[Symbol(named property tracker)]: NamedPropertiesTracker {
			object: [Circular *1],
			objectProxy: [Circular *2],
			resolverFunc: [Function: bound namedPropertyResolver],
			trackedValues: Map(0) {}
		}
	},
	_resourceLoader: NoOpResourceLoader {
		_strictSSL: true,
		_proxy: undefined,
		_userAgent: 'Mozilla/5.0 (win32) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.1.0'
	},
	_globalProxy: [Circular *2],
	_document: Document {
		location: [Getter/Setter],
		[Symbol(DOM SymbolTree)]: SymbolTreeNode {
			parent: null,
			previousSibling: null,
			nextSibling: null,
			firstChild: null,
			lastChild: null,
			childrenVersion: 0,
			childIndexCachedUpTo: null,
			cachedIndex: -1,
			cachedIndexVersion: NaN
		}
	},
	_origin: 'null',
	_sessionHistory: <ref *3> SessionHistory {
		_window: <ref *1> Window {
			// Interface constructors redacted for brevity
			// Event handlers redacted for brevity
			_registeredHandlers: Set(0) {},
			_eventHandlers: [Object: null prototype] {},
			_globalObject: [Circular *1],
			_resourceLoader: [NoOpResourceLoader],
			_globalProxy: [Circular *2],
			_document: [Document],
			_origin: 'null',
			_sessionHistory: [Circular *3],
			_virtualConsole: [VirtualConsole],
			_runScripts: 'outside-only',
			_top: [Circular *2],
			_parent: [Circular *2],
			_frameElement: null,
			_length: 0,
			_pretendToBeVisual: false,
			_storageQuota: 5000000,
			_commonForOrigin: [Object],
			_currentOriginData: [Object],
			_localStorage: Storage {},
			_sessionStorage: Storage {},
			_selection: [SelectionImpl],
			// Property getters and methods redacted for brevity
			[Symbol([webidl2js]  constructor registry)]: [Object: null prototype],
			[Symbol(named property tracker)]: [NamedPropertiesTracker]
		},
		_windowImpl: EventTargetImpl {
			_globalObject: [Window],
			_eventListeners: [Object: null prototype] {},
			[Symbol(wrapper)]: [Getter]
		},
		_historyTraversalQueue: Set(0) {},
		_entries: [ [Object] ],
		_currentIndex: 0
	},
	_virtualConsole: VirtualConsole {
		_events: [Object: null prototype] {
			error: [Array],
			log: [Function: onMethodCall],
			warn: [Function: onMethodCall],
			dir: [Function: onMethodCall],
			time: [Function: onMethodCall],
			timeEnd: [Function: onMethodCall],
			timeLog: [Function: onMethodCall],
			trace: [Function: onMethodCall],
			assert: [Function: onMethodCall],
			clear: [Function: onMethodCall],
			count: [Function: onMethodCall],
			countReset: [Function: onMethodCall],
			group: [Function: onMethodCall],
			groupEnd: [Function: onMethodCall],
			table: [Function: onMethodCall],
			debug: [Function: onMethodCall],
			info: [Function: onMethodCall],
			dirxml: [Function: onMethodCall],
			groupCollapsed: [Function: onMethodCall],
			Console: [Function: onMethodCall],
			profile: [Function: onMethodCall],
			profileEnd: [Function: onMethodCall],
			timeStamp: [Function: onMethodCall],
			context: [Function: onMethodCall],
			jsdomError: [Function (anonymous)]
		},
		_eventsCount: 25,
		_maxListeners: undefined,
		[Symbol(kCapture)]: false
	},
	_runScripts: 'outside-only',
	_top: [Circular *2],
	_parent: [Circular *2],
	_frameElement: null,
	_length: 0,
	_pretendToBeVisual: false,
	_storageQuota: 5000000,
	_commonForOrigin: {
		null: {
			localStorageArea: Map(0) {},
			sessionStorageArea: Map(0) {},
			windowsInSameOrigin: [Array]
		}
	},
	_currentOriginData: {
		localStorageArea: Map(0) {},
		sessionStorageArea: Map(0) {},
		windowsInSameOrigin: [ [Window] ]
	},
	_localStorage: Storage {},
	_sessionStorage: Storage {},
	_selection: <ref *4> SelectionImpl {
		_range: null,
		_direction: 0,
		_globalObject: <ref *1> Window {
			// Interface constructors redacted for brevity
			// Event handlers redacted for brevity
			_registeredHandlers: Set(0) {},
			_eventHandlers: [Object: null prototype] {},
			_globalObject: [Circular *1],
			_resourceLoader: [NoOpResourceLoader],
			_globalProxy: [Circular *2],
			_document: [Document],
			_origin: 'null',
			_sessionHistory: [SessionHistory],
			_virtualConsole: [VirtualConsole],
			_runScripts: 'outside-only',
			_top: [Circular *2],
			_parent: [Circular *2],
			_frameElement: null,
			_length: 0,
			_pretendToBeVisual: false,
			_storageQuota: 5000000,
			_commonForOrigin: [Object],
			_currentOriginData: [Object],
			_localStorage: Storage {},
			_sessionStorage: Storage {},
			_selection: [Circular *4],
			// Property getters and methods redacted for brevity
			[Symbol([webidl2js]  constructor registry)]: [Object: null prototype],
			[Symbol(named property tracker)]: [NamedPropertiesTracker]
		},
		[Symbol(wrapper)]: Selection {}
	},
	// Property getters and methods redacted for brevity
	console: {
		assert: [Function (anonymous)],
		clear: [Function (anonymous)],
		count: [Function (anonymous)],
		countReset: [Function (anonymous)],
		debug: [Function (anonymous)],
		dir: [Function (anonymous)],
		dirxml: [Function (anonymous)],
		error: [Function (anonymous)],
		group: [Function (anonymous)],
		groupCollapsed: [Function (anonymous)],
		groupEnd: [Function (anonymous)],
		info: [Function (anonymous)],
		log: [Function (anonymous)],
		table: [Function (anonymous)],
		time: [Function (anonymous)],
		timeLog: [Function (anonymous)],
		timeEnd: [Function (anonymous)],
		trace: [Function (anonymous)],
		warn: [Function (anonymous)]
	}
}

Node provides the util.inspect.custom symbol, which is registered globally using Symbol.for("nodejs.util.inspect.custom") since Node v10, and which can be used to add functions to customise how objects are displayed by the console.

Merging this makes it possible for JSDOM and other WebIDL2JS consumers to customise how the Node console and util.inspect function displays WebIDL2JS objects.

@domenic
Copy link
Member

domenic commented Feb 16, 2020

I'm unsure whether this is a good idea in general; adding more observable symbols to the public API is not the direction I'd like to go, but instead fewer (see #155). I certainly don't think introducing 42 lines to every generated wrapper class, most of which is hand-written copies of generated code doing argument validation, is a good way to do it. Especially since I don't see a reason for that argument validation.

@Sebmaster
Copy link
Member

Sebmaster commented Feb 16, 2020

Agreed. Maybe we can add an (optional) helper to jsdom that adds these post-fact instead?

@domenic
Copy link
Member

domenic commented Feb 16, 2020

I guess that's a bit harder now that the prototypes are all generated per-window ^_^

@ExE-Boss ExE-Boss changed the title feat: Add support for Node’s util.inspect.custom symbol method feat: Add support for Node’s [util.inspect.custom] symbol method Feb 16, 2020
@TimothyGu TimothyGu closed this Mar 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants