44
55from lxml .etree import XPath , XPathSyntaxError
66
7- from browserdebuggertools .exceptions import (
8- InvalidXPathError , ResourceNotFoundError , IFrameNotFoundError
9- )
7+ from browserdebuggertools .exceptions import InvalidXPathError , ResourceNotFoundError
108from browserdebuggertools .sockethandler import SocketHandler
119
1210
@@ -34,7 +32,7 @@ def __init__(self, port, timeout=30, domains=None):
3432 is a dictionary of the arguments passed with the domain upon enabling.
3533 """
3634 self ._socket_handler = SocketHandler (port , timeout , domains = domains ) # type: SocketHandler
37- self ._node_map = {}
35+ self ._dom_manager = _DOMManager ( self . _socket_handler )
3836
3937 def quit (self ):
4038 self ._socket_handler .close ()
@@ -43,6 +41,7 @@ def reset(self):
4341 """ Clears all stored messages
4442 """
4543 self ._socket_handler .reset ()
44+ self ._dom_manager .reset ()
4645
4746 def get_events (self , domain , clear = False ):
4847 """ Retrieves all events for a given domain
@@ -208,7 +207,7 @@ def get_iframe_source_content(self, xpath):
208207 except XPathSyntaxError :
209208 raise InvalidXPathError ("{0} is not a valid xpath" .format (xpath ))
210209
211- return self ._get_iframe_html (xpath )
210+ return self ._dom_manager . get_iframe_html (xpath )
212211
213212 def get_page_source (self ):
214213 # type: () -> str
@@ -219,65 +218,90 @@ def get_page_source(self):
219218 :return: HTML markup
220219 """
221220
222- root_node_id = self ._socket_handler .event_handlers ["PageLoad" ].get_root_node_id ()
223- return self ._get_outer_html (root_node_id )
221+ root_node_id = self ._socket_handler .event_handlers ["PageLoad" ].get_root_backend_node_id ()
222+ return self ._dom_manager .get_outer_html (root_node_id )
223+
224224
225- def _get_iframe_html (self , xpath , _attempts = 0 ):
226- # type: (str, int) -> str
225+ class _DOMManager (object ):
226+
227+ def __init__ (self , socket_handler ):
228+ self ._socket_handler = socket_handler
229+ self ._node_map = {}
230+
231+ def get_outer_html (self , backend_node_id ):
232+ # type: (int) -> str
233+ return self ._socket_handler .execute (
234+ "DOM" , "getOuterHTML" , {"backendNodeId" : backend_node_id }
235+ )["outerHTML" ]
236+
237+ def get_iframe_html (self , xpath ):
238+ # type: (str) -> str
227239
228240 backend_node_id = self ._get_iframe_backend_node_id (xpath )
229241 try :
230- return self ._get_outer_html (backend_node_id )
242+ return self .get_outer_html (backend_node_id )
231243 except ResourceNotFoundError :
232244 # The cached node doesn't exist any more, so we need to find a new one that matches
233245 # the xpath. Backend node IDs are unique, so there is not a risk of getting the
234246 # outer html of the wrong node.
235- if _attempts < 1 :
236- if xpath in self ._node_map :
237- del self ._node_map [xpath ]
238- return self ._get_iframe_html (xpath , _attempts = _attempts + 1 )
239-
240- raise IFrameNotFoundError ()
247+ if xpath in self ._node_map :
248+ del self ._node_map [xpath ]
249+ backend_node_id = self ._get_iframe_backend_node_id (xpath )
250+ return self .get_outer_html (backend_node_id )
241251
242252 def _get_iframe_backend_node_id (self , xpath ):
243253 # type: (str) -> int
244254
245255 if xpath in self ._node_map :
246256 return self ._node_map [xpath ]
247257
258+ node_info = self ._get_info_for_first_matching_node (xpath )
248259 try :
249- node_info = self . _get_info_for_first_matching_node ( xpath )
260+
250261 backend_node_id = node_info ["node" ]["contentDocument" ]["backendNodeId" ]
251- except (KeyError , ResourceNotFoundError ):
252- # Either we couldn't find a node matching the xpath, or we did find a node but it's
253- # not an iframe.
254- raise IFrameNotFoundError ()
262+ except KeyError :
263+ raise ResourceNotFoundError ("The node found by xpath '%s' is not an iframe" % xpath )
255264
256265 self ._node_map [xpath ] = backend_node_id
257266 return backend_node_id
258267
259268 def _get_info_for_first_matching_node (self , xpath ):
260269 # type: (str) -> dict
261270
271+ with self ._get_node_ids (xpath ) as node_ids :
272+ if node_ids :
273+ return self ._describe_node (node_ids [0 ])
274+ raise ResourceNotFoundError ("No matching nodes for xpath: %s" % xpath )
275+
276+ @contextlib .contextmanager
277+ def _get_node_ids (self , xpath , max_matches = 1 ):
278+ # type: (str, int) -> list
279+
280+ assert max_matches > 0
262281 search_info = self ._perform_search (xpath )
263- if search_info .get ("resultCount" , 0 ) > 0 :
264- search_results = self ._get_search_results (search_info ["searchId" ], 0 , 1 )
282+ try :
283+ results = []
284+ if search_info ["resultCount" ] > 0 :
285+ results = self ._get_search_results (
286+ search_info ["searchId" ], 0 , min ([max_matches , search_info ["resultCount" ]])
287+ )["nodeIds" ]
288+ yield results
289+
290+ finally :
265291 self ._discard_search (search_info ["searchId" ])
266- return self ._describe_node (search_results ["nodeIds" ].pop ())
267- raise ResourceNotFoundError ("No matching nodes for xpath: %s" % xpath )
268292
269293 def _perform_search (self , xpath ):
270294 # type: (str) -> dict
271295
272296 # DOM.getDocument must have been called on the current page first otherwise performSearch
273297 # returns an array of 0s.
274298 self ._socket_handler .event_handlers ["PageLoad" ].check_page_load ()
275- return self .execute ("DOM" , "performSearch" , {"query" : xpath })
299+ return self ._socket_handler . execute ("DOM" , "performSearch" , {"query" : xpath })
276300
277301 def _get_search_results (self , search_id , from_index , to_index ):
278302 # type: (str, int, int) -> dict
279303
280- return self .execute ("DOM" , "getSearchResults" , {
304+ return self ._socket_handler . execute ("DOM" , "getSearchResults" , {
281305 "searchId" : search_id , "fromIndex" : from_index , "toIndex" : to_index
282306 })
283307
@@ -295,9 +319,5 @@ def _describe_node(self, node_id):
295319
296320 return self ._socket_handler .execute ("DOM" , "describeNode" , {"nodeId" : node_id })
297321
298- def _get_outer_html (self , backend_node_id ):
299- # type: (int) -> str
300-
301- return self .execute (
302- "DOM" , "getOuterHTML" , {"backendNodeId" : backend_node_id }
303- )["outerHTML" ]
322+ def reset (self ):
323+ self ._node_map = {}
0 commit comments