Version 15 (modified by 9 years ago) ( diff ) | ,
---|
These are JavaScript coding styles used in the Source/WebInspectorUI/UserInterface folder.
(Note: Few if any of these guidelines are checked by check-webkit-style
. There's a tracking bug for that: https://bugs.webkit.org/show_bug.cgi?id=125045)
Tokens, spacing, indentation, syntax
- No trailing whitespace
- Indent with 4 spaces
- The opening bracket
'{'
after a named, non-inlined function goes on the next line. Anywhere else, the opening bracket'{'
stays on the same line. - Style for object literals is:
{key1: value1, key2: value2}
. - Add new lines before and after different tasks performed in the same function.
- Else-blocks should share a line with leading } or }).
- Long promise chains should place
.then()
blocks on a new line. - Calling a constructor with no arguments should have no parenthesis
'()'
. eg.var map = new Map;
- Inline anonymous functions, especially if they don't need a bound
this
-object (using.bind()
). Example:this.requestRootDOMNode(function(rootNode) { ... });
Naming things
- Avoid using the "on" prefix where possible. The
_onFoo
methods can just be_foo
or_handleFoo
. - New class names should use the name of the base class as a suffix. (ex:
TimelinesContentView
<ContentView
). Exceptions: classes extendingWebInspector.Object
(unless they are a represented object), and deep hierarchies such asDebuggerSidebarPanel
<NavigationSidebarPanel
<SidebarPanel
<Object
. - Spell out
identifier
instead ofid
if not doing so would result in a name ending with capitalizedId
. For example, justthis.id
is fine, butthis.breakpointId
should bethis.breakpointIdentifier
. - An object's events live on the
Event
property of the constructor. Event names are properties on theEvent
object, and property values duplicate the event name, but are lowercased, hyphenated, and prefixed with the constructor name. See the skeleton example below.
API preferences
- Consider using `Map` and `Set` collections instead of plain objects.
- Consider using `hsla()' over hex or RGB colors in CSS.
- Use
for..of
syntax when performing small actions on each element. UseforEach
when the function body is longer. Use a classical for loop when doing index math. - When using
forEach
ormap
, supply thethis
-object as the optional second parameter rather than binding it. - In promise chains, assign
const instance = this;' and capture a reference to
instance` to avoid noisy calls to Function.prototype.bind().
Layering and abstractions
- Firewall the protocol inside the Manager classes. JSON objects received from the protocol are called "payload" in the code. The payload is usually deconstructed at the Managers level and passes down as smart objects inheriting from
WebInspector.Object
. - Avoid accessing *View classes from *Manager or *Object classes. This is a layering violation that prevents writing tests for models.
- Avoid storing DOM elements in *Manager or *Object classes. (see above.)
- Avoid using Inspector TypeBuilders outside of InspectorAgent classes. We want to isolate protocol considerations from other functionality in JavaScriptCore and WebCore.
Understanding and Using Promises
What's so great about Promises? The point of promises is to give us back functional composition and error bubbling in the async world. They do this by saying that your functions should return a promise, which can do one of two things:
- Become fulfilled by a value
- Become rejected with an Error instance or by throwing an exception
And, if you have a correctly implemented then()
function, then fulfillment and rejection will compose just like their synchronous counterparts, with fulfillments flowing up a compositional chain, but being interrupted at any time by a rejection that is only handled by someone who declares they are ready to handle it.
Promise Gotchas
(Summarized from change.org Blog and The Art of Code Blog)
- Don't nest promises to perform multiple async operations; instead, chain them or use
Promise.all()
. - Beware of storing or returning promise values that are not from the end of a chain. Each
.then()
returns a new promise value, so return the last promise. - Use
Promise.all()
withmap()
to process an array of asynchronous work in parallel. UsePromise.all()
withreduce()
to sequence an array asynchronous work. - If a result may be a promise or an actual value, wrap the value in a promise, e.g.,
Promise.resolve(val)
- Use
.catch()
at the end of a chain to perform error handling. Most promise chains should have a catch block to avoid dropping errors. - To reject a promise, throw an
Error
instance or call thereject
callback with anError
instance. - A
.catch()
block is considered resolved if it does not re-throw anError
instance. Re-throw if you want to log an error message and allow other parts of a chain (i.e, an API client) to handle an error condition. - Don't directly pass a promise's
resolve
function toObject.addEventListener
, as it will leak the promise if the event never fires. Instead, use a single-fireWebInspector.EventListener
object defined outside of the promise chain and connect it inside a.then()
body. Inside the.catch
block, disconnect theEventListener
if necessary. - For APIs that return promises, document what the fulfilled value will be, if any. Example:
createSession() // --> (sessionId)
New class skeleton
New Inspector object classes use ES6 class syntax and should have the following format:
WebInspector.NewObjectType = class NewObjectType extends WebInspector.Object { constructor(param) { super(); this._propertyName = param; } // Public get propertyName() { return this._propertyName; } set propertyName(value) { this._propertyName = value; this.dispatchEventToListeners(WebInspector.NewObjectType.Event.PropertyWasChanged); } publicMethod: function() { /* public methods called outside the class */ } // Protected handleEvent: function(event) { /* delegate methods, event handlers, and overrides. */ } // Private _privateMethod: function() { /* private methods are underscore prefixed */ } }; WebInspector.NewObjectType.Event = { PropertyWasChanged: "new-object-type-property-was-changed" };
Old class skeleton
Some existing Inspector object classes have not been converted to ES6 classes. The should conform to the following format:
WebInspector.NewObjectType = function() { WebInspector.Object.call(this); this._propertyName = ...; } WebInspector.NewObjectType.Event = { PropertyWasChanged: "new-object-type-property-was-changed" }; WebInspector.NewObjectType.prototype = { constructor: WebInspector.NewObjectType, __proto__: WebInspector.Object.prototype, // Public get propertyName() { return this._propertyName; }, set propertyName(value) { this._propertyName = value; this.dispatchEventToListeners(WebInspector.NewObjectType.Event.PropertyWasChanged); }, publicMethod: function() { /* public methods called outside the class */ } // Protected handleEvent: function(event) { /* delegate methods, event handlers, and overrides. */ }, // Private _privateMethod: function() { /* private methods are underscore prefixed */ } };