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 }