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