Addition of chromedp-proxy, fixing SendKeys, more

- Adding chromedp-proxy command to help record chrome sessions
- Changing the headless writeup to point to the knq chrome-headless
  docker image
- Fixing issues with SendKeys action (should correctly work for all
  well known keys)
- Updated to latest Chrome protocol.json and re-generated API
- Other minor fixes
This commit is contained in:
Kenneth Shaw 2017-01-29 10:37:56 +07:00
parent 7421a99dcc
commit 4145d8367d
25 changed files with 2173 additions and 508 deletions

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
out.txt
out*.txt
old*.txt
cdp-*.log
cdp-*.txt
# binaries
/chromedp-gen

View File

@ -265,10 +265,12 @@ func (t AXGlobalStates) String() string {
// AXGlobalStates values.
const (
AXGlobalStatesDisabled AXGlobalStates = "disabled"
AXGlobalStatesHidden AXGlobalStates = "hidden"
AXGlobalStatesHiddenRoot AXGlobalStates = "hiddenRoot"
AXGlobalStatesInvalid AXGlobalStates = "invalid"
AXGlobalStatesDisabled AXGlobalStates = "disabled"
AXGlobalStatesHidden AXGlobalStates = "hidden"
AXGlobalStatesHiddenRoot AXGlobalStates = "hiddenRoot"
AXGlobalStatesInvalid AXGlobalStates = "invalid"
AXGlobalStatesKeyshortcuts AXGlobalStates = "keyshortcuts"
AXGlobalStatesRoledescription AXGlobalStates = "roledescription"
)
// MarshalEasyJSON satisfies easyjson.Marshaler.
@ -292,6 +294,10 @@ func (t *AXGlobalStates) UnmarshalEasyJSON(in *jlexer.Lexer) {
*t = AXGlobalStatesHiddenRoot
case AXGlobalStatesInvalid:
*t = AXGlobalStatesInvalid
case AXGlobalStatesKeyshortcuts:
*t = AXGlobalStatesKeyshortcuts
case AXGlobalStatesRoledescription:
*t = AXGlobalStatesRoledescription
default:
in.AddError(errors.New("unknown AXGlobalStates value"))
@ -435,6 +441,7 @@ func (t AXWidgetStates) String() string {
const (
AXWidgetStatesChecked AXWidgetStates = "checked"
AXWidgetStatesExpanded AXWidgetStates = "expanded"
AXWidgetStatesModal AXWidgetStates = "modal"
AXWidgetStatesPressed AXWidgetStates = "pressed"
AXWidgetStatesSelected AXWidgetStates = "selected"
)
@ -456,6 +463,8 @@ func (t *AXWidgetStates) UnmarshalEasyJSON(in *jlexer.Lexer) {
*t = AXWidgetStatesChecked
case AXWidgetStatesExpanded:
*t = AXWidgetStatesExpanded
case AXWidgetStatesModal:
*t = AXWidgetStatesModal
case AXWidgetStatesPressed:
*t = AXWidgetStatesPressed
case AXWidgetStatesSelected:
@ -483,9 +492,11 @@ func (t AXRelationshipAttributes) String() string {
// AXRelationshipAttributes values.
const (
AXRelationshipAttributesActivedescendant AXRelationshipAttributes = "activedescendant"
AXRelationshipAttributesFlowto AXRelationshipAttributes = "flowto"
AXRelationshipAttributesControls AXRelationshipAttributes = "controls"
AXRelationshipAttributesDescribedby AXRelationshipAttributes = "describedby"
AXRelationshipAttributesDetails AXRelationshipAttributes = "details"
AXRelationshipAttributesErrormessage AXRelationshipAttributes = "errormessage"
AXRelationshipAttributesFlowto AXRelationshipAttributes = "flowto"
AXRelationshipAttributesLabelledby AXRelationshipAttributes = "labelledby"
AXRelationshipAttributesOwns AXRelationshipAttributes = "owns"
)
@ -505,12 +516,16 @@ func (t *AXRelationshipAttributes) UnmarshalEasyJSON(in *jlexer.Lexer) {
switch AXRelationshipAttributes(in.String()) {
case AXRelationshipAttributesActivedescendant:
*t = AXRelationshipAttributesActivedescendant
case AXRelationshipAttributesFlowto:
*t = AXRelationshipAttributesFlowto
case AXRelationshipAttributesControls:
*t = AXRelationshipAttributesControls
case AXRelationshipAttributesDescribedby:
*t = AXRelationshipAttributesDescribedby
case AXRelationshipAttributesDetails:
*t = AXRelationshipAttributesDetails
case AXRelationshipAttributesErrormessage:
*t = AXRelationshipAttributesErrormessage
case AXRelationshipAttributesFlowto:
*t = AXRelationshipAttributesFlowto
case AXRelationshipAttributesLabelledby:
*t = AXRelationshipAttributesLabelledby
case AXRelationshipAttributesOwns:

View File

@ -214,6 +214,7 @@ const (
CommandDOMEnable MethodType = "DOM.enable"
CommandDOMDisable MethodType = "DOM.disable"
CommandDOMGetDocument MethodType = "DOM.getDocument"
CommandDOMGetFlattenedDocument MethodType = "DOM.getFlattenedDocument"
CommandDOMCollectClassNamesFromSubtree MethodType = "DOM.collectClassNamesFromSubtree"
CommandDOMRequestChildNodes MethodType = "DOM.requestChildNodes"
CommandDOMQuerySelector MethodType = "DOM.querySelector"
@ -794,6 +795,8 @@ func (t *MethodType) UnmarshalEasyJSON(in *jlexer.Lexer) {
*t = CommandDOMDisable
case CommandDOMGetDocument:
*t = CommandDOMGetDocument
case CommandDOMGetFlattenedDocument:
*t = CommandDOMGetFlattenedDocument
case CommandDOMCollectClassNamesFromSubtree:
*t = CommandDOMCollectClassNamesFromSubtree
case CommandDOMRequestChildNodes:
@ -1651,6 +1654,7 @@ func (t *ShadowRootType) UnmarshalJSON(buf []byte) error {
// represent the actual DOM nodes. DOMNode is a base node mirror type.
type Node struct {
NodeID NodeID `json:"nodeId,omitempty"` // Node identifier that is passed into the rest of the DOM messages as the nodeId. Backend will only push node with given id once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.
ParentID NodeID `json:"parentId,omitempty"` // The id of the parent node if any.
BackendNodeID BackendNodeID `json:"backendNodeId,omitempty"` // The BackendNodeId for this node.
NodeType NodeType `json:"nodeType,omitempty"` // Node's nodeType.
NodeName string `json:"nodeName,omitempty"` // Node's nodeName.
@ -1698,7 +1702,7 @@ func (n *Node) AttributeValue(name string) string {
}
// xpath builds the xpath string.
func (n *Node) xpath(stopWhenID bool) string {
func (n *Node) xpath(stopAtDocument, stopAtID bool) string {
p := ""
pos := ""
id := n.AttributeValue("id")
@ -1706,7 +1710,10 @@ func (n *Node) xpath(stopWhenID bool) string {
case n.Parent == nil:
return n.LocalName
case stopWhenID && id != "":
case stopAtDocument && n.NodeType == NodeTypeDocument:
return ""
case stopAtID && id != "":
p = "/"
pos = `[@id='` + id + `']`
@ -1723,7 +1730,7 @@ func (n *Node) xpath(stopWhenID bool) string {
}
}
p = n.Parent.xpath(stopWhenID)
p = n.Parent.xpath(stopAtDocument, stopAtID)
if found {
pos = "[" + strconv.Itoa(i) + "]"
}
@ -1732,15 +1739,28 @@ func (n *Node) xpath(stopWhenID bool) string {
return p + "/" + n.LocalName + pos
}
// XPathByID returns the XPath tree for the node, stopping at the first parent
// with an id attribute.
func (n *Node) XPathByID() string {
return n.xpath(true)
// PartialXPathByID returns the partial XPath for the node, stopping at the
// first parent with an id attribute or at nearest parent document node.
func (n *Node) PartialXPathByID() string {
return n.xpath(true, true)
}
// XPath returns the full XPath tree for the node.
func (n *Node) XPath() string {
return n.xpath(false)
// PartialXPath returns the partial XPath for the node, stopping at the nearest
// parent document node.
func (n *Node) PartialXPath() string {
return n.xpath(true, false)
}
// FullXPathByID returns the full XPath for the node, stopping at the top most
// document root or at the closest parent node with an id attribute.
func (n *Node) FullXPathByID() string {
return n.xpath(false, true)
}
// FullXPath returns the full XPath for the node, stopping only at the top most
// document root.
func (n *Node) FullXPath() string {
return n.xpath(false, false)
}
// NodeState is the state of a DOM node.

View File

@ -2798,6 +2798,8 @@ func easyjsonC5a4559bDecodeGithubComKnqChromedpCdpDebugger31(in *jlexer.Lexer, o
out.SourceMapURL = string(in.String())
case "hasSourceURL":
out.HasSourceURL = bool(in.Bool())
case "isModule":
out.IsModule = bool(in.Bool())
default:
in.SkipRecursive()
}
@ -2908,6 +2910,14 @@ func easyjsonC5a4559bEncodeGithubComKnqChromedpCdpDebugger31(out *jwriter.Writer
out.RawString("\"hasSourceURL\":")
out.Bool(bool(in.HasSourceURL))
}
if in.IsModule {
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"isModule\":")
out.Bool(bool(in.IsModule))
}
out.RawByte('}')
}
@ -2975,6 +2985,8 @@ func easyjsonC5a4559bDecodeGithubComKnqChromedpCdpDebugger32(in *jlexer.Lexer, o
out.SourceMapURL = string(in.String())
case "hasSourceURL":
out.HasSourceURL = bool(in.Bool())
case "isModule":
out.IsModule = bool(in.Bool())
default:
in.SkipRecursive()
}
@ -3077,6 +3089,14 @@ func easyjsonC5a4559bEncodeGithubComKnqChromedpCdpDebugger32(out *jwriter.Writer
out.RawString("\"hasSourceURL\":")
out.Bool(bool(in.HasSourceURL))
}
if in.IsModule {
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"isModule\":")
out.Bool(bool(in.IsModule))
}
out.RawByte('}')
}

View File

@ -23,6 +23,7 @@ type EventScriptParsed struct {
IsLiveEdit bool `json:"isLiveEdit,omitempty"` // True, if this script is generated as a result of the live edit operation.
SourceMapURL string `json:"sourceMapURL,omitempty"` // URL of source map associated with script (if any).
HasSourceURL bool `json:"hasSourceURL,omitempty"` // True, if this script has sourceURL.
IsModule bool `json:"isModule,omitempty"` // True, if this script is ES6 module.
}
// EventScriptFailedToParse fired when virtual machine fails to parse the
@ -39,6 +40,7 @@ type EventScriptFailedToParse struct {
ExecutionContextAuxData easyjson.RawMessage `json:"executionContextAuxData,omitempty"`
SourceMapURL string `json:"sourceMapURL,omitempty"` // URL of source map associated with script (if any).
HasSourceURL bool `json:"hasSourceURL,omitempty"` // True, if this script has sourceURL.
IsModule bool `json:"isModule,omitempty"` // True, if this script is ES6 module.
}
// EventBreakpointResolved fired when breakpoint is resolved to an actual

View File

@ -184,6 +184,88 @@ func (p *GetDocumentParams) Do(ctxt context.Context, h cdp.FrameHandler) (root *
return nil, cdp.ErrUnknownResult
}
// GetFlattenedDocumentParams returns the root DOM node (and optionally the
// subtree) to the caller.
type GetFlattenedDocumentParams struct {
Depth int64 `json:"depth,omitempty"` // The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the entire subtree or provide an integer larger than 0.
Pierce bool `json:"pierce,omitempty"` // Whether or not iframes and shadow roots should be traversed when returning the subtree (default is false).
}
// GetFlattenedDocument returns the root DOM node (and optionally the
// subtree) to the caller.
//
// parameters:
func GetFlattenedDocument() *GetFlattenedDocumentParams {
return &GetFlattenedDocumentParams{}
}
// WithDepth the maximum depth at which children should be retrieved,
// defaults to 1. Use -1 for the entire subtree or provide an integer larger
// than 0.
func (p GetFlattenedDocumentParams) WithDepth(depth int64) *GetFlattenedDocumentParams {
p.Depth = depth
return &p
}
// WithPierce whether or not iframes and shadow roots should be traversed
// when returning the subtree (default is false).
func (p GetFlattenedDocumentParams) WithPierce(pierce bool) *GetFlattenedDocumentParams {
p.Pierce = pierce
return &p
}
// GetFlattenedDocumentReturns return values.
type GetFlattenedDocumentReturns struct {
Nodes []*cdp.Node `json:"nodes,omitempty"` // Resulting node.
}
// Do executes DOM.getFlattenedDocument.
//
// returns:
// nodes - Resulting node.
func (p *GetFlattenedDocumentParams) Do(ctxt context.Context, h cdp.FrameHandler) (nodes []*cdp.Node, err error) {
if ctxt == nil {
ctxt = context.Background()
}
// marshal
buf, err := easyjson.Marshal(p)
if err != nil {
return nil, err
}
// execute
ch := h.Execute(ctxt, cdp.CommandDOMGetFlattenedDocument, easyjson.RawMessage(buf))
// read response
select {
case res := <-ch:
if res == nil {
return nil, cdp.ErrChannelClosed
}
switch v := res.(type) {
case easyjson.RawMessage:
// unmarshal
var r GetFlattenedDocumentReturns
err = easyjson.Unmarshal(v, &r)
if err != nil {
return nil, cdp.ErrInvalidResult
}
return r.Nodes, nil
case error:
return nil, v
}
case <-ctxt.Done():
return nil, cdp.ErrContextDone
}
return nil, cdp.ErrUnknownResult
}
// CollectClassNamesFromSubtreeParams collects class names for the node with
// given id and all of it's child nodes.
type CollectClassNamesFromSubtreeParams struct {

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,8 @@ func easyjsonC5a4559bDecodeGithubComKnqChromedpCdp1(in *jlexer.Lexer, out *Node)
switch key {
case "nodeId":
(out.NodeID).UnmarshalEasyJSON(in)
case "parentId":
(out.ParentID).UnmarshalEasyJSON(in)
case "backendNodeId":
(out.BackendNodeID).UnmarshalEasyJSON(in)
case "nodeType":
@ -352,6 +354,14 @@ func easyjsonC5a4559bEncodeGithubComKnqChromedpCdp1(out *jwriter.Writer, in Node
out.RawString("\"nodeId\":")
out.Int64(int64(in.NodeID))
}
if in.ParentID != 0 {
if !first {
out.RawByte(',')
}
first = false
out.RawString("\"parentId\":")
out.Int64(int64(in.ParentID))
}
if in.BackendNodeID != 0 {
if !first {
out.RawByte(',')

View File

@ -493,6 +493,9 @@ func UnmarshalMessage(msg *cdp.Message) (interface{}, error) {
case cdp.CommandDOMGetDocument:
v = new(dom.GetDocumentReturns)
case cdp.CommandDOMGetFlattenedDocument:
v = new(dom.GetFlattenedDocumentReturns)
case cdp.CommandDOMCollectClassNamesFromSubtree:
v = new(dom.CollectClassNamesFromSubtreeReturns)

View File

@ -4328,6 +4328,13 @@
"$ref": "NodeId",
"description": "Node identifier that is passed into the rest of the DOM messages as the <code>nodeId</code>. Backend will only push node with given <code>id</code> once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client."
},
{
"name": "parentId",
"$ref": "NodeId",
"optional": true,
"description": "The id of the parent node if any.",
"experimental": true
},
{
"name": "backendNodeId",
"$ref": "BackendNodeId",
@ -4774,6 +4781,36 @@
],
"description": "Returns the root DOM node (and optionally the subtree) to the caller."
},
{
"name": "getFlattenedDocument",
"parameters": [
{
"name": "depth",
"type": "integer",
"optional": true,
"description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the entire subtree or provide an integer larger than 0.",
"experimental": true
},
{
"name": "pierce",
"type": "boolean",
"optional": true,
"description": "Whether or not iframes and shadow roots should be traversed when returning the subtree (default is false).",
"experimental": true
}
],
"returns": [
{
"name": "nodes",
"type": "array",
"items": {
"$ref": "Node"
},
"description": "Resulting node."
}
],
"description": "Returns the root DOM node (and optionally the subtree) to the caller."
},
{
"name": "collectClassNamesFromSubtree",
"parameters": [
@ -9526,7 +9563,9 @@
"disabled",
"hidden",
"hiddenRoot",
"invalid"
"invalid",
"keyshortcuts",
"roledescription"
],
"description": "States which apply to every AX node."
},
@ -9566,6 +9605,7 @@
"enum": [
"checked",
"expanded",
"modal",
"pressed",
"selected"
],
@ -9576,9 +9616,11 @@
"type": "string",
"enum": [
"activedescendant",
"flowto",
"controls",
"describedby",
"details",
"errormessage",
"flowto",
"labelledby",
"owns"
],
@ -11843,6 +11885,13 @@
"optional": true,
"description": "True, if this script has sourceURL.",
"experimental": true
},
{
"name": "isModule",
"type": "boolean",
"optional": true,
"description": "True, if this script is ES6 module.",
"experimental": true
}
],
"description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger."
@ -11908,6 +11957,13 @@
"optional": true,
"description": "True, if this script has sourceURL.",
"experimental": true
},
{
"name": "isModule",
"type": "boolean",
"optional": true,
"description": "True, if this script is ES6 module.",
"experimental": true
}
],
"description": "Fired when virtual machine fails to parse the script."

View File

@ -82,7 +82,7 @@ func (n *Node) AttributeValue(name string) string {
}
// xpath builds the xpath string.
func (n *Node) xpath(stopWhenID bool) string {
func (n *Node) xpath(stopAtDocument, stopAtID bool) string {
p := ""
pos := ""
id := n.AttributeValue("id")
@ -90,7 +90,10 @@ func (n *Node) xpath(stopWhenID bool) string {
case n.Parent == nil:
return n.LocalName
case stopWhenID && id != "":
case stopAtDocument && n.NodeType == NodeTypeDocument:
return ""
case stopAtID && id != "":
p = "/"
pos = `[@id='`+id+`']`
@ -107,7 +110,7 @@ func (n *Node) xpath(stopWhenID bool) string {
}
}
p = n.Parent.xpath(stopWhenID)
p = n.Parent.xpath(stopAtDocument, stopAtID)
if found {
pos = "["+strconv.Itoa(i)+"]"
}
@ -116,15 +119,28 @@ func (n *Node) xpath(stopWhenID bool) string {
return p + "/" + n.LocalName + pos
}
// XPathByID returns the XPath tree for the node, stopping at the first parent
// with an id attribute.
func (n *Node) XPathByID() string {
return n.xpath(true)
// PartialXPathByID returns the partial XPath for the node, stopping at the
// first parent with an id attribute or at nearest parent document node.
func (n *Node) PartialXPathByID() string {
return n.xpath(true, true)
}
// XPath returns the full XPath tree for the node.
func (n *Node) XPath() string {
return n.xpath(false)
// PartialXPath returns the partial XPath for the node, stopping at the nearest
// parent document node.
func (n *Node) PartialXPath() string {
return n.xpath(true, false)
}
// FullXPathByID returns the full XPath for the node, stopping at the top most
// document root or at the closest parent node with an id attribute.
func (n *Node) FullXPathByID() string {
return n.xpath(false, true)
}
// FullXPath returns the full XPath for the node, stopping only at the top most
// document root.
func (n *Node) FullXPath() string {
return n.xpath(false, false)
}
// NodeState is the state of a DOM node.

View File

@ -191,7 +191,7 @@ func (n *Node) AttributeValue(name string) string {
}
// xpath builds the xpath string.
func (n *Node) xpath(stopWhenID bool) string {
func (n *Node) xpath(stopAtDocument, stopAtID bool) string {
p := ""
pos := ""
id := n.AttributeValue("id")
@ -199,7 +199,10 @@ func (n *Node) xpath(stopWhenID bool) string {
case n.Parent == nil:
return n.LocalName
case stopWhenID && id != "":
case stopAtDocument && n.NodeType == NodeTypeDocument:
return ""
case stopAtID && id != "":
p = "/"
pos = `)
//line templates/extra.qtpl:69
@ -232,7 +235,7 @@ func (n *Node) xpath(stopWhenID bool) string {
}
}
p = n.Parent.xpath(stopWhenID)
p = n.Parent.xpath(stopAtDocument, stopAtID)
if found {
pos = "["+strconv.Itoa(i)+"]"
}
@ -241,15 +244,28 @@ func (n *Node) xpath(stopWhenID bool) string {
return p + "/" + n.LocalName + pos
}
// XPathByID returns the XPath tree for the node, stopping at the first parent
// with an id attribute.
func (n *Node) XPathByID() string {
return n.xpath(true)
// PartialXPathByID returns the partial XPath for the node, stopping at the
// first parent with an id attribute or at nearest parent document node.
func (n *Node) PartialXPathByID() string {
return n.xpath(true, true)
}
// XPath returns the full XPath tree for the node.
func (n *Node) XPath() string {
return n.xpath(false)
// PartialXPath returns the partial XPath for the node, stopping at the nearest
// parent document node.
func (n *Node) PartialXPath() string {
return n.xpath(true, false)
}
// FullXPathByID returns the full XPath for the node, stopping at the top most
// document root or at the closest parent node with an id attribute.
func (n *Node) FullXPathByID() string {
return n.xpath(false, true)
}
// FullXPath returns the full XPath for the node, stopping only at the top most
// document root.
func (n *Node) FullXPath() string {
return n.xpath(false, false)
}
// NodeState is the state of a DOM node.
@ -280,136 +296,136 @@ func (ns NodeState) String() string {
return "[" + strings.Join(s, " ") + "]"
}
`)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
}
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
func WriteExtraNodeTemplate(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
StreamExtraNodeTemplate(qw422016)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
}
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
func ExtraNodeTemplate() string {
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
WriteExtraNodeTemplate(qb422016)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
return qs422016
//line templates/extra.qtpl:157
//line templates/extra.qtpl:173
}
// ExtraFixStringUnmarshaler is a template that forces values to be parsed properly.
//line templates/extra.qtpl:160
//line templates/extra.qtpl:176
func StreamExtraFixStringUnmarshaler(qw422016 *qt422016.Writer, typ, parseFunc, extra string) {
//line templates/extra.qtpl:160
//line templates/extra.qtpl:176
qw422016.N().S(`
// UnmarshalEasyJSON satisfies easyjson.Unmarshaler.
func (t *`)
//line templates/extra.qtpl:162
//line templates/extra.qtpl:178
qw422016.N().S(typ)
//line templates/extra.qtpl:162
//line templates/extra.qtpl:178
qw422016.N().S(`) UnmarshalEasyJSON(in *jlexer.Lexer) {
buf := in.Raw()
if l := len(buf); l > 2 && buf[0] == '"' && buf[l-1] == '"' {
buf = buf[1:l-1]
}
`)
//line templates/extra.qtpl:167
//line templates/extra.qtpl:183
if parseFunc != "" {
//line templates/extra.qtpl:167
//line templates/extra.qtpl:183
qw422016.N().S(`
v, err := strconv.`)
//line templates/extra.qtpl:168
//line templates/extra.qtpl:184
qw422016.N().S(parseFunc)
//line templates/extra.qtpl:168
//line templates/extra.qtpl:184
qw422016.N().S(`(string(buf)`)
//line templates/extra.qtpl:168
//line templates/extra.qtpl:184
qw422016.N().S(extra)
//line templates/extra.qtpl:168
//line templates/extra.qtpl:184
qw422016.N().S(`)
if err != nil {
in.AddError(err)
}
`)
//line templates/extra.qtpl:172
//line templates/extra.qtpl:188
}
//line templates/extra.qtpl:172
//line templates/extra.qtpl:188
qw422016.N().S(`
*t = `)
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
qw422016.N().S(typ)
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
qw422016.N().S(`(`)
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
if parseFunc != "" {
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
qw422016.N().S(`v`)
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
} else {
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
qw422016.N().S(`buf`)
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
}
//line templates/extra.qtpl:173
//line templates/extra.qtpl:189
qw422016.N().S(`)
}
// UnmarshalJSON satisfies json.Unmarshaler.
func (t *`)
//line templates/extra.qtpl:177
//line templates/extra.qtpl:193
qw422016.N().S(typ)
//line templates/extra.qtpl:177
//line templates/extra.qtpl:193
qw422016.N().S(`) UnmarshalJSON(buf []byte) error {
return easyjson.Unmarshal(buf, t)
}
`)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
}
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
func WriteExtraFixStringUnmarshaler(qq422016 qtio422016.Writer, typ, parseFunc, extra string) {
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
StreamExtraFixStringUnmarshaler(qw422016, typ, parseFunc, extra)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
}
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
func ExtraFixStringUnmarshaler(typ, parseFunc, extra string) string {
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
WriteExtraFixStringUnmarshaler(qb422016, typ, parseFunc, extra)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
return qs422016
//line templates/extra.qtpl:180
//line templates/extra.qtpl:196
}
// ExtraCDPTypes is the template for additional internal type
// declarations.
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
func StreamExtraCDPTypes(qw422016 *qt422016.Writer) {
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
qw422016.N().S(`
// Error satisfies the error interface.
@ -432,49 +448,49 @@ type FrameHandler interface {
// Empty is an empty JSON object message.
var Empty = easyjson.RawMessage(`)
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
qw422016.N().S("`")
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
qw422016.N().S(`{}`)
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
qw422016.N().S("`")
//line templates/extra.qtpl:184
//line templates/extra.qtpl:200
qw422016.N().S(`)
`)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
}
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
func WriteExtraCDPTypes(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
StreamExtraCDPTypes(qw422016)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
}
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
func ExtraCDPTypes() string {
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
WriteExtraCDPTypes(qb422016)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
return qs422016
//line templates/extra.qtpl:206
//line templates/extra.qtpl:222
}
// ExtraUtilTemplate generates the decode func for the Message type.
//line templates/extra.qtpl:209
//line templates/extra.qtpl:225
func StreamExtraUtilTemplate(qw422016 *qt422016.Writer, domains []*internal.Domain) {
//line templates/extra.qtpl:209
//line templates/extra.qtpl:225
qw422016.N().S(`
type empty struct{}
var emptyVal = &empty{}
@ -483,66 +499,66 @@ var emptyVal = &empty{}
func UnmarshalMessage(msg *cdp.Message) (interface{}, error) {
var v easyjson.Unmarshaler
switch msg.Method {`)
//line templates/extra.qtpl:216
//line templates/extra.qtpl:232
for _, d := range domains {
//line templates/extra.qtpl:216
//line templates/extra.qtpl:232
for _, c := range d.Commands {
//line templates/extra.qtpl:216
//line templates/extra.qtpl:232
qw422016.N().S(`
case cdp.`)
//line templates/extra.qtpl:217
//line templates/extra.qtpl:233
qw422016.N().S(c.CommandMethodType(d))
//line templates/extra.qtpl:217
//line templates/extra.qtpl:233
qw422016.N().S(`:`)
//line templates/extra.qtpl:217
//line templates/extra.qtpl:233
if len(c.Returns) == 0 {
//line templates/extra.qtpl:217
//line templates/extra.qtpl:233
qw422016.N().S(`
return emptyVal, nil`)
//line templates/extra.qtpl:218
//line templates/extra.qtpl:234
} else {
//line templates/extra.qtpl:218
//line templates/extra.qtpl:234
qw422016.N().S(`
v = new(`)
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
qw422016.N().S(d.PackageRefName())
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
qw422016.N().S(`.`)
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
qw422016.N().S(c.CommandReturnsType())
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
qw422016.N().S(`)`)
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
}
//line templates/extra.qtpl:219
//line templates/extra.qtpl:235
qw422016.N().S(`
`)
//line templates/extra.qtpl:220
//line templates/extra.qtpl:236
}
//line templates/extra.qtpl:220
//line templates/extra.qtpl:236
for _, e := range d.Events {
//line templates/extra.qtpl:220
//line templates/extra.qtpl:236
qw422016.N().S(`
case cdp.`)
//line templates/extra.qtpl:221
//line templates/extra.qtpl:237
qw422016.N().S(e.EventMethodType(d))
//line templates/extra.qtpl:221
//line templates/extra.qtpl:237
qw422016.N().S(`:
v = new(`)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:238
qw422016.N().S(d.PackageRefName())
//line templates/extra.qtpl:222
//line templates/extra.qtpl:238
qw422016.N().S(`.`)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:238
qw422016.N().S(e.EventType())
//line templates/extra.qtpl:222
//line templates/extra.qtpl:238
qw422016.N().S(`)
`)
//line templates/extra.qtpl:223
//line templates/extra.qtpl:239
}
//line templates/extra.qtpl:223
//line templates/extra.qtpl:239
}
//line templates/extra.qtpl:223
//line templates/extra.qtpl:239
qw422016.N().S(`}
var buf easyjson.RawMessage
@ -565,69 +581,69 @@ func UnmarshalMessage(msg *cdp.Message) (interface{}, error) {
return v, nil
}
`)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
}
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
func WriteExtraUtilTemplate(qq422016 qtio422016.Writer, domains []*internal.Domain) {
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
StreamExtraUtilTemplate(qw422016, domains)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
}
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
func ExtraUtilTemplate(domains []*internal.Domain) string {
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
WriteExtraUtilTemplate(qb422016, domains)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
return qs422016
//line templates/extra.qtpl:244
//line templates/extra.qtpl:260
}
//line templates/extra.qtpl:246
//line templates/extra.qtpl:262
func StreamExtraMethodTypeDomainDecoder(qw422016 *qt422016.Writer) {
//line templates/extra.qtpl:246
//line templates/extra.qtpl:262
qw422016.N().S(`
// Domain returns the Chrome Debugging Protocol domain of the event or command.
func (t MethodType) Domain() string {
return string(t[:strings.IndexByte(string(t), '.')])
}
`)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
}
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
func WriteExtraMethodTypeDomainDecoder(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
StreamExtraMethodTypeDomainDecoder(qw422016)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
}
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
func ExtraMethodTypeDomainDecoder() string {
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
WriteExtraMethodTypeDomainDecoder(qb422016)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
return qs422016
//line templates/extra.qtpl:251
//line templates/extra.qtpl:267
}

3
cmd/chromedp-proxy/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
chromedp-proxy
chromedp-proxy.exe
logs/

View File

@ -0,0 +1,57 @@
# About chromedp-proxy
`chromedp-proxy` is a simple cli tool to log/intercept Chrome Debugging
Protocol sessions, notably the websocket messages sent to/from Chrome DevTools
and a Chromium/Chrome/headless_shell/etc. instance.
This is useful for finding problems/issues with the [`chromedp`](https://github.com/knq/chromedp)
package or to review/log/capture Chrome Debugging Protocol commands, command
results, and events sent/received by DevTools, Selenium, or any other
application speaking the Chrome Debugging Protocol.
## Installation
Install in the usual Go way:
```sh
go get -u github.com/knq/chromedp/cmd/chromedp-proxy
```
## Use
By default, `chromedp-proxy` will listen on localhost:9223 and will proxy requests to/from localhost:9222:
```sh
chromedp-proxy
```
`chromedp-proxy` can also be used to expose a local Chrome instance on an
external address/port:
```sh
chromedp-proxy -l 192.168.1.10:9222
```
By default, `chromedp-proxy` will log to both `stdout` and to `logs/cdp-<id>.log`, and can be modified using cli flags:
```sh
# only log to stdout
chromedp-proxy -n
# another way to only log to stdout
chromedp-proxy -log ''
# log to /var/log/cdp/session-<id>.log
chromedp-proxy -log '/var/log/cdp/session-%s.log'
```
## Flags:
```sh
$ ./chromedp-proxy -help
Usage of ./chromedp-proxy:
-l string
listen address (default "localhost:9223")
-log string
log file mask (default "logs/cdp-%s.log")
-n disable logging to file
-r string
remote address (default "localhost:9222")
```

178
cmd/chromedp-proxy/main.go Normal file
View File

@ -0,0 +1,178 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path"
"github.com/gorilla/websocket"
)
var (
flagListen = flag.String("l", "localhost:9223", "listen address")
flagRemote = flag.String("r", "localhost:9222", "remote address")
flagNoLog = flag.Bool("n", false, "disable logging to file")
flagLogMask = flag.String("log", "logs/cdp-%s.log", "log file mask")
)
const (
IncomingBufferSize = 10 * 1024 * 1024
OutgoingBufferSize = 25 * 1024 * 1024
)
var wsUpgrader = &websocket.Upgrader{
ReadBufferSize: IncomingBufferSize,
WriteBufferSize: OutgoingBufferSize,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var wsDialer = &websocket.Dialer{
ReadBufferSize: OutgoingBufferSize,
WriteBufferSize: IncomingBufferSize,
}
func main() {
flag.Parse()
mux := http.NewServeMux()
simplep := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: *flagRemote})
mux.Handle("/json", simplep)
mux.Handle("/", simplep)
mux.HandleFunc("/devtools/page/", func(res http.ResponseWriter, req *http.Request) {
id := path.Base(req.URL.Path)
f, logger := createLog(id)
if f != nil {
defer f.Close()
}
logger.Printf("---------- connection from %s ----------", req.RemoteAddr)
ver, err := checkVersion()
if err != nil {
msg := fmt.Sprintf("version error, got: %v", err)
logger.Println(msg)
http.Error(res, msg, 500)
return
}
logger.Printf("endpoint %s reported: %s", *flagRemote, string(ver))
endpoint := "ws://" + *flagRemote + "/devtools/page/" + id
// connect outgoing websocket
logger.Printf("connecting to %s", endpoint)
out, pres, err := wsDialer.Dial(endpoint, nil)
if err != nil {
msg := fmt.Sprintf("could not connect to %s, got: %v", endpoint, err)
logger.Println(msg)
http.Error(res, msg, 500)
return
}
defer pres.Body.Close()
defer out.Close()
logger.Printf("connected to %s", endpoint)
// connect incoming websocket
logger.Printf("upgrading connection on %s", req.RemoteAddr)
in, err := wsUpgrader.Upgrade(res, req, nil)
if err != nil {
msg := fmt.Sprintf("could not upgrade websocket from %s, got: %v", req.RemoteAddr, err)
logger.Println(msg)
http.Error(res, msg, 500)
return
}
defer in.Close()
logger.Printf("upgraded connection on %s", req.RemoteAddr)
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
errc := make(chan error, 1)
go proxyWS(ctxt, logger, "<-", in, out, errc)
go proxyWS(ctxt, logger, "->", out, in, errc)
<-errc
logger.Printf("---------- closing %s ----------", req.RemoteAddr)
})
log.Fatal(http.ListenAndServe(*flagListen, mux))
}
func proxyWS(ctxt context.Context, logger *log.Logger, prefix string, in, out *websocket.Conn, errc chan error) {
var mt int
var buf []byte
var err error
for {
select {
default:
mt, buf, err = in.ReadMessage()
if err != nil {
errc <- err
return
}
logger.Printf("%s %s", prefix, string(buf))
err = out.WriteMessage(mt, buf)
if err != nil {
errc <- err
return
}
case <-ctxt.Done():
return
}
}
}
func checkVersion() ([]byte, error) {
cl := &http.Client{}
req, err := http.NewRequest("GET", "http://"+*flagRemote+"/json/version", nil)
if err != nil {
return nil, err
}
res, err := cl.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var v map[string]string
err = json.Unmarshal(body, &v)
if err != nil {
return nil, fmt.Errorf("expected json result")
}
return body, nil
}
func createLog(id string) (io.Closer, *log.Logger) {
var f io.Closer
var w io.Writer = os.Stdout
if !*flagNoLog && *flagLogMask != "" {
l, err := os.OpenFile(fmt.Sprintf(*flagLogMask, id), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
f = l
w = io.MultiWriter(os.Stdout, l)
}
return f, log.New(w, "", log.LstdFlags)
}

View File

@ -1,2 +1,3 @@
headless
headless.exe
*.png

View File

@ -1,16 +1,16 @@
# About headless
This is a version of the simple example but with the chromedp settings changed
to use the docker [yukinying/chrome-headless](yukinying/chrome-headless) image.
to use the docker [knqz/chrome-headless](https://hub.docker.com/r/knqz/chrome-headless/) image.
## Running
```sh
# retrieve docker image
docker pull yukinying/chrome-headless
docker pull knqz/chrome-headless
# start docker headless
docker run -i -t --shm-size=256m --rm --name=chrome-headless -p=127.0.0.1:9222:9222 yukinying/chrome-headless about:blank
# start chrome-headless
docker run -d -p 9222:9222 --rm --name chrome-headless knqz/chrome-headless
# run chromedp headless example
go build && ./headless

View File

@ -42,9 +42,8 @@ func googleSearch(q, text string, site, res *string) cdp.Tasks {
cdp.Navigate(`https://www.google.com`),
cdp.Sleep(2 * time.Second),
cdp.WaitVisible(`#hplogo`, cdp.ByID),
cdp.SendKeys(`#lst-ib`, q, cdp.ByID),
cdp.Click(`input[name="btnK"]`, cdp.ByQuery),
cdp.WaitNotVisible(`input[name="btnI"]`, cdp.ByQuery),
cdp.SendKeys(`#lst-ib`, q+"\n", cdp.ByID),
cdp.WaitVisible(`#res`, cdp.ByID),
cdp.Text(sel, res),
cdp.Click(sel),
cdp.Sleep(2 * time.Second),

View File

@ -7,6 +7,7 @@ import (
"time"
cdp "github.com/knq/chromedp"
"github.com/knq/chromedp/kb"
)
func main() {
@ -52,12 +53,12 @@ func sendkeys(val1, val2, val3, val4 *string) cdp.Tasks {
cdp.Navigate("file:" + os.Getenv("GOPATH") + "/src/github.com/knq/chromedp/testdata/visible.html"),
cdp.WaitVisible(`#input1`, cdp.ByID),
cdp.WaitVisible(`#textarea1`, cdp.ByID),
cdp.SendKeys(`#textarea1`, "\b\b\n\naoeu\n\ntest1\n\nblah2\n\n\t\t\t\b\bother box!\t\ntest4", cdp.ByID),
cdp.SendKeys(`#textarea1`, kb.End+"\b\b\n\naoeu\n\ntest1\n\nblah2\n\n\t\t\t\b\bother box!\t\ntest4", cdp.ByID),
cdp.Value(`#input1`, val1, cdp.ByID),
cdp.Value(`#textarea1`, val2, cdp.ByID),
cdp.SetValue(`#input2`, "test3", cdp.ByID),
cdp.Value(`#input2`, val3, cdp.ByID),
cdp.SendKeys(`#select1`, cdp.KeyCodeDown+cdp.KeyCodeDown, cdp.ByID),
cdp.SendKeys(`#select1`, kb.ArrowDown+kb.ArrowDown, cdp.ByID),
cdp.Value(`#select1`, val4, cdp.ByID),
cdp.Sleep(30 * time.Second),
}

View File

@ -53,9 +53,8 @@ func googleSearch(q, text string, site, res *string) cdp.Tasks {
cdp.Navigate(`https://www.google.com`),
cdp.Sleep(2 * time.Second),
cdp.WaitVisible(`#hplogo`, cdp.ByID),
cdp.SendKeys(`#lst-ib`, q, cdp.ByID),
cdp.Click(`input[name="btnK"]`, cdp.ByQuery),
cdp.WaitNotVisible(`input[name="btnI"]`, cdp.ByQuery),
cdp.SendKeys(`#lst-ib`, q+"\n", cdp.ByID),
cdp.WaitVisible(`#res`, cdp.ByID),
cdp.Text(sel, res),
cdp.Click(sel),
cdp.Sleep(2 * time.Second),

109
input.go
View File

@ -8,6 +8,7 @@ import (
"github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/dom"
"github.com/knq/chromedp/cdp/input"
"github.com/knq/chromedp/kb"
)
// Error types.
@ -90,96 +91,42 @@ func ClickCount(n int) MouseOption {
}
}
// KeyAction contains information about a key action.
type KeyAction struct {
v string
opts []KeyOption
}
// KeyAction will synthesize a keyDown, char, and keyUp event for each rune
// contained in keys along with any supplied key options. Well known KeyCode
// runes will not synthesize the char event.
//
// Note: KeyCodeCR and KeyCodeLF are exceptions to the above, and a char of
// KeyCodeCR ('\r') will be synthesized for both.
func KeyAction(keys string, opts ...KeyOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
var err error
// KeyCode are known system key codes.
type KeyCode string
// KeyCode values.
const (
KeyCodeBackspace = "\b"
KeyCodeTab = "\t"
KeyCodeCR = "\r"
KeyCodeLF = "\n"
KeyCodeLeft = "\x25"
KeyCodeUp = "\x26"
KeyCodeRight = "\x27"
KeyCodeDown = "\x28"
)
const (
keyRuneCR = '\r'
)
// keyCodeNames is the map of key code values to their respective named
// identifiers.
var keyCodeNames = map[KeyCode]string{
KeyCodeBackspace: "Backspace",
KeyCodeTab: "Tab",
KeyCodeCR: "Enter",
KeyCodeLF: "Enter",
KeyCodeLeft: "Left",
KeyCodeUp: "Up",
KeyCodeRight: "Right",
KeyCodeDown: "Down",
}
// Do satisfies Action interface.
func (ka *KeyAction) Do(ctxt context.Context, h cdp.FrameHandler) error {
var err error
// apply opts
sysP := input.DispatchKeyEvent(input.KeyRawDown)
keyP := input.DispatchKeyEvent(input.KeyChar)
for _, o := range ka.opts {
sysP = o(sysP)
keyP = o(keyP)
}
for _, r := range ka.v {
s := string(r)
keyS := KeyCode(r)
if n, ok := keyCodeNames[keyS]; ok {
kc := int64(r)
if keyS == KeyCodeLF {
s = string(keyRuneCR)
kc = int64(keyRuneCR)
for _, r := range keys {
for _, k := range kb.Encode(r) {
err = k.Do(ctxt, h)
if err != nil {
return err
}
}
err = sysP.WithKey(n).
WithNativeVirtualKeyCode(kc).
WithWindowsVirtualKeyCode(kc).
WithKeyIdentifier(s).
WithIsSystemKey(true).
Do(ctxt, h)
if err != nil {
return err
}
// TODO: move to context
time.Sleep(5 * time.Millisecond)
}
err = keyP.WithText(s).Do(ctxt, h)
return nil
})
}
// KeyActionNode dispatches a key event on a node.
func KeyActionNode(n *cdp.Node, keys string, opts ...KeyOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
err := dom.Focus(n.NodeID).Do(ctxt, h)
if err != nil {
return err
}
// FIXME
time.Sleep(100 * time.Millisecond)
}
return nil
}
// KeyActionNode dispatches a key event on a node.
func KeyActionNode(n *cdp.Node, v string, opts ...KeyOption) Action {
return Tasks{
dom.Focus(n.NodeID),
MouseActionNode(n),
&KeyAction{v, opts},
}
return KeyAction(keys, opts...).Do(ctxt, h)
})
}
// KeyOption is a key action option.

560
kb/gen.go Normal file
View File

@ -0,0 +1,560 @@
// +build ignore
package main
import (
"bytes"
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"github.com/knq/chromedp/kb"
)
var (
flagOut = flag.String("out", "keys.go", "out source")
flagPkg = flag.String("pkg", "kb", "out package name")
)
const (
// chromiumSrc is the base chromium source repo location
chromiumSrc = "https://chromium.googlesource.com/chromium/src"
// domUsLayoutDataH contains the {printable,non-printable} DomCode -> DomKey
// also contains DomKey -> VKEY (not used)
domUsLayoutDataH = chromiumSrc + "/+/master/ui/events/keycodes/dom_us_layout_data.h?format=TEXT"
// keycodeConverterDataInc contains DomKey -> Key Name
keycodeConverterDataInc = chromiumSrc + "/+/master/ui/events/keycodes/dom/keycode_converter_data.inc?format=TEXT"
// domKeyDataInc contains DomKey -> Key Name + unicode (non-printable)
domKeyDataInc = chromiumSrc + "/+/master/ui/events/keycodes/dom/dom_key_data.inc?format=TEXT"
// keyboardCodesPosixH contains the scan code definitions for posix (ie native) keys.
keyboardCodesPosixH = chromiumSrc + "/+/master/ui/events/keycodes/keyboard_codes_posix.h?format=TEXT"
// keyboardCodesWinH contains the scan code definitions for windows keys.
keyboardCodesWinH = chromiumSrc + "/+/master/ui/events/keycodes/keyboard_codes_win.h?format=TEXT"
// windowsKeyboardCodesH contains the actual #defs for windows.
windowsKeyboardCodesH = chromiumSrc + "/third_party/+/master/WebKit/Source/platform/WindowsKeyboardCodes.h?format=TEXT"
)
const (
hdr = `package %s
// DOM keys.
const (
%s)
// Keys is the map of unicode characters to their DOM key data.
var Keys = map[rune]*Key{
%s}
`
)
func main() {
var err error
flag.Parse()
// special characters
keys := map[rune]kb.Key{
'\b': kb.Key{"Backspace", "Backspace", "", "", int64('\b'), int64('\b'), false, false},
'\t': kb.Key{"Tab", "Tab", "", "", int64('\t'), int64('\t'), false, false},
'\r': kb.Key{"Enter", "Enter", "\r", "\r", int64('\r'), int64('\r'), false, true},
}
// load keys
err = loadKeys(keys)
if err != nil {
log.Fatal(err)
}
// process keys
constBuf, mapBuf, err := processKeys(keys)
if err != nil {
log.Fatal(err)
}
// output
err = ioutil.WriteFile(
*flagOut,
[]byte(fmt.Sprintf(hdr, *flagPkg, string(constBuf), string(mapBuf))),
0644,
)
if err != nil {
log.Fatal(err)
}
// format
err = exec.Command("goimports", "-w", *flagOut).Run()
if err != nil {
log.Fatal(err)
}
}
// loadKeys loads the dom key definitions from the chromium source tree.
func loadKeys(keys map[rune]kb.Key) error {
var err error
// load key converter data
keycodeConverterMap, err := loadKeycodeConverterData()
if err != nil {
return err
}
// load dom code map
domKeyMap, err := loadDomKeyData()
if err != nil {
return err
}
// load US layout data
layoutBuf, err := grab(domUsLayoutDataH)
if err != nil {
return err
}
// load scan code map
scanCodeMap, err := loadScanCodes(keycodeConverterMap, domKeyMap, layoutBuf)
if err != nil {
return err
}
// process printable
err = loadPrintable(keys, keycodeConverterMap, domKeyMap, layoutBuf, scanCodeMap)
if err != nil {
return err
}
// process non-printable
err = loadNonPrintable(keys, keycodeConverterMap, domKeyMap, layoutBuf, scanCodeMap)
if err != nil {
return err
}
return nil
}
var fixRE = regexp.MustCompile(`,\n\s{10,}`)
var usbKeyRE = regexp.MustCompile(`(?m)^\s*USB_KEYMAP\((.*?), (.*?), (.*?), (.*?), (.*?), (.*?), (.*?)\)`)
// loadKeycodeConverterData loads the key codes from the keycode_converter_data.inc.
func loadKeycodeConverterData() (map[string][]string, error) {
buf, err := grab(keycodeConverterDataInc)
if err != nil {
return nil, err
}
buf = fixRE.ReplaceAllLiteral(buf, []byte(", "))
domMap := make(map[string][]string)
matches := usbKeyRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
vkey := m[7]
if _, ok := domMap[vkey]; ok {
panic(fmt.Sprintf("vkey %s already defined", vkey))
}
domMap[vkey] = m[1:]
}
return domMap, nil
}
// decodeRune is a wrapper around parsing a printable c++ int/char definition to a unicode
// rune value.
func decodeRune(s string) rune {
if strings.HasPrefix(s, "0x") {
i, err := strconv.ParseInt(s, 0, 16)
if err != nil {
panic(err)
}
return rune(i)
}
if !strings.HasPrefix(s, "'") || !strings.HasSuffix(s, "'") {
panic(fmt.Sprintf("expected character, got: %s", s))
}
if len(s) == 4 {
if s[1] != '\\' {
panic(fmt.Sprintf("expected escaped character, got: %s", s))
}
return rune(s[2])
}
if len(s) != 3 {
panic(fmt.Sprintf("expected character, got: %s", s))
}
return rune(s[1])
}
// getCode is a simple wrapper around parsing the code definition.
func getCode(s string) string {
if !strings.HasPrefix(s, `"`) || !strings.HasSuffix(s, `"`) {
panic(fmt.Sprintf("expected string, got: %s", s))
}
return s[1 : len(s)-1]
}
// addKey is a simple map add wrapper to panic if the key is already defined,
// and to lookup the correct scan code.
func addKey(keys map[rune]kb.Key, r rune, key kb.Key, scanCodeMap map[string][]int64, shouldPanic bool) {
if _, ok := keys[r]; ok {
if shouldPanic {
panic(fmt.Sprintf("rune %U (%s/%s) already defined in keys", r, key.Code, key.Key))
}
return
}
sc, ok := scanCodeMap[key.Code]
if ok {
key.Native = sc[0]
key.Windows = sc[1]
}
keys[r] = key
}
var printableKeyRE = regexp.MustCompile(`\{DomCode::(.+?), \{(.+?), (.+?)\}\}`)
// loadPrintable loads the printable key definitions.
func loadPrintable(keys map[rune]kb.Key, keycodeConverterMap, domKeyMap map[string][]string, layoutBuf []byte, scanCodeMap map[string][]int64) error {
buf := extract(layoutBuf, "kPrintableCodeMap")
matches := printableKeyRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
domCode := m[1]
// ignore domCodes that are duplicates of other unicode characters
if domCode == "INTL_BACKSLASH" || domCode == "INTL_HASH" || strings.HasPrefix(domCode, "NUMPAD") {
continue
}
kc, ok := keycodeConverterMap[domCode]
if !ok {
panic(fmt.Sprintf("could not find key %s in keycode map", domCode))
}
code := getCode(kc[5])
r1, r2 := decodeRune(m[2]), decodeRune(m[3])
addKey(keys, r1, kb.Key{
Code: code,
Key: string(r1),
Text: string(r1),
Unmodified: string(r1),
Print: true,
}, scanCodeMap, true)
// shifted value is same as non-shifted, so skip
if r2 == r1 {
continue
}
// skip for duplicate keys
if r2 == '|' && domCode != "BACKSLASH" {
continue
}
addKey(keys, r2, kb.Key{
Code: code,
Key: string(r2),
Text: string(r2),
Unmodified: string(r1),
Shift: true,
Print: true,
}, scanCodeMap, true)
}
return nil
}
var domKeyRE = regexp.MustCompile(`(?m)^\s+DOM_KEY_(?:UNI|MAP)\("(.+?)",\s*(.+?),\s*(0x[0-9A-F]{4})\)`)
// loadDomKeyData loads the dom key data definitions.
func loadDomKeyData() (map[string][]string, error) {
buf, err := grab(domKeyDataInc)
if err != nil {
return nil, err
}
buf = fixRE.ReplaceAllLiteral(buf, []byte(", "))
keyMap := make(map[string][]string)
matches := domKeyRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
keyMap[m[2]] = m[1:]
}
return keyMap, nil
}
var nonPrintableKeyRE = regexp.MustCompile(`\n\s{4}\{DomCode::(.+?), DomKey::(.+?)\}`)
// loadNonPrintable loads the not printable key definitions.
func loadNonPrintable(keys map[rune]kb.Key, keycodeConverterMap, domKeyMap map[string][]string, layoutBuf []byte, scanCodeMap map[string][]int64) error {
buf := extract(layoutBuf, "kNonPrintableCodeMap")
matches := nonPrintableKeyRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
code, key := m[1], m[2]
// get code, key definitions
dc, ok := keycodeConverterMap[code]
if !ok {
panic(fmt.Sprintf("no dom code definition for %s", code))
}
dk, ok := domKeyMap[key]
if !ok {
panic(fmt.Sprintf("no dom key definition for %s", key))
}
// some scan codes do not have names defined, so use key name
c := dk[0]
if dc[5] != "NULL" {
c = getCode(dc[5])
}
// convert rune
r, err := strconv.ParseInt(dk[2], 0, 32)
if err != nil {
return err
}
addKey(keys, rune(r), kb.Key{
Code: c,
Key: dk[0],
}, scanCodeMap, false)
}
return nil
}
var nameRE = regexp.MustCompile(`[A-Z][a-z]+:`)
// processKeys processes the generated keys.
func processKeys(keys map[rune]kb.Key) ([]byte, []byte, error) {
// order rune keys
idx := make([]rune, len(keys))
i := 0
for c := range keys {
idx[i] = c
i++
}
sort.Slice(idx, func(a, b int) bool {
return idx[a] < idx[b]
})
// process
var constBuf, mapBuf bytes.Buffer
for _, c := range idx {
key := keys[c]
g, isGoCode := goCodes[c]
s := fmt.Sprintf("\\u%04x", c)
if isGoCode {
s = g
} else if key.Print {
s = fmt.Sprintf("%c", c)
}
// add key definition
v := strings.TrimPrefix(fmt.Sprintf("%#v", key), "kb.")
v = nameRE.ReplaceAllString(v, "")
mapBuf.WriteString(fmt.Sprintf("'%s': &%s,\n", s, v))
// fix 'Quote' const
if s == `\'` {
s = `'`
}
// add const definition
if (isGoCode && c != '\n') || !key.Print {
n := strings.TrimPrefix(key.Key, ".")
if n == `'` || n == `\` {
n = key.Code
}
constBuf.WriteString(fmt.Sprintf("%s = \"%s\"\n", n, s))
}
}
return constBuf.Bytes(), mapBuf.Bytes(), nil
}
var domCodeVkeyFixRE = regexp.MustCompile(`,\n\s{5,}`)
var domCodeVkeyRE = regexp.MustCompile(`(?m)^\s*\{DomCode::(.+?), (.+?)\}`)
// loadScanCodes loads the scan codes for the dom key definitions.
func loadScanCodes(keycodeConverterMap, domKeyMap map[string][]string, layoutBuf []byte) (map[string][]int64, error) {
vkeyCodeMap, err := loadPosixWinKeyboardCodes()
if err != nil {
return nil, err
}
buf := extract(layoutBuf, "kDomCodeToKeyboardCodeMap")
buf = domCodeVkeyFixRE.ReplaceAllLiteral(buf, []byte(", "))
scanCodeMap := make(map[string][]int64)
matches := domCodeVkeyRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
domCode, vkey := m[1], m[2]
kc, ok := keycodeConverterMap[domCode]
if !ok {
panic(fmt.Sprintf("dom code %s not defined in keycode map", domCode))
}
sc, ok := vkeyCodeMap[vkey]
if !ok {
panic(fmt.Sprintf("vkey %s is not defined in keyboardCodeMap", vkey))
}
scanCodeMap[getCode(kc[5])] = sc
}
return scanCodeMap, nil
}
var defineRE = regexp.MustCompile(`(?m)^#define\s+(.+?)\s+([0-9A-Fx]+)`)
// loadPosixWinKeyboardCodes loads the native and windows keyboard scan codes
// mapped to the DOM key.
func loadPosixWinKeyboardCodes() (map[string][]int64, error) {
var err error
lookup := map[string]string{
// mac alias
"VKEY_LWIN": "0x5B",
// no idea where these are defined in chromium code base (assuming in
// windows headers)
//
// manually added here as pulled from various online docs
"VK_CANCEL": "0x03",
"VK_OEM_ATTN": "0xF0",
"VK_OEM_FINISH": "0xF1",
"VK_OEM_COPY": "0xF2",
"VK_DBE_SBCSCHAR": "0xF3",
"VK_DBE_DBCSCHAR": "0xF4",
"VK_OEM_BACKTAB": "0xF5",
"VK_OEM_AX": "0xE1",
}
// load windows key lookups
buf, err := grab(windowsKeyboardCodesH)
if err != nil {
return nil, err
}
matches := defineRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
lookup[m[1]] = m[2]
}
// load posix and win keyboard codes
keyboardCodeMap := make(map[string][]int64)
err = loadKeyboardCodes(keyboardCodeMap, lookup, keyboardCodesPosixH, 0)
if err != nil {
return nil, err
}
err = loadKeyboardCodes(keyboardCodeMap, lookup, keyboardCodesWinH, 1)
if err != nil {
return nil, err
}
return keyboardCodeMap, nil
}
var keyboardCodeRE = regexp.MustCompile(`(?m)^\s+(VKEY_.+?)\s+=\s+(.+?),`)
// loadKeyboardCodes loads the enum definition from the specified path, saving
// the resolved symbol value to the specified position for the resulting dom
// key name in the vkeyCodeMap.
func loadKeyboardCodes(vkeyCodeMap map[string][]int64, lookup map[string]string, path string, pos int) error {
buf, err := grab(path)
if err != nil {
return err
}
buf = extract(buf, "KeyboardCode")
matches := keyboardCodeRE.FindAllStringSubmatch(string(buf), -1)
for _, m := range matches {
v := m[2]
switch {
case strings.HasPrefix(m[2], "'"):
v = fmt.Sprintf("0x%04x", m[2][1])
case !strings.HasPrefix(m[2], "0x") && m[2] != "0":
z, ok := lookup[v]
if !ok {
panic(fmt.Sprintf("could not find %s in lookup", v))
}
v = z
}
// load the value
i, err := strconv.ParseInt(v, 0, 32)
if err != nil {
panic(fmt.Sprintf("could not parse %s // %s // %s", m[1], m[2], v))
}
vkey, ok := vkeyCodeMap[m[1]]
if !ok {
vkey = make([]int64, 2)
}
vkey[pos] = i
vkeyCodeMap[m[1]] = vkey
}
return nil
}
var endRE = regexp.MustCompile(`\n}`)
// extract extracts a block of next from a block of c++ code.
func extract(buf []byte, name string) []byte {
extractRE := regexp.MustCompile(`\s+` + name + `.+?{`)
buf = buf[extractRE.FindIndex(buf)[0]:]
return buf[:endRE.FindIndex(buf)[1]]
}
// grab retrieves a file from the chromium source code.
func grab(path string) ([]byte, error) {
res, err := http.Get(path)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
buf, err := base64.StdEncoding.DecodeString(string(body))
if err != nil {
return nil, err
}
return buf, nil
}
var goCodes = map[rune]string{
'\a': `\a`,
'\b': `\b`,
'\f': `\f`,
'\n': `\n`,
'\r': `\r`,
'\t': `\t`,
'\v': `\v`,
'\\': `\\`,
'\'': `\'`,
}

123
kb/kb.go Normal file
View File

@ -0,0 +1,123 @@
package kb
//go:generate go run gen.go -out keys.go -pkg kb
import (
"unicode"
"github.com/knq/chromedp/cdp/input"
)
// Key contains information for generating a key press based off the unicode
// value.
//
// Example data for the following runes:
// '\r' '\n' | ',' '<' | 'a' 'A' | '\u0a07'
// _____________________________________________________
type Key struct {
// Code is the key code:
// "Enter" | "Comma" | "KeyA" | "MediaStop"
Code string
// Key is the key value:
// "Enter" | "," "<" | "a" "A" | "MediaStop"
Key string
// Text is the text for printable keys:
// "\r" "\r" | "," "<" | "a" "A" | ""
Text string
// Unmodified is the unmodified text for printable keys:
// "\r" "\r" | "," "," | "a" "a" | ""
Unmodified string
// Native is the native scan code.
// 0x13 0x13 | 0xbc 0xbc | 0x61 0x41 | 0x00ae
Native int64
// Windows is the windows scan code.
// 0x13 0x13 | 0xbc 0xbc | 0x61 0x41 | 0xe024
Windows int64
// Shift indicates whether or not the Shift modifier should be sent.
// false false | false true | false true | false
Shift bool
// Print indicates whether or not the character is a printable character
// (ie, should a "char" event be generated).
// true true | true true | true true | false
Print bool
}
// EncodeUnidentified encodes a keyDown, char, and keyUp sequence for an unidentified rune.
//
// TODO: write unit tests for non-latin/ascii unicode characters.
func EncodeUnidentified(r rune) []*input.DispatchKeyEventParams {
// create
keyDown := input.DispatchKeyEventParams{
Key: "Unidentified",
/*NativeVirtualKeyCode: int64(r), // not sure if should be specifying the key code or not ...
WindowsVirtualKeyCode: int64(r),*/
}
keyUp := keyDown
keyDown.Type, keyUp.Type = input.KeyDown, input.KeyUp
// printable, so create char event
if unicode.IsPrint(r) {
keyChar := keyDown
keyChar.Type = input.KeyChar
keyChar.Text = string(r)
keyChar.UnmodifiedText = string(r)
return []*input.DispatchKeyEventParams{&keyDown, &keyChar, &keyUp}
}
return []*input.DispatchKeyEventParams{&keyDown, &keyUp}
}
// Encode encodes a keyDown, char, and keyUp sequence for the specified rune.
func Encode(r rune) []*input.DispatchKeyEventParams {
// force \n -> \r
if r == '\n' {
r = '\r'
}
// if not known key, encode as unidentified
v, ok := Keys[r]
if !ok {
return EncodeUnidentified(r)
}
// create
keyDown := input.DispatchKeyEventParams{
Key: v.Key,
Code: v.Code,
NativeVirtualKeyCode: v.Native,
WindowsVirtualKeyCode: v.Windows,
}
if v.Shift {
keyDown.Modifiers |= input.ModifierShift
}
keyUp := keyDown
keyDown.Type, keyUp.Type = input.KeyDown, input.KeyUp
// printable, so create char event
if v.Print {
keyChar := keyDown
keyChar.Type = input.KeyChar
keyChar.Text = v.Text
keyChar.UnmodifiedText = v.Unmodified
// the virtual key code for char events for printable characters will
// be different than the defined keycode when not shifted...
//
// specifically, it always sends the ascii value as the scan code,
// which is available as the rune.
keyChar.NativeVirtualKeyCode = int64(r)
keyChar.WindowsVirtualKeyCode = int64(r)
return []*input.DispatchKeyEventParams{&keyDown, &keyChar, &keyUp}
}
return []*input.DispatchKeyEventParams{&keyDown, &keyUp}
}

367
kb/keys.go Normal file
View File

@ -0,0 +1,367 @@
package kb
// DOM keys.
const (
Backspace = "\b"
Tab = "\t"
Enter = "\r"
Escape = "\u001b"
Quote = "'"
Backslash = "\\"
Delete = "\u007f"
Alt = "\u0102"
CapsLock = "\u0104"
Control = "\u0105"
Fn = "\u0106"
FnLock = "\u0107"
Hyper = "\u0108"
Meta = "\u0109"
NumLock = "\u010a"
ScrollLock = "\u010c"
Shift = "\u010d"
Super = "\u010e"
ArrowDown = "\u0301"
ArrowLeft = "\u0302"
ArrowRight = "\u0303"
ArrowUp = "\u0304"
End = "\u0305"
Home = "\u0306"
PageDown = "\u0307"
PageUp = "\u0308"
Clear = "\u0401"
Copy = "\u0402"
Cut = "\u0404"
Insert = "\u0407"
Paste = "\u0408"
Redo = "\u0409"
Undo = "\u040a"
Again = "\u0502"
Cancel = "\u0504"
ContextMenu = "\u0505"
Find = "\u0507"
Help = "\u0508"
Pause = "\u0509"
Props = "\u050b"
Select = "\u050c"
ZoomIn = "\u050d"
ZoomOut = "\u050e"
BrightnessDown = "\u0601"
BrightnessUp = "\u0602"
Eject = "\u0604"
LogOff = "\u0605"
Power = "\u0606"
PrintScreen = "\u0608"
WakeUp = "\u060b"
Convert = "\u0705"
NonConvert = "\u070d"
HangulMode = "\u0711"
HanjaMode = "\u0712"
Hiragana = "\u0716"
KanaMode = "\u0718"
Katakana = "\u071a"
ZenkakuHankaku = "\u071d"
F1 = "\u0801"
F2 = "\u0802"
F3 = "\u0803"
F4 = "\u0804"
F5 = "\u0805"
F6 = "\u0806"
F7 = "\u0807"
F8 = "\u0808"
F9 = "\u0809"
F10 = "\u080a"
F11 = "\u080b"
F12 = "\u080c"
F13 = "\u080d"
F14 = "\u080e"
F15 = "\u080f"
F16 = "\u0810"
F17 = "\u0811"
F18 = "\u0812"
F19 = "\u0813"
F20 = "\u0814"
F21 = "\u0815"
F22 = "\u0816"
F23 = "\u0817"
F24 = "\u0818"
Close = "\u0a01"
MailForward = "\u0a02"
MailReply = "\u0a03"
MailSend = "\u0a04"
MediaPlayPause = "\u0a05"
MediaStop = "\u0a07"
MediaTrackNext = "\u0a08"
MediaTrackPrevious = "\u0a09"
New = "\u0a0a"
Open = "\u0a0b"
Print = "\u0a0c"
Save = "\u0a0d"
SpellCheck = "\u0a0e"
AudioVolumeDown = "\u0a0f"
AudioVolumeUp = "\u0a10"
AudioVolumeMute = "\u0a11"
LaunchCalculator = "\u0b01"
LaunchCalendar = "\u0b02"
LaunchMail = "\u0b03"
LaunchMediaPlayer = "\u0b04"
LaunchMusicPlayer = "\u0b05"
LaunchMyComputer = "\u0b06"
LaunchScreenSaver = "\u0b07"
LaunchSpreadsheet = "\u0b08"
LaunchWebBrowser = "\u0b09"
LaunchContacts = "\u0b0c"
LaunchPhone = "\u0b0d"
BrowserBack = "\u0c01"
BrowserFavorites = "\u0c02"
BrowserForward = "\u0c03"
BrowserHome = "\u0c04"
BrowserRefresh = "\u0c05"
BrowserSearch = "\u0c06"
BrowserStop = "\u0c07"
ChannelDown = "\u0d0a"
ChannelUp = "\u0d0b"
ClosedCaptionToggle = "\u0d12"
Exit = "\u0d15"
Guide = "\u0d22"
Info = "\u0d25"
MediaFastForward = "\u0d2c"
MediaLast = "\u0d2d"
MediaPlay = "\u0d2f"
MediaRecord = "\u0d30"
MediaRewind = "\u0d31"
Settings = "\u0d43"
ZoomToggle = "\u0d4e"
AudioBassBoostToggle = "\u0e02"
SpeechInputToggle = "\u0f02"
AppSwitch = "\u1001"
)
// Keys is the map of unicode characters to their DOM key data.
var Keys = map[rune]*Key{
'\b': &Key{"Backspace", "Backspace", "", "", 8, 8, false, false},
'\t': &Key{"Tab", "Tab", "", "", 9, 9, false, false},
'\r': &Key{"Enter", "Enter", "\r", "\r", 13, 13, false, true},
'\u001b': &Key{"Escape", "Escape", "", "", 27, 27, false, false},
' ': &Key{"Space", " ", " ", " ", 32, 32, false, true},
'!': &Key{"Digit1", "!", "!", "1", 49, 49, true, true},
'"': &Key{"Quote", "\"", "\"", "'", 222, 222, true, true},
'#': &Key{"Digit3", "#", "#", "3", 51, 51, true, true},
'$': &Key{"Digit4", "$", "$", "4", 52, 52, true, true},
'%': &Key{"Digit5", "%", "%", "5", 53, 53, true, true},
'&': &Key{"Digit7", "&", "&", "7", 55, 55, true, true},
'\'': &Key{"Quote", "'", "'", "'", 222, 222, false, true},
'(': &Key{"Digit9", "(", "(", "9", 57, 57, true, true},
')': &Key{"Digit0", ")", ")", "0", 48, 48, true, true},
'*': &Key{"Digit8", "*", "*", "8", 56, 56, true, true},
'+': &Key{"Equal", "+", "+", "=", 187, 187, true, true},
',': &Key{"Comma", ",", ",", ",", 188, 188, false, true},
'-': &Key{"Minus", "-", "-", "-", 189, 189, false, true},
'.': &Key{"Period", ".", ".", ".", 190, 190, false, true},
'/': &Key{"Slash", "/", "/", "/", 191, 191, false, true},
'0': &Key{"Digit0", "0", "0", "0", 48, 48, false, true},
'1': &Key{"Digit1", "1", "1", "1", 49, 49, false, true},
'2': &Key{"Digit2", "2", "2", "2", 50, 50, false, true},
'3': &Key{"Digit3", "3", "3", "3", 51, 51, false, true},
'4': &Key{"Digit4", "4", "4", "4", 52, 52, false, true},
'5': &Key{"Digit5", "5", "5", "5", 53, 53, false, true},
'6': &Key{"Digit6", "6", "6", "6", 54, 54, false, true},
'7': &Key{"Digit7", "7", "7", "7", 55, 55, false, true},
'8': &Key{"Digit8", "8", "8", "8", 56, 56, false, true},
'9': &Key{"Digit9", "9", "9", "9", 57, 57, false, true},
':': &Key{"Semicolon", ":", ":", ";", 186, 186, true, true},
';': &Key{"Semicolon", ";", ";", ";", 186, 186, false, true},
'<': &Key{"Comma", "<", "<", ",", 188, 188, true, true},
'=': &Key{"Equal", "=", "=", "=", 187, 187, false, true},
'>': &Key{"Period", ">", ">", ".", 190, 190, true, true},
'?': &Key{"Slash", "?", "?", "/", 191, 191, true, true},
'@': &Key{"Digit2", "@", "@", "2", 50, 50, true, true},
'A': &Key{"KeyA", "A", "A", "a", 65, 65, true, true},
'B': &Key{"KeyB", "B", "B", "b", 66, 66, true, true},
'C': &Key{"KeyC", "C", "C", "c", 67, 67, true, true},
'D': &Key{"KeyD", "D", "D", "d", 68, 68, true, true},
'E': &Key{"KeyE", "E", "E", "e", 69, 69, true, true},
'F': &Key{"KeyF", "F", "F", "f", 70, 70, true, true},
'G': &Key{"KeyG", "G", "G", "g", 71, 71, true, true},
'H': &Key{"KeyH", "H", "H", "h", 72, 72, true, true},
'I': &Key{"KeyI", "I", "I", "i", 73, 73, true, true},
'J': &Key{"KeyJ", "J", "J", "j", 74, 74, true, true},
'K': &Key{"KeyK", "K", "K", "k", 75, 75, true, true},
'L': &Key{"KeyL", "L", "L", "l", 76, 76, true, true},
'M': &Key{"KeyM", "M", "M", "m", 77, 77, true, true},
'N': &Key{"KeyN", "N", "N", "n", 78, 78, true, true},
'O': &Key{"KeyO", "O", "O", "o", 79, 79, true, true},
'P': &Key{"KeyP", "P", "P", "p", 80, 80, true, true},
'Q': &Key{"KeyQ", "Q", "Q", "q", 81, 81, true, true},
'R': &Key{"KeyR", "R", "R", "r", 82, 82, true, true},
'S': &Key{"KeyS", "S", "S", "s", 83, 83, true, true},
'T': &Key{"KeyT", "T", "T", "t", 84, 84, true, true},
'U': &Key{"KeyU", "U", "U", "u", 85, 85, true, true},
'V': &Key{"KeyV", "V", "V", "v", 86, 86, true, true},
'W': &Key{"KeyW", "W", "W", "w", 87, 87, true, true},
'X': &Key{"KeyX", "X", "X", "x", 88, 88, true, true},
'Y': &Key{"KeyY", "Y", "Y", "y", 89, 89, true, true},
'Z': &Key{"KeyZ", "Z", "Z", "z", 90, 90, true, true},
'[': &Key{"BracketLeft", "[", "[", "[", 219, 219, false, true},
'\\': &Key{"Backslash", "\\", "\\", "\\", 220, 220, false, true},
']': &Key{"BracketRight", "]", "]", "]", 221, 221, false, true},
'^': &Key{"Digit6", "^", "^", "6", 54, 54, true, true},
'_': &Key{"Minus", "_", "_", "-", 189, 189, true, true},
'`': &Key{"Backquote", "`", "`", "`", 192, 192, false, true},
'a': &Key{"KeyA", "a", "a", "a", 65, 65, false, true},
'b': &Key{"KeyB", "b", "b", "b", 66, 66, false, true},
'c': &Key{"KeyC", "c", "c", "c", 67, 67, false, true},
'd': &Key{"KeyD", "d", "d", "d", 68, 68, false, true},
'e': &Key{"KeyE", "e", "e", "e", 69, 69, false, true},
'f': &Key{"KeyF", "f", "f", "f", 70, 70, false, true},
'g': &Key{"KeyG", "g", "g", "g", 71, 71, false, true},
'h': &Key{"KeyH", "h", "h", "h", 72, 72, false, true},
'i': &Key{"KeyI", "i", "i", "i", 73, 73, false, true},
'j': &Key{"KeyJ", "j", "j", "j", 74, 74, false, true},
'k': &Key{"KeyK", "k", "k", "k", 75, 75, false, true},
'l': &Key{"KeyL", "l", "l", "l", 76, 76, false, true},
'm': &Key{"KeyM", "m", "m", "m", 77, 77, false, true},
'n': &Key{"KeyN", "n", "n", "n", 78, 78, false, true},
'o': &Key{"KeyO", "o", "o", "o", 79, 79, false, true},
'p': &Key{"KeyP", "p", "p", "p", 80, 80, false, true},
'q': &Key{"KeyQ", "q", "q", "q", 81, 81, false, true},
'r': &Key{"KeyR", "r", "r", "r", 82, 82, false, true},
's': &Key{"KeyS", "s", "s", "s", 83, 83, false, true},
't': &Key{"KeyT", "t", "t", "t", 84, 84, false, true},
'u': &Key{"KeyU", "u", "u", "u", 85, 85, false, true},
'v': &Key{"KeyV", "v", "v", "v", 86, 86, false, true},
'w': &Key{"KeyW", "w", "w", "w", 87, 87, false, true},
'x': &Key{"KeyX", "x", "x", "x", 88, 88, false, true},
'y': &Key{"KeyY", "y", "y", "y", 89, 89, false, true},
'z': &Key{"KeyZ", "z", "z", "z", 90, 90, false, true},
'{': &Key{"BracketLeft", "{", "{", "[", 219, 219, true, true},
'|': &Key{"Backslash", "|", "|", "\\", 220, 220, true, true},
'}': &Key{"BracketRight", "}", "}", "]", 221, 221, true, true},
'~': &Key{"Backquote", "~", "~", "`", 192, 192, true, true},
'\u007f': &Key{"Delete", "Delete", "", "", 46, 46, false, false},
'¥': &Key{"IntlYen", "¥", "¥", "¥", 220, 220, false, true},
'\u0102': &Key{"AltLeft", "Alt", "", "", 164, 164, false, false},
'\u0104': &Key{"CapsLock", "CapsLock", "", "", 20, 20, false, false},
'\u0105': &Key{"ControlLeft", "Control", "", "", 162, 162, false, false},
'\u0106': &Key{"Fn", "Fn", "", "", 0, 0, false, false},
'\u0107': &Key{"FnLock", "FnLock", "", "", 0, 0, false, false},
'\u0108': &Key{"Hyper", "Hyper", "", "", 0, 0, false, false},
'\u0109': &Key{"MetaLeft", "Meta", "", "", 91, 91, false, false},
'\u010a': &Key{"NumLock", "NumLock", "", "", 144, 144, false, false},
'\u010c': &Key{"ScrollLock", "ScrollLock", "", "", 145, 145, false, false},
'\u010d': &Key{"ShiftLeft", "Shift", "", "", 160, 160, false, false},
'\u010e': &Key{"Super", "Super", "", "", 0, 0, false, false},
'\u0301': &Key{"ArrowDown", "ArrowDown", "", "", 40, 40, false, false},
'\u0302': &Key{"ArrowLeft", "ArrowLeft", "", "", 37, 37, false, false},
'\u0303': &Key{"ArrowRight", "ArrowRight", "", "", 39, 39, false, false},
'\u0304': &Key{"ArrowUp", "ArrowUp", "", "", 38, 38, false, false},
'\u0305': &Key{"End", "End", "", "", 35, 35, false, false},
'\u0306': &Key{"Home", "Home", "", "", 36, 36, false, false},
'\u0307': &Key{"PageDown", "PageDown", "", "", 34, 34, false, false},
'\u0308': &Key{"PageUp", "PageUp", "", "", 33, 33, false, false},
'\u0401': &Key{"NumpadClear", "Clear", "", "", 12, 12, false, false},
'\u0402': &Key{"Copy", "Copy", "", "", 0, 0, false, false},
'\u0404': &Key{"Cut", "Cut", "", "", 0, 0, false, false},
'\u0407': &Key{"Insert", "Insert", "", "", 45, 45, false, false},
'\u0408': &Key{"Paste", "Paste", "", "", 0, 0, false, false},
'\u0409': &Key{"Redo", "Redo", "", "", 0, 0, false, false},
'\u040a': &Key{"Undo", "Undo", "", "", 0, 0, false, false},
'\u0502': &Key{"Again", "Again", "", "", 0, 0, false, false},
'\u0504': &Key{"Abort", "Cancel", "", "", 0, 0, false, false},
'\u0505': &Key{"ContextMenu", "ContextMenu", "", "", 93, 93, false, false},
'\u0507': &Key{"Find", "Find", "", "", 0, 0, false, false},
'\u0508': &Key{"Help", "Help", "", "", 47, 47, false, false},
'\u0509': &Key{"Pause", "Pause", "", "", 19, 19, false, false},
'\u050b': &Key{"Props", "Props", "", "", 0, 0, false, false},
'\u050c': &Key{"Select", "Select", "", "", 41, 41, false, false},
'\u050d': &Key{"ZoomIn", "ZoomIn", "", "", 0, 0, false, false},
'\u050e': &Key{"ZoomOut", "ZoomOut", "", "", 0, 0, false, false},
'\u0601': &Key{"BrightnessDown", "BrightnessDown", "", "", 216, 0, false, false},
'\u0602': &Key{"BrightnessUp", "BrightnessUp", "", "", 217, 0, false, false},
'\u0604': &Key{"Eject", "Eject", "", "", 0, 0, false, false},
'\u0605': &Key{"LogOff", "LogOff", "", "", 0, 0, false, false},
'\u0606': &Key{"Power", "Power", "", "", 152, 0, false, false},
'\u0608': &Key{"PrintScreen", "PrintScreen", "", "", 44, 44, false, false},
'\u060b': &Key{"WakeUp", "WakeUp", "", "", 0, 0, false, false},
'\u0705': &Key{"Convert", "Convert", "", "", 28, 28, false, false},
'\u070d': &Key{"NonConvert", "NonConvert", "", "", 29, 29, false, false},
'\u0711': &Key{"Lang1", "HangulMode", "", "", 21, 21, false, false},
'\u0712': &Key{"Lang2", "HanjaMode", "", "", 25, 25, false, false},
'\u0716': &Key{"Lang4", "Hiragana", "", "", 0, 0, false, false},
'\u0718': &Key{"KanaMode", "KanaMode", "", "", 21, 21, false, false},
'\u071a': &Key{"Lang3", "Katakana", "", "", 0, 0, false, false},
'\u071d': &Key{"Lang5", "ZenkakuHankaku", "", "", 0, 0, false, false},
'\u0801': &Key{"F1", "F1", "", "", 112, 112, false, false},
'\u0802': &Key{"F2", "F2", "", "", 113, 113, false, false},
'\u0803': &Key{"F3", "F3", "", "", 114, 114, false, false},
'\u0804': &Key{"F4", "F4", "", "", 115, 115, false, false},
'\u0805': &Key{"F5", "F5", "", "", 116, 116, false, false},
'\u0806': &Key{"F6", "F6", "", "", 117, 117, false, false},
'\u0807': &Key{"F7", "F7", "", "", 118, 118, false, false},
'\u0808': &Key{"F8", "F8", "", "", 119, 119, false, false},
'\u0809': &Key{"F9", "F9", "", "", 120, 120, false, false},
'\u080a': &Key{"F10", "F10", "", "", 121, 121, false, false},
'\u080b': &Key{"F11", "F11", "", "", 122, 122, false, false},
'\u080c': &Key{"F12", "F12", "", "", 123, 123, false, false},
'\u080d': &Key{"F13", "F13", "", "", 124, 124, false, false},
'\u080e': &Key{"F14", "F14", "", "", 125, 125, false, false},
'\u080f': &Key{"F15", "F15", "", "", 126, 126, false, false},
'\u0810': &Key{"F16", "F16", "", "", 127, 127, false, false},
'\u0811': &Key{"F17", "F17", "", "", 128, 128, false, false},
'\u0812': &Key{"F18", "F18", "", "", 129, 129, false, false},
'\u0813': &Key{"F19", "F19", "", "", 130, 130, false, false},
'\u0814': &Key{"F20", "F20", "", "", 131, 131, false, false},
'\u0815': &Key{"F21", "F21", "", "", 132, 132, false, false},
'\u0816': &Key{"F22", "F22", "", "", 133, 133, false, false},
'\u0817': &Key{"F23", "F23", "", "", 134, 134, false, false},
'\u0818': &Key{"F24", "F24", "", "", 135, 135, false, false},
'\u0a01': &Key{"Close", "Close", "", "", 0, 0, false, false},
'\u0a02': &Key{"MailForward", "MailForward", "", "", 0, 0, false, false},
'\u0a03': &Key{"MailReply", "MailReply", "", "", 0, 0, false, false},
'\u0a04': &Key{"MailSend", "MailSend", "", "", 0, 0, false, false},
'\u0a05': &Key{"MediaPlayPause", "MediaPlayPause", "", "", 179, 179, false, false},
'\u0a07': &Key{"MediaStop", "MediaStop", "", "", 178, 178, false, false},
'\u0a08': &Key{"MediaTrackNext", "MediaTrackNext", "", "", 176, 176, false, false},
'\u0a09': &Key{"MediaTrackPrevious", "MediaTrackPrevious", "", "", 177, 177, false, false},
'\u0a0a': &Key{"New", "New", "", "", 0, 0, false, false},
'\u0a0b': &Key{"Open", "Open", "", "", 43, 43, false, false},
'\u0a0c': &Key{"Print", "Print", "", "", 0, 0, false, false},
'\u0a0d': &Key{"Save", "Save", "", "", 0, 0, false, false},
'\u0a0e': &Key{"SpellCheck", "SpellCheck", "", "", 0, 0, false, false},
'\u0a0f': &Key{"AudioVolumeDown", "AudioVolumeDown", "", "", 174, 174, false, false},
'\u0a10': &Key{"AudioVolumeUp", "AudioVolumeUp", "", "", 175, 175, false, false},
'\u0a11': &Key{"AudioVolumeMute", "AudioVolumeMute", "", "", 173, 173, false, false},
'\u0b01': &Key{"LaunchApp2", "LaunchCalculator", "", "", 183, 183, false, false},
'\u0b02': &Key{"LaunchCalendar", "LaunchCalendar", "", "", 0, 0, false, false},
'\u0b03': &Key{"LaunchMail", "LaunchMail", "", "", 180, 180, false, false},
'\u0b04': &Key{"MediaSelect", "LaunchMediaPlayer", "", "", 181, 181, false, false},
'\u0b05': &Key{"LaunchMusicPlayer", "LaunchMusicPlayer", "", "", 0, 0, false, false},
'\u0b06': &Key{"LaunchApp1", "LaunchMyComputer", "", "", 182, 182, false, false},
'\u0b07': &Key{"LaunchScreenSaver", "LaunchScreenSaver", "", "", 0, 0, false, false},
'\u0b08': &Key{"LaunchSpreadsheet", "LaunchSpreadsheet", "", "", 0, 0, false, false},
'\u0b09': &Key{"LaunchWebBrowser", "LaunchWebBrowser", "", "", 0, 0, false, false},
'\u0b0c': &Key{"LaunchContacts", "LaunchContacts", "", "", 0, 0, false, false},
'\u0b0d': &Key{"LaunchPhone", "LaunchPhone", "", "", 0, 0, false, false},
'\u0c01': &Key{"BrowserBack", "BrowserBack", "", "", 166, 166, false, false},
'\u0c02': &Key{"BrowserFavorites", "BrowserFavorites", "", "", 171, 171, false, false},
'\u0c03': &Key{"BrowserForward", "BrowserForward", "", "", 167, 167, false, false},
'\u0c04': &Key{"BrowserHome", "BrowserHome", "", "", 172, 172, false, false},
'\u0c05': &Key{"BrowserRefresh", "BrowserRefresh", "", "", 168, 168, false, false},
'\u0c06': &Key{"BrowserSearch", "BrowserSearch", "", "", 170, 170, false, false},
'\u0c07': &Key{"BrowserStop", "BrowserStop", "", "", 169, 169, false, false},
'\u0d0a': &Key{"ChannelDown", "ChannelDown", "", "", 0, 0, false, false},
'\u0d0b': &Key{"ChannelUp", "ChannelUp", "", "", 0, 0, false, false},
'\u0d12': &Key{"ClosedCaptionToggle", "ClosedCaptionToggle", "", "", 0, 0, false, false},
'\u0d15': &Key{"Exit", "Exit", "", "", 0, 0, false, false},
'\u0d22': &Key{"Guide", "Guide", "", "", 0, 0, false, false},
'\u0d25': &Key{"Info", "Info", "", "", 0, 0, false, false},
'\u0d2c': &Key{"MediaFastForward", "MediaFastForward", "", "", 0, 0, false, false},
'\u0d2d': &Key{"MediaLast", "MediaLast", "", "", 0, 0, false, false},
'\u0d2f': &Key{"MediaPlay", "MediaPlay", "", "", 0, 0, false, false},
'\u0d30': &Key{"MediaRecord", "MediaRecord", "", "", 0, 0, false, false},
'\u0d31': &Key{"MediaRewind", "MediaRewind", "", "", 0, 0, false, false},
'\u0d43': &Key{"Settings", "Settings", "", "", 0, 0, false, false},
'\u0d4e': &Key{"ZoomToggle", "ZoomToggle", "", "", 251, 251, false, false},
'\u0e02': &Key{"AudioBassBoostToggle", "AudioBassBoostToggle", "", "", 0, 0, false, false},
'\u0f02': &Key{"SpeechInputToggle", "SpeechInputToggle", "", "", 0, 0, false, false},
'\u1001': &Key{"SelectTask", "AppSwitch", "", "", 0, 0, false, false},
}

View File

@ -133,7 +133,7 @@ func Value(sel interface{}, value *string, opts ...QueryOption) Action {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
p := rundom.Evaluate(fmt.Sprintf(valueJS, nodes[0].XPath()))
p := rundom.Evaluate(fmt.Sprintf(valueJS, nodes[0].FullXPath()))
p.IncludeCommandLineAPI = true
p.ObjectGroup = "console"
@ -161,7 +161,7 @@ func SetValue(sel interface{}, value string, opts ...QueryOption) Action {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
p := rundom.Evaluate(fmt.Sprintf(setValueJS, nodes[0].XPath(), value))
p := rundom.Evaluate(fmt.Sprintf(setValueJS, nodes[0].FullXPath(), value))
p.IncludeCommandLineAPI = true
p.ObjectGroup = "console"
@ -190,7 +190,7 @@ func Text(sel interface{}, text *string, opts ...QueryOption) Action {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
p := rundom.Evaluate(fmt.Sprintf(textJS, nodes[0].XPath()))
p := rundom.Evaluate(fmt.Sprintf(textJS, nodes[0].FullXPath()))
p.IncludeCommandLineAPI = true
p.ObjectGroup = "console"
@ -429,7 +429,7 @@ func Submit(sel interface{}, opts ...QueryOption) Action {
return fmt.Errorf("selector `%s` did not return any nodes", sel)
}
p := rundom.Evaluate(fmt.Sprintf(submitJS, nodes[0].XPath()))
p := rundom.Evaluate(fmt.Sprintf(submitJS, nodes[0].FullXPath()))
p.IncludeCommandLineAPI = true
p.ObjectGroup = "console"