d70d54233f
Adding full timestamp types. Fixes #71
629 lines
19 KiB
Go
629 lines
19 KiB
Go
// Package fixup modifies/alters/fixes and adds to the low level type
|
|
// definitions for the Chrome Debugging Protocol domains, as generated from
|
|
// protocol.json.
|
|
//
|
|
// The goal of package fixup is to fix the issues associated with generating Go
|
|
// code from the existing Chrome domain definitions, and is wrapped up in one
|
|
// high-level func, FixDomains.
|
|
//
|
|
// Currently, FixDomains will do the following:
|
|
// - add 'Inspector.MethodType' type as a string enumeration of all the event/command names.
|
|
// - add 'Inspector.MessageError' type as a object with code (integer), and message (string).
|
|
// - add 'Inspector.Message' type as a object with id (integer), method (MethodType), params (interface{}), error (MessageError).
|
|
// - add 'Inspector.DetachReason' type and change event 'Inspector.detached''s parameter reason's type.
|
|
// - add 'Inspector.ErrorType' type.
|
|
// - change type of Network.Timestamp and Runtime.Timestamp to internal Timestamp type.
|
|
// - add 'Page.Bootstamp' type and convert all command/event parameters named
|
|
// 'timestamp' in the Page domain to the Bootstamp type.
|
|
// - change any object property or command/event parameter named 'timestamp'
|
|
// that doesn't have a $ref defined to 'Network.Timestamp'.
|
|
// - convert object properties and event/command parameters that are enums into independent types.
|
|
// - change '*.modifiers' parameters to type Input.Modifier.
|
|
// - add 'DOM.NodeType' type and convert "nodeType" parameters to it.
|
|
// - change Page.Frame.id/parentID to FrameID type.
|
|
// - add additional properties to 'Page.Frame' and 'DOM.Node' for use by higher level packages.
|
|
// - add special unmarshaler to NodeId, BackendNodeId, FrameId to handle
|
|
// values from older (v1.1) protocol versions. -- NOTE: this might need to be
|
|
// applied to more types, such as network.LoaderId
|
|
// - rename 'Input.GestureSourceType' -> 'Input.GestureType'.
|
|
// - rename CSS.CSS* types.
|
|
// - add Error() method to 'Runtime.ExceptionDetails' type so that it can be used as error.
|
|
// - change 'Network.Headers' type to map[string]interface{}.
|
|
//
|
|
// Please note that the above is not an exhaustive list of all modifications
|
|
// applied to the domains, however it does attempt to give a comprehensive
|
|
// overview of the most important changes to the definition vs the vanilla
|
|
// specification.
|
|
package fixup
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/knq/chromedp/cmd/chromedp-gen/internal"
|
|
"github.com/knq/chromedp/cmd/chromedp-gen/templates"
|
|
)
|
|
|
|
func setup() {
|
|
types := map[string]bool{
|
|
"DOM.BackendNodeId": true,
|
|
"DOM.BackendNode": true,
|
|
"DOM.NodeId": true,
|
|
"DOM.Node": true,
|
|
"DOM.NodeType": true,
|
|
"DOM.PseudoType": true,
|
|
"DOM.RGBA": true,
|
|
"DOM.ShadowRootType": true,
|
|
"Inspector.ErrorType": true,
|
|
"Inspector.MessageError": true,
|
|
"Inspector.Message": true,
|
|
"Inspector.MethodType": true,
|
|
"Network.LoaderId": true,
|
|
"Network.Timestamp": true,
|
|
"Page.FrameId": true,
|
|
"Page.Frame": true,
|
|
}
|
|
|
|
if *internal.FlagRedirect {
|
|
types["Network.Cookie"] = true
|
|
types["Network.CookieSameSite"] = true
|
|
types["Page.ResourceType"] = true
|
|
}
|
|
|
|
// set the cdp types
|
|
internal.SetCDPTypes(types)
|
|
}
|
|
|
|
// Specific type names to use for the applied fixes to the protocol domains.
|
|
//
|
|
// These need to be here in case the location of these types change (see above)
|
|
// relative to the generated 'cdp' package.
|
|
const (
|
|
domNodeIDRef = "NodeID"
|
|
domNodeRef = "*Node"
|
|
)
|
|
|
|
// FixDomains modifies, updates, alters, fixes, and adds to the types defined
|
|
// in the domains, so that the generated Chrome Debugging Protocol domain code
|
|
// is more Go-like and easier to use.
|
|
//
|
|
// Please see package-level documentation for the list of changes made to the
|
|
// various debugging protocol domains.
|
|
func FixDomains(domains []*internal.Domain) {
|
|
// set up the internal types
|
|
setup()
|
|
|
|
// method type
|
|
methodType := &internal.Type{
|
|
ID: "MethodType",
|
|
Type: internal.TypeString,
|
|
Description: "Chrome Debugging Protocol method type (ie, event and command names).",
|
|
EnumValueNameMap: make(map[string]string),
|
|
Extra: templates.ExtraMethodTypeDomainDecoder(),
|
|
}
|
|
|
|
// message error type
|
|
messageErrorType := &internal.Type{
|
|
ID: "MessageError",
|
|
Type: internal.TypeObject,
|
|
Description: "Message error type.",
|
|
Properties: []*internal.Type{
|
|
{
|
|
Name: "code",
|
|
Type: internal.TypeInteger,
|
|
Description: "Error code.",
|
|
},
|
|
{
|
|
Name: "message",
|
|
Type: internal.TypeString,
|
|
Description: "Error message.",
|
|
},
|
|
},
|
|
Extra: "// Error satisfies error interface.\nfunc (e *MessageError) Error() string {\nreturn fmt.Sprintf(\"%s (%d)\", e.Message, e.Code)\n}\n",
|
|
}
|
|
|
|
// message type
|
|
messageType := &internal.Type{
|
|
ID: "Message",
|
|
Type: internal.TypeObject,
|
|
Description: "Chrome Debugging Protocol message sent to/read over websocket connection.",
|
|
Properties: []*internal.Type{
|
|
{
|
|
Name: "id",
|
|
Type: internal.TypeInteger,
|
|
Description: "Unique message identifier.",
|
|
},
|
|
{
|
|
Name: "method",
|
|
Ref: "Inspector.MethodType",
|
|
Description: "Event or command type.",
|
|
},
|
|
{
|
|
Name: "params",
|
|
Type: internal.TypeAny,
|
|
Description: "Event or command parameters.",
|
|
},
|
|
{
|
|
Name: "result",
|
|
Type: internal.TypeAny,
|
|
Description: "Command return values.",
|
|
},
|
|
{
|
|
Name: "error",
|
|
Ref: "MessageError",
|
|
Description: "Error message.",
|
|
},
|
|
},
|
|
}
|
|
|
|
// detach reason type
|
|
detachReasonType := &internal.Type{
|
|
ID: "DetachReason",
|
|
Type: internal.TypeString,
|
|
Enum: []string{"target_closed", "canceled_by_user", "replaced_with_devtools", "Render process gone."},
|
|
Description: "Detach reason.",
|
|
}
|
|
|
|
// cdp error types
|
|
errorValues := []string{"channel closed", "invalid result", "unknown result"}
|
|
errorValueNameMap := make(map[string]string)
|
|
for _, e := range errorValues {
|
|
errorValueNameMap[e] = "Err" + internal.ForceCamel(e)
|
|
}
|
|
errorType := &internal.Type{
|
|
ID: "ErrorType",
|
|
Type: internal.TypeString,
|
|
Enum: errorValues,
|
|
EnumValueNameMap: errorValueNameMap,
|
|
Description: "Error type.",
|
|
Extra: templates.ExtraCDPTypes(),
|
|
}
|
|
|
|
// modifier type
|
|
modifierType := &internal.Type{
|
|
ID: "Modifier",
|
|
Type: internal.TypeInteger,
|
|
EnumBitMask: true,
|
|
Description: "Input key modifier type.",
|
|
Enum: []string{"None", "Alt", "Ctrl", "Meta", "Shift"},
|
|
Extra: "// ModifierCommand is an alias for ModifierMeta.\nconst ModifierCommand Modifier = ModifierMeta",
|
|
}
|
|
|
|
// node type type -- see: https://developer.mozilla.org/en/docs/Web/API/Node/nodeType
|
|
nodeTypeType := &internal.Type{
|
|
ID: "NodeType",
|
|
Type: internal.TypeInteger,
|
|
Description: "Node type.",
|
|
Enum: []string{
|
|
"Element", "Attribute", "Text", "CDATA", "EntityReference",
|
|
"Entity", "ProcessingInstruction", "Comment", "Document",
|
|
"DocumentType", "DocumentFragment", "Notation",
|
|
},
|
|
}
|
|
|
|
// bootstamp type
|
|
bootstampType := &internal.Type{
|
|
ID: "Bootstamp",
|
|
Type: internal.TypeTimestamp,
|
|
TimestampType: internal.TimestampTypeBootstamp,
|
|
Description: "Bootstamp type.",
|
|
}
|
|
|
|
// process domains
|
|
for _, d := range domains {
|
|
switch d.Domain {
|
|
case internal.DomainInspector:
|
|
// add Inspector types
|
|
d.Types = append(d.Types, messageErrorType, messageType, methodType, detachReasonType, errorType)
|
|
|
|
// find detached event's reason parameter and change type
|
|
for _, e := range d.Events {
|
|
if e.Name == "detached" {
|
|
for _, t := range e.Parameters {
|
|
if t.Name == "reason" {
|
|
t.Ref = "DetachReason"
|
|
t.Type = internal.TypeEnum("")
|
|
break
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
case internal.DomainCSS:
|
|
for _, t := range d.Types {
|
|
if t.ID == "CSSComputedStyleProperty" {
|
|
t.ID = "ComputedProperty"
|
|
}
|
|
}
|
|
|
|
case internal.DomainInput:
|
|
// add Input types
|
|
d.Types = append(d.Types, modifierType)
|
|
for _, t := range d.Types {
|
|
if t.ID == "GestureSourceType" {
|
|
t.ID = "GestureType"
|
|
}
|
|
}
|
|
|
|
case internal.DomainDOM:
|
|
// add DOM types
|
|
d.Types = append(d.Types, nodeTypeType)
|
|
|
|
for _, t := range d.Types {
|
|
switch t.ID {
|
|
case "NodeId", "BackendNodeId":
|
|
t.Extra += templates.ExtraFixStringUnmarshaler(internal.ForceCamel(t.ID), "ParseInt", ", 10, 64")
|
|
|
|
case "Node":
|
|
t.Properties = append(t.Properties,
|
|
&internal.Type{
|
|
Name: "Parent",
|
|
Ref: domNodeRef,
|
|
Description: "Parent node.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "Invalidated",
|
|
Ref: "chan struct{}",
|
|
Description: "Invalidated channel.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "State",
|
|
Ref: "NodeState",
|
|
Description: "Node state.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "",
|
|
Ref: "sync.RWMutex",
|
|
Description: "Read write mutex.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
)
|
|
t.Extra += templates.ExtraNodeTemplate()
|
|
}
|
|
}
|
|
|
|
case internal.DomainPage:
|
|
// add Page types
|
|
d.Types = append(d.Types, bootstampType)
|
|
|
|
for _, t := range d.Types {
|
|
switch t.ID {
|
|
case "Bootstamp":
|
|
t.Extra += templates.ExtraTimestampTemplate(t, d)
|
|
|
|
case "FrameId":
|
|
t.Extra += templates.ExtraFixStringUnmarshaler(internal.ForceCamel(t.ID), "", "")
|
|
|
|
case "Frame":
|
|
t.Properties = append(t.Properties,
|
|
&internal.Type{
|
|
Name: "State",
|
|
Ref: "FrameState",
|
|
Description: "Frame state.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "Root",
|
|
Ref: domNodeRef,
|
|
Description: "Frame document root.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "Nodes",
|
|
Ref: "map[" + domNodeIDRef + "]" + domNodeRef,
|
|
Description: "Frame nodes.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
&internal.Type{
|
|
Name: "",
|
|
Ref: "sync.RWMutex",
|
|
Description: "Read write mutex.",
|
|
NoResolve: true,
|
|
NoExpose: true,
|
|
},
|
|
)
|
|
t.Extra += templates.ExtraFrameTemplate()
|
|
|
|
// convert Frame.id/parentId to $ref of FrameID
|
|
for _, p := range t.Properties {
|
|
if p.Name == "id" || p.Name == "parentId" {
|
|
p.Ref = "FrameId"
|
|
p.Type = internal.TypeEnum("")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case internal.DomainNetwork:
|
|
for _, t := range d.Types {
|
|
// change Timestamp to TypeTimestamp and add extra unmarshaling template
|
|
if t.ID == "Timestamp" {
|
|
t.Type = internal.TypeTimestamp
|
|
t.TimestampType = internal.TimestampTypeSecond
|
|
t.Extra = templates.ExtraTimestampTemplate(t, d)
|
|
}
|
|
|
|
// change Headers to be a map[string]interface{}
|
|
if t.ID == "Headers" {
|
|
t.Type = internal.TypeAny
|
|
t.Ref = "map[string]interface{}"
|
|
}
|
|
}
|
|
|
|
case internal.DomainRuntime:
|
|
var types []*internal.Type
|
|
for _, t := range d.Types {
|
|
switch t.ID {
|
|
case "Timestamp":
|
|
t.Type = internal.TypeTimestamp
|
|
t.TimestampType = internal.TimestampTypeMillisecond
|
|
t.Extra += templates.ExtraTimestampTemplate(t, d)
|
|
|
|
case "ExceptionDetails":
|
|
t.Extra += templates.ExtraExceptionDetailsTemplate()
|
|
}
|
|
|
|
types = append(types, t)
|
|
}
|
|
d.Types = types
|
|
}
|
|
|
|
for _, t := range d.Types {
|
|
// convert object properties
|
|
if t.Properties != nil {
|
|
t.Properties = convertObjectProperties(t.Properties, d, t.ID)
|
|
}
|
|
}
|
|
|
|
// process events and commands
|
|
processTypesWithParameters(methodType, d, d.Events, internal.EventMethodPrefix, internal.EventMethodSuffix)
|
|
processTypesWithParameters(methodType, d, d.Commands, internal.CommandMethodPrefix, internal.CommandMethodSuffix)
|
|
|
|
// fix input enums
|
|
if d.Domain == internal.DomainInput {
|
|
for _, t := range d.Types {
|
|
if t.Enum != nil && t.ID != "Modifier" {
|
|
t.EnumValueNameMap = make(map[string]string)
|
|
for _, v := range t.Enum {
|
|
prefix := ""
|
|
switch t.ID {
|
|
case "GestureType":
|
|
prefix = "Gesture"
|
|
case "ButtonType":
|
|
prefix = "Button"
|
|
}
|
|
n := prefix + internal.ForceCamel(v)
|
|
if t.ID == "KeyType" {
|
|
n = "Key" + strings.Replace(n, "Key", "", -1)
|
|
}
|
|
t.EnumValueNameMap[v] = strings.Replace(n, "Cancell", "Cancel", -1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, t := range d.Types {
|
|
// fix type stuttering
|
|
if !t.NoExpose && !t.NoResolve {
|
|
id := strings.TrimPrefix(t.ID, d.Domain.String())
|
|
if id == "" {
|
|
continue
|
|
}
|
|
|
|
t.ID = id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// processTypesWithParameters adds the types to t's enum values, setting the
|
|
// enum value map for m. Also, converts the Parameters and Returns properties.
|
|
func processTypesWithParameters(m *internal.Type, d *internal.Domain, types []*internal.Type, prefix, suffix string) {
|
|
for _, t := range types {
|
|
n := t.ProtoName(d)
|
|
m.Enum = append(m.Enum, n)
|
|
m.EnumValueNameMap[n] = t.TypeName(prefix+d.String(), suffix)
|
|
|
|
t.Parameters = convertObjectProperties(t.Parameters, d, t.Name)
|
|
if t.Returns != nil {
|
|
t.Returns = convertObjectProperties(t.Returns, d, t.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// convertObjectProperties converts object properties.
|
|
func convertObjectProperties(params []*internal.Type, d *internal.Domain, name string) []*internal.Type {
|
|
r := make([]*internal.Type, 0)
|
|
for _, p := range params {
|
|
switch {
|
|
case p.Items != nil:
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Type: internal.TypeArray,
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
Items: convertObjectProperties([]*internal.Type{p.Items}, d, name+"."+p.Name)[0],
|
|
})
|
|
|
|
case p.Enum != nil:
|
|
r = append(r, fixupEnumParameter(name, p, d))
|
|
|
|
case p.Name == "timestamp" && p.Ref == "" && d.Domain == internal.DomainPage:
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "Page.Bootstamp",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Name == "timestamp" && p.Ref == "":
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "Network.Timestamp",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Name == "modifiers":
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "Modifier",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Name == "nodeType":
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "DOM.NodeType",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Ref == "GestureSourceType":
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "GestureType",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Ref == "CSSComputedStyleProperty":
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: "ComputedProperty",
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
case p.Ref != "" && !p.NoExpose && !p.NoResolve:
|
|
ref := strings.SplitN(p.Ref, ".", 2)
|
|
if len(ref) == 1 {
|
|
ref[0] = strings.TrimPrefix(ref[0], d.Domain.String())
|
|
} else {
|
|
ref[1] = strings.TrimPrefix(ref[1], ref[0])
|
|
}
|
|
|
|
z := strings.Join(ref, ".")
|
|
if z == "" {
|
|
z = p.Ref
|
|
}
|
|
|
|
r = append(r, &internal.Type{
|
|
Name: p.Name,
|
|
Ref: z,
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
})
|
|
|
|
default:
|
|
r = append(r, p)
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// addEnumValues adds orig.Enum values to type named n's Enum values in domain.
|
|
func addEnumValues(d *internal.Domain, n string, p *internal.Type) {
|
|
// find type
|
|
var typ *internal.Type
|
|
for _, t := range d.Types {
|
|
if t.ID == n {
|
|
typ = t
|
|
break
|
|
}
|
|
}
|
|
if typ == nil {
|
|
typ = &internal.Type{
|
|
ID: n,
|
|
Type: internal.TypeString,
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
}
|
|
d.Types = append(d.Types, typ)
|
|
}
|
|
|
|
// combine typ.Enum and vals
|
|
v := make(map[string]bool)
|
|
all := append(typ.Enum, p.Enum...)
|
|
for _, z := range all {
|
|
v[z] = false
|
|
}
|
|
|
|
var i int
|
|
typ.Enum = make([]string, len(v))
|
|
for _, z := range all {
|
|
if !v[z] {
|
|
typ.Enum[i] = z
|
|
i++
|
|
}
|
|
v[z] = true
|
|
}
|
|
}
|
|
|
|
// enumRefMap is the fully qualified parameter name to ref.
|
|
var enumRefMap = map[string]string{
|
|
"Animation.Animation.type": "Type",
|
|
"Console.ConsoleMessage.level": "MessageLevel",
|
|
"Console.ConsoleMessage.source": "MessageSource",
|
|
"CSS.CSSMedia.source": "MediaSource",
|
|
"CSS.forcePseudoState.forcedPseudoClasses": "PseudoClass",
|
|
"Debugger.setPauseOnExceptions.state": "ExceptionsState",
|
|
"Emulation.ScreenOrientation.type": "OrientationType",
|
|
"Emulation.setTouchEmulationEnabled.configuration": "EnabledConfiguration",
|
|
"Input.dispatchKeyEvent.type": "KeyType",
|
|
"Input.dispatchMouseEvent.button": "ButtonType",
|
|
"Input.dispatchMouseEvent.type": "MouseType",
|
|
"Input.dispatchTouchEvent.type": "TouchType",
|
|
"Input.emulateTouchFromMouseEvent.button": "ButtonType",
|
|
"Input.emulateTouchFromMouseEvent.type": "MouseType",
|
|
"Input.TouchPoint.state": "TouchState",
|
|
"Log.LogEntry.level": "Level",
|
|
"Log.LogEntry.source": "Source",
|
|
"Log.ViolationSetting.name": "Violation",
|
|
"Network.Request.mixedContentType": "MixedContentType",
|
|
"Network.Request.referrerPolicy": "ReferrerPolicy",
|
|
"Page.startScreencast.format": "ScreencastFormat",
|
|
"Runtime.consoleAPICalled.type": "APIType",
|
|
"Runtime.ObjectPreview.subtype": "Subtype",
|
|
"Runtime.ObjectPreview.type": "Type",
|
|
"Runtime.PropertyPreview.subtype": "Subtype",
|
|
"Runtime.PropertyPreview.type": "Type",
|
|
"Runtime.RemoteObject.subtype": "Subtype",
|
|
"Runtime.RemoteObject.type": "Type",
|
|
"Tracing.start.transferMode": "TransferMode",
|
|
"Tracing.TraceConfig.recordMode": "RecordMode",
|
|
}
|
|
|
|
// fixupEnumParameter takes an enum parameter, adds it to the domain and
|
|
// returns a type suitable for use in place of the type.
|
|
func fixupEnumParameter(typ string, p *internal.Type, d *internal.Domain) *internal.Type {
|
|
fqname := strings.TrimSuffix(fmt.Sprintf("%s.%s.%s", d.Domain, typ, p.Name), ".")
|
|
ref := internal.ForceCamel(typ + "." + p.Name)
|
|
if n, ok := enumRefMap[fqname]; ok {
|
|
ref = n
|
|
}
|
|
|
|
// add enum values to type name
|
|
addEnumValues(d, ref, p)
|
|
|
|
return &internal.Type{
|
|
Name: p.Name,
|
|
Ref: ref,
|
|
Description: p.Description,
|
|
Optional: p.Optional,
|
|
}
|
|
}
|