Expanding Evaluate API

- fixed issues with Evaluate
- added examples/eval
This commit is contained in:
Kenneth Shaw 2017-02-08 14:27:39 +07:00
parent 8c2c95f9a2
commit 465e7becad
10 changed files with 294 additions and 104 deletions

View File

@ -2,6 +2,7 @@ package runtime
import (
"errors"
"fmt"
"github.com/mailru/easyjson"
"github.com/mailru/easyjson/jlexer"
@ -181,6 +182,13 @@ type ExceptionDetails struct {
ExecutionContextID ExecutionContextID `json:"executionContextId,omitempty"` // Identifier of the context where exception happened.
}
// Error satisfies the error interface.
func (e *ExceptionDetails) Error() string {
// TODO: watch script parsed events and match the ExceptionDetails.ScriptID
// to the name/location of the actual code and display here
return fmt.Sprintf("encountered exception '%s' (%d:%d)", e.Text, e.LineNumber, e.ColumnNumber)
}
// CallFrame stack entry for runtime errors and assertions.
type CallFrame struct {
FunctionName string `json:"functionName,omitempty"` // JavaScript function name.

View File

@ -56,6 +56,7 @@ const (
// - 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.
func FixupDomains(domains []*internal.Domain) {
// method type
methodType := &internal.Type{
@ -309,9 +310,14 @@ func FixupDomains(domains []*internal.Domain) {
case internal.DomainRuntime:
var types []*internal.Type
for _, t := range d.Types {
if t.ID == "Timestamp" {
switch t.ID {
case "Timestamp":
continue
case "ExceptionDetails":
t.Extra += templates.ExtraExceptionDetailsTemplate()
}
types = append(types, t)
}
d.Types = types

View File

@ -195,6 +195,17 @@ func (t *{%s= typ %}) UnmarshalJSON(buf []byte) error {
}
{% endfunc %}
// ExtraExceptionDetailsTemplate is a special template for the Runtime.ExceptionDetails type that
// defines the standard error interface.
{% func ExtraExceptionDetailsTemplate() %}
// Error satisfies the error interface.
func (e *ExceptionDetails) Error() string {
// TODO: watch script parsed events and match the ExceptionDetails.ScriptID
// to the name/location of the actual code and display here
return fmt.Sprintf("encountered exception '%s' (%d:%d)", e.Text, e.LineNumber, e.ColumnNumber)
}
{% endfunc %}
// ExtraCDPTypes is the template for additional internal type
// declarations.
{% func ExtraCDPTypes() %}

View File

@ -420,12 +420,55 @@ func ExtraFixStringUnmarshaler(typ, parseFunc, extra string) string {
//line templates/extra.qtpl:196
}
// ExtraExceptionDetailsTemplate is a special template for the Runtime.ExceptionDetails type that
// defines the standard error interface.
//line templates/extra.qtpl:200
func StreamExtraExceptionDetailsTemplate(qw422016 *qt422016.Writer) {
//line templates/extra.qtpl:200
qw422016.N().S(`
// Error satisfies the error interface.
func (e *ExceptionDetails) Error() string {
// TODO: watch script parsed events and match the ExceptionDetails.ScriptID
// to the name/location of the actual code and display here
return fmt.Sprintf("encountered exception '%s' (%d:%d)", e.Text, e.LineNumber, e.ColumnNumber)
}
`)
//line templates/extra.qtpl:207
}
//line templates/extra.qtpl:207
func WriteExtraExceptionDetailsTemplate(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:207
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:207
StreamExtraExceptionDetailsTemplate(qw422016)
//line templates/extra.qtpl:207
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:207
}
//line templates/extra.qtpl:207
func ExtraExceptionDetailsTemplate() string {
//line templates/extra.qtpl:207
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:207
WriteExtraExceptionDetailsTemplate(qb422016)
//line templates/extra.qtpl:207
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:207
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:207
return qs422016
//line templates/extra.qtpl:207
}
// ExtraCDPTypes is the template for additional internal type
// declarations.
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
func StreamExtraCDPTypes(qw422016 *qt422016.Writer) {
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
qw422016.N().S(`
// Error satisfies the error interface.
@ -448,49 +491,49 @@ type FrameHandler interface {
// Empty is an empty JSON object message.
var Empty = easyjson.RawMessage(`)
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
qw422016.N().S("`")
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
qw422016.N().S(`{}`)
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
qw422016.N().S("`")
//line templates/extra.qtpl:200
//line templates/extra.qtpl:211
qw422016.N().S(`)
`)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
}
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
func WriteExtraCDPTypes(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
StreamExtraCDPTypes(qw422016)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
}
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
func ExtraCDPTypes() string {
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
WriteExtraCDPTypes(qb422016)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
return qs422016
//line templates/extra.qtpl:222
//line templates/extra.qtpl:233
}
// ExtraUtilTemplate generates the decode func for the Message type.
//line templates/extra.qtpl:225
//line templates/extra.qtpl:236
func StreamExtraUtilTemplate(qw422016 *qt422016.Writer, domains []*internal.Domain) {
//line templates/extra.qtpl:225
//line templates/extra.qtpl:236
qw422016.N().S(`
type empty struct{}
var emptyVal = &empty{}
@ -499,66 +542,66 @@ var emptyVal = &empty{}
func UnmarshalMessage(msg *cdp.Message) (interface{}, error) {
var v easyjson.Unmarshaler
switch msg.Method {`)
//line templates/extra.qtpl:232
//line templates/extra.qtpl:243
for _, d := range domains {
//line templates/extra.qtpl:232
//line templates/extra.qtpl:243
for _, c := range d.Commands {
//line templates/extra.qtpl:232
//line templates/extra.qtpl:243
qw422016.N().S(`
case cdp.`)
//line templates/extra.qtpl:233
//line templates/extra.qtpl:244
qw422016.N().S(c.CommandMethodType(d))
//line templates/extra.qtpl:233
//line templates/extra.qtpl:244
qw422016.N().S(`:`)
//line templates/extra.qtpl:233
//line templates/extra.qtpl:244
if len(c.Returns) == 0 {
//line templates/extra.qtpl:233
//line templates/extra.qtpl:244
qw422016.N().S(`
return emptyVal, nil`)
//line templates/extra.qtpl:234
//line templates/extra.qtpl:245
} else {
//line templates/extra.qtpl:234
//line templates/extra.qtpl:245
qw422016.N().S(`
v = new(`)
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
qw422016.N().S(d.PackageRefName())
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
qw422016.N().S(`.`)
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
qw422016.N().S(c.CommandReturnsType())
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
qw422016.N().S(`)`)
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
}
//line templates/extra.qtpl:235
//line templates/extra.qtpl:246
qw422016.N().S(`
`)
//line templates/extra.qtpl:236
//line templates/extra.qtpl:247
}
//line templates/extra.qtpl:236
//line templates/extra.qtpl:247
for _, e := range d.Events {
//line templates/extra.qtpl:236
//line templates/extra.qtpl:247
qw422016.N().S(`
case cdp.`)
//line templates/extra.qtpl:237
//line templates/extra.qtpl:248
qw422016.N().S(e.EventMethodType(d))
//line templates/extra.qtpl:237
//line templates/extra.qtpl:248
qw422016.N().S(`:
v = new(`)
//line templates/extra.qtpl:238
//line templates/extra.qtpl:249
qw422016.N().S(d.PackageRefName())
//line templates/extra.qtpl:238
//line templates/extra.qtpl:249
qw422016.N().S(`.`)
//line templates/extra.qtpl:238
//line templates/extra.qtpl:249
qw422016.N().S(e.EventType())
//line templates/extra.qtpl:238
//line templates/extra.qtpl:249
qw422016.N().S(`)
`)
//line templates/extra.qtpl:239
//line templates/extra.qtpl:250
}
//line templates/extra.qtpl:239
//line templates/extra.qtpl:250
}
//line templates/extra.qtpl:239
//line templates/extra.qtpl:250
qw422016.N().S(`}
var buf easyjson.RawMessage
@ -581,69 +624,69 @@ func UnmarshalMessage(msg *cdp.Message) (interface{}, error) {
return v, nil
}
`)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
}
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
func WriteExtraUtilTemplate(qq422016 qtio422016.Writer, domains []*internal.Domain) {
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
StreamExtraUtilTemplate(qw422016, domains)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
}
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
func ExtraUtilTemplate(domains []*internal.Domain) string {
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
WriteExtraUtilTemplate(qb422016, domains)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
return qs422016
//line templates/extra.qtpl:260
//line templates/extra.qtpl:271
}
//line templates/extra.qtpl:262
//line templates/extra.qtpl:273
func StreamExtraMethodTypeDomainDecoder(qw422016 *qt422016.Writer) {
//line templates/extra.qtpl:262
//line templates/extra.qtpl:273
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:267
//line templates/extra.qtpl:278
}
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
func WriteExtraMethodTypeDomainDecoder(qq422016 qtio422016.Writer) {
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
StreamExtraMethodTypeDomainDecoder(qw422016)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
qt422016.ReleaseWriter(qw422016)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
}
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
func ExtraMethodTypeDomainDecoder() string {
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
qb422016 := qt422016.AcquireByteBuffer()
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
WriteExtraMethodTypeDomainDecoder(qb422016)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
qs422016 := string(qb422016.B)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
qt422016.ReleaseByteBuffer(qb422016)
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
return qs422016
//line templates/extra.qtpl:267
//line templates/extra.qtpl:278
}

93
eval.go Normal file
View File

@ -0,0 +1,93 @@
package chromedp
import (
"context"
"encoding/json"
"github.com/knq/chromedp/cdp"
rundom "github.com/knq/chromedp/cdp/runtime"
)
// Evaluate evaluates the supplied Javascript expression, attempting to
// unmarshal the resulting value into res.
//
// If res is a **chromedp/cdp/runtime.RemoteObject, then it will be set to the
// raw, returned RuntimeObject, Otherwise, the result value be json.Unmarshal'd
// to res.
func Evaluate(expression string, res interface{}, opts ...EvaluateOption) Action {
if res == nil {
panic("res cannot be nil")
}
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
var err error
// check if we want a 'raw' result
obj, raw := res.(**rundom.RemoteObject)
// set up parameters
p := rundom.Evaluate(expression)
if !raw {
p = p.WithReturnByValue(true)
}
// apply opts
for _, o := range opts {
p = o(p)
}
// evaluate
v, exp, err := p.Do(ctxt, h)
if err != nil {
return err
}
if exp != nil {
return exp
}
if raw {
*obj = v
return nil
}
// unmarshal
return json.Unmarshal(v.Value, res)
})
}
// EvaluateOption is an Evaluate call option.
type EvaluateOption func(*rundom.EvaluateParams) *rundom.EvaluateParams
// EvalObjectGroup is a Evaluate option to set the object group.
func EvalObjectGroup(objectGroup string) EvaluateOption {
return func(p *rundom.EvaluateParams) *rundom.EvaluateParams {
return p.WithObjectGroup(objectGroup)
}
}
// EvalWithCommandLineAPI is an Evaluate option to include the DevTools Command
// Line API.
//
// Note: this should not be used with any untrusted code.
func EvalWithCommandLineAPI(p *rundom.EvaluateParams) *rundom.EvaluateParams {
return p.WithIncludeCommandLineAPI(true)
}
// EvalSilent is a Evaluate option that will cause script evaluation to ignore
// exceptions.
func EvalSilent(p *rundom.EvaluateParams) *rundom.EvaluateParams {
return p.WithSilent(true)
}
// EvalAsValue is a Evaluate option that will case the script to encode its
// result as a value.
func EvalAsValue(p *rundom.EvaluateParams) *rundom.EvaluateParams {
return p.WithReturnByValue(true)
}
// EvaluateAsDevTools evaluates a Javascript expression in the same
//
// Note: this should not be used with any untrusted code.
func EvaluateAsDevTools(expression string) Action {
return Evaluate(expression, EvalObjectGroup("console"), EvalWithCommandLineAPI)
}

2
examples/eval/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
eval
eval.exe

47
examples/eval/main.go Normal file
View File

@ -0,0 +1,47 @@
package main
import (
"context"
"log"
cdp "github.com/knq/chromedp"
)
func main() {
var err error
// create context
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
// create chrome instance
c, err := cdp.New(ctxt)
if err != nil {
log.Fatal(err)
}
// run task list
var res []string
err = c.Run(ctxt, cdp.Tasks{
cdp.Navigate(`https://www.brank.as`),
cdp.WaitVisible(`#footer`, cdp.ByID),
cdp.Evaluate(`Object.keys(window);`, &res),
})
if err != nil {
log.Fatal(err)
}
// shutdown chrome
err = c.Shutdown(ctxt)
if err != nil {
log.Fatal(err)
}
// wait for chrome to finish
err = c.Wait()
if err != nil {
log.Fatal(err)
}
log.Printf("window object keys: %v", res)
}

22
nav.go
View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"log"
"github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/page"
@ -121,25 +120,6 @@ func Stop() Action {
return page.StopLoading()
}
// Evaluate evaluates a script.
func Evaluate(expression string, res **rundom.RemoteObject) Action {
if res == nil {
panic("res cannot be nil")
}
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
v, exp, err := rundom.Evaluate(expression).Do(ctxt, h)
if err != nil {
return err
}
if exp != nil {
log.Printf(">>> GOT EXECPTION: %v", exp)
}
*res = v
return nil
})
}
// Location retrieves the URL location.
func Location(urlstr *string) Action {
if urlstr == nil {
@ -151,7 +131,7 @@ func Location(urlstr *string) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))

View File

@ -142,7 +142,7 @@ func Value(sel interface{}, value *string, opts ...QueryOption) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))
@ -170,7 +170,7 @@ func SetValue(sel interface{}, value string, opts ...QueryOption) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))
@ -199,7 +199,7 @@ func Text(sel interface{}, text *string, opts ...QueryOption) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))
@ -378,7 +378,7 @@ func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))
@ -438,7 +438,7 @@ func Submit(sel interface{}, opts ...QueryOption) Action {
return err
}
if exp != nil {
return fmt.Errorf("got exception evaluating script: %#v", exp)
return exp
}
if res.Type != rundom.TypeString || len(res.Value) < 2 {
return fmt.Errorf("expected string of at least length 2, got %s length %d", res.Subtype, len(res.Value))