chromedp / chromedp

A faster, simpler way to drive browsers supporting the Chrome DevTools Protocol.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to execute JavaScript in a specified context?

Lua12138 opened this issue · comments

What versions are you running?

$ go list -m github.com/chromedp/chromedp
github.com/chromedp/chromedp v0.9.5
$ google-chrome --version
123.0.6312.86
$ go version
1.22.0

What did you do? Include clear steps.

I want to execute JavaScript in a specific context, for example in a specific iframe in a web page.

This feature is shown in Chrome's DevTools like this

image

When I select a specific context, the JavaScript entered in the console will only run in that context.

I used a less elegant implementation here. Please let me know if there is a better way later.

example.go

func examples(){
  // Omit the content of the creation context.
        ctx, cancel := .......
	chromedp.ListenTarget(ctx, func(ev interface{}) {
		switch ev := ev.(type) {
		case *runtime.EventExecutionContextCreated:
			data := EventExecutionContextAuxData{}
			json.Unmarshal(ev.Context.AuxData, &data)
			if data.FrameId != "" {
				GlobalExecutionContextPool.Push(data.FrameId, ev.Context.UniqueID)
			}
		case *runtime.EventExecutionContextDestroyed:
			GlobalExecutionContextPool.RemoveByUniqueId(ev.ExecutionContextUniqueID)
		case *page.EventFrameNavigated:
			// This is my method for executing JavaScript after the frame is navigated, and matching UniqueID through frameId.
			go onTargetNavigated(ev, ctx)
	})
}

func onTargetNavigated(ev *page.EventFrameNavigated, ctx context.Context){
	if strings.HasPrefix(ev.Frame.URL, "http") {
		var err error
		if ev.Frame.ParentID == "" {
			// Parent Page/Non-iframe
		} else {
			uniqueId := ""
			deadline := time.Now().Add(5 * time.Second)
			for uniqueId == "" {
				time.Sleep(10 * time.Millisecond)
				uniqueId = GlobalExecutionContextPool.Find(ev.Frame.ID.String())
				if time.Now().UnixMilli() > deadline.UnixMilli() {
					break
				}
			}

			if uniqueId == "" {
				log.Println("cannot match frame execution context")
				return
			}
			err = chromedp.Run(
				ctx,
				chromedp.EvaluateAsDevTools(
					"alert('current page is ' + location.href)", // this is the script
					nil,
					func(ep *runtime.EvaluateParams) *runtime.EvaluateParams {
						return ep.WithUniqueContextID(uniqueId)
					},
				),
			)
		}
	}
}

type ExecutionContextPool struct {
	pool   map[string]string
	locker sync.RWMutex
}

var GlobalExecutionContextPool ExecutionContextPool = ExecutionContextPool{
	pool: map[string]string{},
}

func (s *ExecutionContextPool) Push(frameId string, uniqueId string) {
	log.Println("ExecutionContextPool.Push:", frameId, uniqueId)
	// s.pool.Store(frameId, uniqueId)
	s.locker.Lock()
	defer s.locker.Unlock()
	s.pool[frameId] = uniqueId
}
func (s *ExecutionContextPool) Find(frameId string) string {
	s.locker.RLock()
	defer s.locker.RUnlock()
	if val, ok := s.pool[frameId]; ok {
		return val
	}
	return ""
}
func (s *ExecutionContextPool) RemoveByFrameId(frameId string) {
	s.locker.Lock()
	defer s.locker.Unlock()
	delete(s.pool, frameId)
}
func (s *ExecutionContextPool) RemoveByUniqueId(uniqueId string) {
	s.locker.Lock()
	defer s.locker.Unlock()
	for k, v := range s.pool {
		if v == uniqueId {
			log.Println("ExecutionContextPool.RemoveByUniqueId", k, v)
			delete(s.pool, k)
			return
		}
	}
}