github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/eval/function.go (about)

     1  package eval
     2  
     3  import (
     4  	"strconv"
     5  	"strings"
     6  
     7  	"github.com/mafredri/cdp/protocol/runtime"
     8  	"github.com/wI2L/jettison"
     9  
    10  	"github.com/MontFerret/ferret/pkg/drivers"
    11  	"github.com/MontFerret/ferret/pkg/runtime/core"
    12  )
    13  
    14  type Function struct {
    15  	exp        string
    16  	name       string
    17  	ownerID    runtime.RemoteObjectID
    18  	args       FunctionArguments
    19  	returnType ReturnType
    20  	async      bool
    21  }
    22  
    23  const (
    24  	defaultArgsCount = 5
    25  	expName          = "$exp"
    26  	compiledExpName  = "$$exp"
    27  )
    28  
    29  func F(exp string) *Function {
    30  	op := new(Function)
    31  	op.exp = exp
    32  	op.returnType = ReturnNothing
    33  
    34  	return op
    35  }
    36  
    37  func (fn *Function) CallOn(id runtime.RemoteObjectID) *Function {
    38  	fn.ownerID = id
    39  
    40  	return fn
    41  }
    42  
    43  func (fn *Function) AsAsync() *Function {
    44  	fn.async = true
    45  
    46  	return fn
    47  }
    48  
    49  func (fn *Function) AsSync() *Function {
    50  	fn.async = false
    51  
    52  	return fn
    53  }
    54  
    55  func (fn *Function) AsAnonymous() *Function {
    56  	fn.name = ""
    57  
    58  	return fn
    59  }
    60  
    61  func (fn *Function) AsNamed(name string) *Function {
    62  	if name != "" {
    63  		fn.name = name
    64  	}
    65  
    66  	return fn
    67  }
    68  
    69  func (fn *Function) WithArgRemoteValue(value RemoteValue) *Function {
    70  	return fn.WithArgRef(value.RemoteID())
    71  }
    72  
    73  func (fn *Function) WithArgRef(id runtime.RemoteObjectID) *Function {
    74  	return fn.withArg(runtime.CallArgument{
    75  		ObjectID: &id,
    76  	})
    77  }
    78  
    79  func (fn *Function) WithArgValue(value core.Value) *Function {
    80  	raw, err := value.MarshalJSON()
    81  
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  
    86  	return fn.withArg(runtime.CallArgument{
    87  		Value: raw,
    88  	})
    89  }
    90  
    91  func (fn *Function) WithArgSelector(selector drivers.QuerySelector) *Function {
    92  	return fn.WithArg(selector.String())
    93  }
    94  
    95  func (fn *Function) WithArg(value interface{}) *Function {
    96  	raw, err := jettison.MarshalOpts(value, jettison.NoHTMLEscaping())
    97  
    98  	if err != nil {
    99  		panic(err)
   100  	}
   101  
   102  	return fn.withArg(runtime.CallArgument{
   103  		Value: raw,
   104  	})
   105  }
   106  
   107  func (fn *Function) String() string {
   108  	return fn.exp
   109  }
   110  
   111  func (fn *Function) Length() int {
   112  	return len(fn.args)
   113  }
   114  
   115  func (fn *Function) returnNothing() *Function {
   116  	fn.returnType = ReturnNothing
   117  
   118  	return fn
   119  }
   120  
   121  func (fn *Function) returnRef() *Function {
   122  	fn.returnType = ReturnRef
   123  
   124  	return fn
   125  }
   126  
   127  func (fn *Function) returnValue() *Function {
   128  	fn.returnType = ReturnValue
   129  
   130  	return fn
   131  }
   132  
   133  func (fn *Function) withArg(arg runtime.CallArgument) *Function {
   134  	if fn.args == nil {
   135  		fn.args = make([]runtime.CallArgument, 0, defaultArgsCount)
   136  	}
   137  
   138  	fn.args = append(fn.args, arg)
   139  
   140  	return fn
   141  }
   142  
   143  func (fn *Function) eval(ctx runtime.ExecutionContextID) *runtime.CallFunctionOnArgs {
   144  	exp := fn.prepExp()
   145  
   146  	call := runtime.NewCallFunctionOnArgs(exp).
   147  		SetAwaitPromise(fn.async).
   148  		SetReturnByValue(fn.returnType == ReturnValue)
   149  
   150  	if fn.ownerID != EmptyObjectID {
   151  		call.SetObjectID(fn.ownerID)
   152  	} else if ctx != EmptyExecutionContextID {
   153  		call.SetExecutionContextID(ctx)
   154  	}
   155  
   156  	if len(fn.args) > 0 {
   157  		call.SetArguments(fn.args)
   158  	}
   159  
   160  	return call
   161  }
   162  
   163  func (fn *Function) compile(ctx runtime.ExecutionContextID) *runtime.CompileScriptArgs {
   164  	exp := fn.precompileExp()
   165  
   166  	call := runtime.NewCompileScriptArgs(exp, "", true)
   167  
   168  	if ctx != EmptyExecutionContextID {
   169  		call.SetExecutionContextID(ctx)
   170  	}
   171  
   172  	return call
   173  }
   174  
   175  func (fn *Function) prepExp() string {
   176  	var invoke bool
   177  	exp := strings.TrimSpace(fn.exp)
   178  	name := fn.name
   179  
   180  	// If the given expression is either an arrow or plain function
   181  	if strings.HasPrefix(exp, "(") || strings.HasPrefix(exp, "function") {
   182  		// And if this function must be an anonymous
   183  		// we just pass the expression as is without wrapping it.
   184  		if name == "" {
   185  			return exp
   186  		}
   187  
   188  		// But if the function must be identified (named)
   189  		// we need to wrap the given function with a named one/
   190  		// And then eval it with passing available arguments.
   191  		invoke = true
   192  	}
   193  
   194  	// Start building a wrapper
   195  	var buf strings.Builder
   196  	buf.WriteString("function")
   197  
   198  	// Name the function if the name is set
   199  	if name != "" {
   200  		buf.WriteString(" ")
   201  		buf.WriteString(name)
   202  	}
   203  
   204  	buf.WriteString("(")
   205  
   206  	// If the given expression is a function then we do not need to define wrapper's function arguments.
   207  	// Any available arguments will be passed down via 'arguments' runtime variable.
   208  	// Otherwise, we define a list of arguments as argN, so the given expression could access them by name.
   209  	if !invoke {
   210  		args := len(fn.args)
   211  		lastIndex := args - 1
   212  
   213  		for i := 0; i < args; i++ {
   214  			buf.WriteString("arg")
   215  			buf.WriteString(strconv.Itoa(i + 1))
   216  
   217  			if i != lastIndex {
   218  				buf.WriteString(",")
   219  			}
   220  		}
   221  	}
   222  
   223  	buf.WriteString(") {\n")
   224  
   225  	if !invoke {
   226  		buf.WriteString(exp)
   227  	} else {
   228  		buf.WriteString("const ")
   229  		buf.WriteString(expName)
   230  		buf.WriteString(" = ")
   231  		buf.WriteString(exp)
   232  		buf.WriteString(";\n")
   233  		buf.WriteString("return ")
   234  		buf.WriteString(expName)
   235  		buf.WriteString(".apply(this, arguments);")
   236  	}
   237  
   238  	buf.WriteString("\n}")
   239  
   240  	return buf.String()
   241  }
   242  
   243  func (fn *Function) precompileExp() string {
   244  	exp := fn.prepExp()
   245  	args := fn.args
   246  
   247  	var buf strings.Builder
   248  	var l = len(args)
   249  
   250  	buf.WriteString("const args = [")
   251  
   252  	if l > 0 {
   253  		for i := 0; i < l; i++ {
   254  			buf.WriteRune('\n')
   255  
   256  			arg := args[i]
   257  
   258  			if arg.Value != nil {
   259  				buf.Write(arg.Value)
   260  			} else if arg.ObjectID != nil {
   261  				buf.WriteString("(() => { throw new Error('Reference values cannot be used in pre-compiled scrips')})()")
   262  			}
   263  
   264  			buf.WriteString(",")
   265  		}
   266  
   267  		buf.WriteRune('\n')
   268  	}
   269  
   270  	buf.WriteString("];")
   271  
   272  	buf.WriteRune('\n')
   273  	buf.WriteString("const ")
   274  	buf.WriteString(compiledExpName)
   275  	buf.WriteString(" = ")
   276  	buf.WriteString(exp)
   277  	buf.WriteString(";\n")
   278  	buf.WriteString(compiledExpName)
   279  	buf.WriteString(".apply(this, args);")
   280  	buf.WriteString("\n")
   281  
   282  	return buf.String()
   283  }