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
6import logging
7
8from telemetry.core.backends.chrome import inspector_backend
9
10
11def DebuggerUrlToId(debugger_url):
12  return debugger_url.split('/')[-1]
13
14
15class InspectorBackendList(collections.Sequence):
16  """A dynamic sequence of active InspectorBackends."""
17
18  def __init__(self, browser_backend, backend_wrapper):
19    """Constructor.
20
21    Args:
22      browser_backend: The BrowserBackend instance to query for
23          InspectorBackends.
24      backend_wrapper: A public interface for wrapping each
25          InspectorBackend. It must accept an argument of the
26          InspectorBackend to wrap, and an argument of the
27          InspectorBackendList, and may expose whatever methods
28          are desired on top of that backend.
29    """
30    self._browser_backend = browser_backend
31    # A ordered mapping of context IDs to inspectable contexts.
32    self._inspectable_contexts_dict = collections.OrderedDict()
33    # A cache of inspector backends, by context ID.
34    self._inspector_backend_dict = {}
35    # A wrapper class for InspectorBackends.
36    self._backend_wrapper = backend_wrapper
37
38  def GetContextInfo(self, context_id):
39    return self._inspectable_contexts_dict[context_id]
40
41  def ShouldIncludeContext(self, _context):
42    """Override this method to control which contexts are included."""
43    return True
44
45  #TODO(nednguyen): Remove this method and turn inspector_backend_list API to
46  # dictionary-like API (crbug.com/398467)
47  def __getitem__(self, index):
48    self._Update()
49    if index >= len(self._inspectable_contexts_dict.keys()):
50      logging.error('About to explode: _inspectable_contexts_dict.keys() = %s',
51                    repr({
52                      "index": index,
53                      "keys": self._inspectable_contexts_dict.keys()
54                    }))
55    context_id = self._inspectable_contexts_dict.keys()[index]
56    return self.GetBackendFromContextId(context_id)
57
58  def GetBackendFromContextId(self, context_id):
59    self._Update()
60    if context_id not in self._inspectable_contexts_dict:
61      raise KeyError('Cannot find a context with id=%s' % context_id)
62    if context_id not in self._inspector_backend_dict:
63      backend = inspector_backend.InspectorBackend(
64          self._browser_backend,
65          self._inspectable_contexts_dict[context_id])
66      backend = self._backend_wrapper(backend, self)
67      self._inspector_backend_dict[context_id] = backend
68    return self._inspector_backend_dict[context_id]
69
70  def __iter__(self):
71    self._Update()
72    return self._inspectable_contexts_dict.keys().__iter__()
73
74  def __len__(self):
75    self._Update()
76    return len(self._inspectable_contexts_dict)
77
78  def _Update(self):
79    contexts = self._browser_backend.ListInspectableContexts()
80    context_ids = [context['id'] for context in contexts]
81
82    # Append all new inspectable contexts to the dict.
83    for context in contexts:
84      if not self.ShouldIncludeContext(context):
85        continue
86      if context['id'] in self._inspectable_contexts_dict:
87        continue
88      self._inspectable_contexts_dict[context['id']] = context
89
90    # Remove all inspectable contexts that have gone away from the dict.
91    for context_id in self._inspectable_contexts_dict.keys():
92      if context_id not in context_ids:
93        del self._inspectable_contexts_dict[context_id]
94      else:
95        # Also remove inspectable contexts that have no websocketDebuggerUrls.
96        context = next(context for context in contexts
97                      if context['id'] == context_id)
98        if (context_id not in self._inspector_backend_dict.keys() and
99            'webSocketDebuggerUrl' not in context):
100          logging.debug('webSocketDebuggerUrl missing, removing %s'
101                        % context_id)
102          del self._inspectable_contexts_dict[context_id]
103
104    # Clean up any backends for contexts that have gone away.
105    for context_id in self._inspector_backend_dict.keys():
106      if context_id not in self._inspectable_contexts_dict:
107        del self._inspector_backend_dict[context_id]
108