1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6from telemetry.core import exceptions 7 8 9def DebuggerUrlToId(debugger_url): 10 return debugger_url.split('/')[-1] 11 12 13class InspectorBackendList(collections.Sequence): 14 """A dynamic sequence of active InspectorBackends.""" 15 16 def __init__(self, browser_backend): 17 """Constructor. 18 19 Args: 20 browser_backend: The BrowserBackend instance to query for 21 InspectorBackends. 22 """ 23 self._browser_backend = browser_backend 24 self._devtools_context_map_backend = None 25 # A list of filtered contexts. 26 self._filtered_context_ids = [] 27 # A cache of inspector backends, by context ID. 28 self._wrapper_dict = {} 29 30 @property 31 def _devtools_client(self): 32 return self._browser_backend.devtools_client 33 34 @property 35 def app(self): 36 return self._browser_backend.app 37 38 def GetContextInfo(self, context_id): 39 return self._devtools_context_map_backend.GetContextInfo(context_id) 40 41 def ShouldIncludeContext(self, _): 42 """Override this method to control which contexts are included.""" 43 return True 44 45 def CreateWrapper(self, inspector_backend_instance): 46 """Override to return the wrapper API over InspectorBackend. 47 48 The wrapper API is the public interface for InspectorBackend. It 49 may expose whatever methods are desired on top of that backend. 50 """ 51 raise NotImplementedError 52 53 # TODO(nednguyen): Remove this method and turn inspector_backend_list API to 54 # dictionary-like API (crbug.com/398467) 55 def __getitem__(self, index): 56 self._Update() 57 if index >= len(self._filtered_context_ids): 58 raise exceptions.DevtoolsTargetCrashException( 59 self.app, 60 'Web content with index %s may have crashed. ' 61 'filtered_context_ids = %s' % ( 62 index, repr(self._filtered_context_ids))) 63 context_id = self._filtered_context_ids[index] 64 return self.GetBackendFromContextId(context_id) 65 66 def GetTabById(self, identifier): 67 self._Update() 68 return self.GetBackendFromContextId(identifier) 69 70 def GetBackendFromContextId(self, context_id): 71 self._Update() 72 if context_id not in self._wrapper_dict: 73 try: 74 backend = self._devtools_context_map_backend.GetInspectorBackend( 75 context_id) 76 except exceptions.Error as e: 77 self._HandleDevToolsConnectionError(e) 78 raise e 79 # Propagate KeyError from GetInspectorBackend call. 80 81 wrapper = self.CreateWrapper(backend) 82 self._wrapper_dict[context_id] = wrapper 83 return self._wrapper_dict[context_id] 84 85 def IterContextIds(self): 86 self._Update() 87 return iter(self._filtered_context_ids) 88 89 def __iter__(self): 90 self._Update() 91 for context_id in self._filtered_context_ids: 92 yield self.GetTabById(context_id) 93 94 def __len__(self): 95 self._Update() 96 return len(self._filtered_context_ids) 97 98 def _Update(self): 99 backends_map = self._devtools_client.GetUpdatedInspectableContexts() 100 self._devtools_context_map_backend = backends_map 101 102 # Clear context ids that do not appear in the inspectable contexts. 103 context_ids = [context['id'] for context in backends_map.contexts] 104 self._filtered_context_ids = [context_id 105 for context_id in self._filtered_context_ids 106 if context_id in context_ids] 107 # Add new context ids. 108 for context in backends_map.contexts: 109 if (context['id'] not in self._filtered_context_ids and 110 self.ShouldIncludeContext(context)): 111 self._filtered_context_ids.append(context['id']) 112 113 # Clean up any backends for contexts that have gone away. 114 for context_id in self._wrapper_dict.keys(): 115 if context_id not in self._filtered_context_ids: 116 del self._wrapper_dict[context_id] 117 118 def _HandleDevToolsConnectionError(self, error): 119 """Called when handling errors in connecting to the DevTools websocket. 120 121 This can be overwritten by sub-classes to add more debugging information to 122 errors. 123 """ 124 pass 125