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

     1  package dom
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"github.com/mafredri/cdp"
     8  	"github.com/mafredri/cdp/protocol/page"
     9  	"github.com/mafredri/cdp/protocol/runtime"
    10  	"github.com/pkg/errors"
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
    14  	"github.com/MontFerret/ferret/pkg/drivers/cdp/input"
    15  	"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
    16  	"github.com/MontFerret/ferret/pkg/runtime/core"
    17  	"github.com/MontFerret/ferret/pkg/runtime/logging"
    18  	"github.com/MontFerret/ferret/pkg/runtime/values"
    19  )
    20  
    21  type Manager struct {
    22  	mu        sync.RWMutex
    23  	logger    zerolog.Logger
    24  	client    *cdp.Client
    25  	mouse     *input.Mouse
    26  	keyboard  *input.Keyboard
    27  	mainFrame *AtomicFrameID
    28  	frames    *AtomicFrameCollection
    29  }
    30  
    31  func New(
    32  	logger zerolog.Logger,
    33  	client *cdp.Client,
    34  	mouse *input.Mouse,
    35  	keyboard *input.Keyboard,
    36  ) (manager *Manager, err error) {
    37  
    38  	manager = new(Manager)
    39  	manager.logger = logging.WithName(logger.With(), "dom_manager").Logger()
    40  	manager.client = client
    41  	manager.mouse = mouse
    42  	manager.keyboard = keyboard
    43  	manager.mainFrame = NewAtomicFrameID()
    44  	manager.frames = NewAtomicFrameCollection()
    45  
    46  	return manager, nil
    47  }
    48  
    49  func (m *Manager) Close() error {
    50  	errs := make([]error, 0, m.frames.Length()+1)
    51  
    52  	m.frames.ForEach(func(f Frame, key page.FrameID) bool {
    53  		// if initialized
    54  		if f.node != nil {
    55  			if err := f.node.Close(); err != nil {
    56  				errs = append(errs, err)
    57  			}
    58  		}
    59  
    60  		return true
    61  	})
    62  
    63  	if len(errs) > 0 {
    64  		return core.Errors(errs...)
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func (m *Manager) LoadRootDocument(ctx context.Context) (*HTMLDocument, error) {
    71  	ftRepl, err := m.client.Page.GetFrameTree(ctx)
    72  
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return m.LoadDocument(ctx, ftRepl.FrameTree)
    78  }
    79  
    80  func (m *Manager) LoadDocument(ctx context.Context, frame page.FrameTree) (*HTMLDocument, error) {
    81  	exec, err := eval.Create(ctx, m.logger, m.client, frame.Frame.ID)
    82  
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	inputs := input.New(m.logger, m.client, exec, m.keyboard, m.mouse)
    88  
    89  	ref, err := exec.EvalRef(ctx, templates.GetDocument())
    90  
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "failed to load root element")
    93  	}
    94  
    95  	exec.SetLoader(NewNodeLoader(m))
    96  
    97  	rootElement := NewHTMLElement(
    98  		m.logger,
    99  		m.client,
   100  		m,
   101  		inputs,
   102  		exec,
   103  		*ref.ObjectID,
   104  	)
   105  
   106  	return NewHTMLDocument(
   107  		m.logger,
   108  		m.client,
   109  		m,
   110  		inputs,
   111  		exec,
   112  		rootElement,
   113  		frame,
   114  	), nil
   115  }
   116  
   117  func (m *Manager) ResolveElement(ctx context.Context, frameID page.FrameID, id runtime.RemoteObjectID) (*HTMLElement, error) {
   118  	doc, err := m.GetFrameNode(ctx, frameID)
   119  
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	return NewHTMLElement(
   125  		m.logger,
   126  		m.client,
   127  		m,
   128  		doc.input,
   129  		doc.eval,
   130  		id,
   131  	), nil
   132  }
   133  
   134  func (m *Manager) GetMainFrame() *HTMLDocument {
   135  	m.mu.RLock()
   136  	defer m.mu.RUnlock()
   137  
   138  	mainFrameID := m.mainFrame.Get()
   139  
   140  	if mainFrameID == "" {
   141  		return nil
   142  	}
   143  
   144  	mainFrame, exists := m.frames.Get(mainFrameID)
   145  
   146  	if exists {
   147  		return mainFrame.node
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func (m *Manager) SetMainFrame(doc *HTMLDocument) {
   154  	m.mu.Lock()
   155  	defer m.mu.Unlock()
   156  
   157  	mainFrameID := m.mainFrame.Get()
   158  
   159  	if mainFrameID != "" {
   160  		if err := m.removeFrameRecursivelyInternal(mainFrameID); err != nil {
   161  			m.logger.Error().Err(err).Msg("failed to close previous main frame")
   162  		}
   163  	}
   164  
   165  	m.mainFrame.Set(doc.frameTree.Frame.ID)
   166  
   167  	m.addPreloadedFrame(doc)
   168  }
   169  
   170  func (m *Manager) AddFrame(frame page.FrameTree) {
   171  	m.mu.RLock()
   172  	defer m.mu.RUnlock()
   173  
   174  	m.addFrameInternal(frame)
   175  }
   176  
   177  func (m *Manager) RemoveFrame(frameID page.FrameID) error {
   178  	m.mu.RLock()
   179  	defer m.mu.RUnlock()
   180  
   181  	return m.removeFrameInternal(frameID)
   182  }
   183  
   184  func (m *Manager) RemoveFrameRecursively(frameID page.FrameID) error {
   185  	m.mu.RLock()
   186  	defer m.mu.RUnlock()
   187  
   188  	return m.removeFrameRecursivelyInternal(frameID)
   189  }
   190  
   191  func (m *Manager) RemoveFramesByParentID(parentFrameID page.FrameID) error {
   192  	m.mu.RLock()
   193  	defer m.mu.RUnlock()
   194  
   195  	frame, found := m.frames.Get(parentFrameID)
   196  
   197  	if !found {
   198  		return errors.New("frame not found")
   199  	}
   200  
   201  	for _, child := range frame.tree.ChildFrames {
   202  		if err := m.removeFrameRecursivelyInternal(child.Frame.ID); err != nil {
   203  			return err
   204  		}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func (m *Manager) GetFrameNode(ctx context.Context, frameID page.FrameID) (*HTMLDocument, error) {
   211  	return m.getFrameInternal(ctx, frameID)
   212  }
   213  
   214  func (m *Manager) GetFrameTree(_ context.Context, frameID page.FrameID) (page.FrameTree, error) {
   215  	m.mu.RLock()
   216  	defer m.mu.RUnlock()
   217  
   218  	frame, found := m.frames.Get(frameID)
   219  
   220  	if !found {
   221  		return page.FrameTree{}, core.ErrNotFound
   222  	}
   223  
   224  	return frame.tree, nil
   225  }
   226  
   227  func (m *Manager) GetFrameNodes(ctx context.Context) (*values.Array, error) {
   228  	m.mu.RLock()
   229  	defer m.mu.RUnlock()
   230  
   231  	arr := values.NewArray(m.frames.Length())
   232  
   233  	for _, f := range m.frames.ToSlice() {
   234  		doc, err := m.getFrameInternal(ctx, f.tree.Frame.ID)
   235  
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  
   240  		arr.Push(doc)
   241  	}
   242  
   243  	return arr, nil
   244  }
   245  
   246  func (m *Manager) addFrameInternal(frame page.FrameTree) {
   247  	m.frames.Set(frame.Frame.ID, Frame{
   248  		tree: frame,
   249  		node: nil,
   250  	})
   251  
   252  	for _, child := range frame.ChildFrames {
   253  		m.addFrameInternal(child)
   254  	}
   255  }
   256  
   257  func (m *Manager) addPreloadedFrame(doc *HTMLDocument) {
   258  	m.frames.Set(doc.frameTree.Frame.ID, Frame{
   259  		tree: doc.frameTree,
   260  		node: doc,
   261  	})
   262  
   263  	for _, child := range doc.frameTree.ChildFrames {
   264  		m.addFrameInternal(child)
   265  	}
   266  }
   267  
   268  func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (*HTMLDocument, error) {
   269  	frame, found := m.frames.Get(frameID)
   270  
   271  	if !found {
   272  		return nil, core.ErrNotFound
   273  	}
   274  
   275  	// frame is initialized
   276  	if frame.node != nil {
   277  		return frame.node, nil
   278  	}
   279  
   280  	// the frame is not loaded yet
   281  	doc, err := m.LoadDocument(ctx, frame.tree)
   282  
   283  	if err != nil {
   284  		return nil, errors.Wrap(err, "failed to load frame document")
   285  	}
   286  
   287  	frame.node = doc
   288  
   289  	return doc, nil
   290  }
   291  
   292  func (m *Manager) removeFrameInternal(frameID page.FrameID) error {
   293  	current, exists := m.frames.Get(frameID)
   294  
   295  	if !exists {
   296  		return core.Error(core.ErrNotFound, "frame")
   297  	}
   298  
   299  	m.frames.Remove(frameID)
   300  
   301  	mainFrameID := m.mainFrame.Get()
   302  
   303  	if frameID == mainFrameID {
   304  		m.mainFrame.Reset()
   305  	}
   306  
   307  	if current.node == nil {
   308  		return nil
   309  	}
   310  
   311  	return current.node.Close()
   312  }
   313  
   314  func (m *Manager) removeFrameRecursivelyInternal(frameID page.FrameID) error {
   315  	parent, exists := m.frames.Get(frameID)
   316  
   317  	if !exists {
   318  		return core.Error(core.ErrNotFound, "frame")
   319  	}
   320  
   321  	for _, child := range parent.tree.ChildFrames {
   322  		if err := m.removeFrameRecursivelyInternal(child.Frame.ID); err != nil {
   323  			return err
   324  		}
   325  	}
   326  
   327  	return m.removeFrameInternal(frameID)
   328  }