| Index: tools/telemetry/telemetry/browser_backend.py
 | 
| diff --git a/tools/telemetry/telemetry/browser_backend.py b/tools/telemetry/telemetry/browser_backend.py
 | 
| index 57c4f93dd6716119a960447b6c9f23a4c2b1d47d..01257f5497dda8806df877b3120d3993092e222f 100644
 | 
| --- a/tools/telemetry/telemetry/browser_backend.py
 | 
| +++ b/tools/telemetry/telemetry/browser_backend.py
 | 
| @@ -11,6 +11,7 @@ import sys
 | 
|  import weakref
 | 
|  
 | 
|  from telemetry import browser_gone_exception
 | 
| +from telemetry import extension_page
 | 
|  from telemetry import tab
 | 
|  from telemetry import tracing_backend
 | 
|  from telemetry import user_agent
 | 
| @@ -23,26 +24,19 @@ class BrowserConnectionGoneException(
 | 
|      browser_gone_exception.BrowserGoneException):
 | 
|    pass
 | 
|  
 | 
| -
 | 
| -class TabController(object):
 | 
| +class ContextControler(object):
 | 
|    def __init__(self, browser, browser_backend):
 | 
|      self._browser = browser
 | 
|      self._browser_backend = browser_backend
 | 
| -
 | 
|      # Stores web socket debugger URLs in iteration order.
 | 
| -    self._tab_list = []
 | 
| +    self._context_list = []
 | 
|      # Maps debugger URLs to Tab objects.
 | 
| -    self._tab_dict = weakref.WeakValueDictionary()
 | 
| -
 | 
| -    self._UpdateTabList()
 | 
| -
 | 
| -  def New(self, timeout=None):
 | 
| -    self._browser_backend.Request('new', timeout=timeout)
 | 
| -    return self[-1]
 | 
| +    self._context_dict = weakref.WeakValueDictionary()
 | 
| +    self._UpdateContextList()
 | 
|  
 | 
|    def DoesDebuggerUrlExist(self, url):
 | 
| -    self._UpdateTabList()
 | 
| -    return url in self._tab_list
 | 
| +    self._UpdateContextList()
 | 
| +    return url in self._context_list
 | 
|  
 | 
|    def CloseTab(self, debugger_url, timeout=None):
 | 
|      # TODO(dtu): crbug.com/160946, allow closing the last tab on some platforms.
 | 
| @@ -81,52 +75,118 @@ class TabController(object):
 | 
|      return tab_info['url']
 | 
|  
 | 
|    def __iter__(self):
 | 
| -    self._UpdateTabList()
 | 
| -    return self._tab_list.__iter__()
 | 
| +    self._UpdateContextList()
 | 
| +    return self._context_list.__iter__()
 | 
|  
 | 
|    def __len__(self):
 | 
| -    self._UpdateTabList()
 | 
| -    return len(self._tab_list)
 | 
| +    self._UpdateContextList()
 | 
| +    return len(self._context_list)
 | 
|  
 | 
|    def __getitem__(self, index):
 | 
| -    self._UpdateTabList()
 | 
| +    self._UpdateContextList()
 | 
|      # This dereference will propagate IndexErrors.
 | 
| -    debugger_url = self._tab_list[index]
 | 
| +    debugger_url = self._context_list[index]
 | 
| +    return self._GetContextObjectForDebugger(debugger_url)
 | 
| +
 | 
| +  def FindByUrl(self, url):
 | 
| +    for tab_info in self._ListContexts():
 | 
| +      if tab_info['url'] == url:
 | 
| +        return self._GetContextObjectForDebugger(
 | 
| +            tab_info.get('webSocketDebuggerUrl'))
 | 
| +    return None
 | 
| +
 | 
| +  def GetAllForUrl(self, url):
 | 
| +    return [self._GetContextObjectForDebugger(
 | 
| +                tab_info.get('webSocketDebuggerUrl'))
 | 
| +            for tab_info in self._ListContexts()
 | 
| +            if tab_info['url'] == url]
 | 
| +
 | 
| +  def _GetContextObjectForDebugger(self, debugger_url):
 | 
|      # Lazily get/create a Tab object.
 | 
| -    tab_object = self._tab_dict.get(debugger_url)
 | 
| +    tab_object = self._context_dict.get(debugger_url)
 | 
|      if not tab_object:
 | 
| -      tab_object = tab.Tab(self._browser, self, debugger_url)
 | 
| -      self._tab_dict[debugger_url] = tab_object
 | 
| +      tab_object = self._CreateContextObject(debugger_url)
 | 
| +      self._context_dict[debugger_url] = tab_object
 | 
|      return tab_object
 | 
|  
 | 
| -  def _ListTabs(self, timeout=None):
 | 
| +  def _ListContexts(self, timeout=None):
 | 
|      try:
 | 
|        data = self._browser_backend.Request('', timeout=timeout)
 | 
|        all_contexts = json.loads(data)
 | 
| -      tabs = [ctx for ctx in all_contexts
 | 
| -              if not ctx['url'].startswith('chrome-extension://')]
 | 
| -      return tabs
 | 
| +      contexts = self._FilterContexts(all_contexts)
 | 
| +      return contexts
 | 
|      except (socket.error, httplib.BadStatusLine, urllib2.URLError):
 | 
|        if not self._browser_backend.IsBrowserRunning():
 | 
|          raise browser_gone_exception.BrowserGoneException()
 | 
|        raise BrowserConnectionGoneException()
 | 
|  
 | 
| -  def _UpdateTabList(self):
 | 
| +  def _UpdateContextList(self):
 | 
|      def GetDebuggerUrl(tab_info):
 | 
|        if 'webSocketDebuggerUrl' not in tab_info:
 | 
|          return None
 | 
|        return tab_info['webSocketDebuggerUrl']
 | 
| -    new_tab_list = map(GetDebuggerUrl, self._ListTabs())
 | 
| -    self._tab_list = [t for t in self._tab_list if t in new_tab_list]
 | 
| -    self._tab_list += [t for t in new_tab_list if t not in self._tab_list]
 | 
| -
 | 
| -  def _FindTabInfo(self, debugger_url):
 | 
| -    for tab_info in self._ListTabs():
 | 
| +    new_context_list = map(GetDebuggerUrl, self._ListContexts())
 | 
| +    self._context_list = [t for t in self._context_list
 | 
| +                          if t in new_context_list]
 | 
| +    self._context_list += [t for t in new_context_list
 | 
| +                           if t not in self._context_list]
 | 
| +
 | 
| +  def _FindContextInfo(self, debugger_url):
 | 
| +    for tab_info in self._ListContexts():
 | 
|        if tab_info.get('webSocketDebuggerUrl') == debugger_url:
 | 
|          return tab_info
 | 
|      return None
 | 
|  
 | 
|  
 | 
| +class TabController(ContextControler):
 | 
| +  def __init__(self, browser, browser_backend):
 | 
| +    super(TabController, self).__init__(browser, browser_backend)
 | 
| +
 | 
| +  def _CreateContextObject(self, debugger_url):
 | 
| +    return tab.Tab(self._browser, self, debugger_url)
 | 
| +
 | 
| +  def _FilterContexts(self, all_contexts):
 | 
| +    return [ctx for ctx in all_contexts
 | 
| +            if not ctx['url'].startswith('chrome-extension://')]
 | 
| +
 | 
| +  def New(self, timeout=None):
 | 
| +    self._browser_backend.Request('new', timeout=timeout)
 | 
| +    return self[-1]
 | 
| +
 | 
| +  def CloseTab(self, debugger_url, timeout=None):
 | 
| +    # TODO(dtu): crbug.com/160946, allow closing the last tab on some platforms.
 | 
| +    # For now, just create a new tab before closing the last tab.
 | 
| +    if len(self) <= 1:
 | 
| +      self.New()
 | 
| +
 | 
| +    tab_id = debugger_url.split('/')[-1]
 | 
| +    try:
 | 
| +      response = self._browser_backend.Request('close/%s' % tab_id,
 | 
| +                                               timeout=timeout)
 | 
| +    except urllib2.HTTPError:
 | 
| +      raise Exception('Unable to close tab, tab id not found: %s' % tab_id)
 | 
| +    assert response == 'Target is closing'
 | 
| +
 | 
| +    util.WaitFor(lambda: not self._FindContextInfo(debugger_url), timeout=5)
 | 
| +    self._UpdateContextList()
 | 
| +
 | 
| +  def GetTabUrl(self, debugger_url):
 | 
| +    tab_info = self._FindContextInfo(debugger_url)
 | 
| +    assert tab_info is not None
 | 
| +    return tab_info['url']
 | 
| +
 | 
| +class ExtensionController(ContextControler):
 | 
| +  def __init__(self, browser, browser_backend):
 | 
| +    super(ExtensionController, self).__init__(browser, browser_backend)
 | 
| +
 | 
| +  def _CreateContextObject(self, debugger_url):
 | 
| +    return extension_page.ExtensionPage(
 | 
| +        tab.Tab(self._browser, self, debugger_url))
 | 
| +
 | 
| +  def _FilterContexts(self, all_contexts):
 | 
| +    return [ctx for ctx in all_contexts
 | 
| +            if ctx['url'].startswith('chrome-extension://')]
 | 
| +
 | 
|  class BrowserBackend(object):
 | 
|    """A base class for browser backends. Provides basic functionality
 | 
|    once a remote-debugger port has been established."""
 | 
| @@ -137,6 +197,7 @@ class BrowserBackend(object):
 | 
|  
 | 
|    def __init__(self, is_content_shell, options):
 | 
|      self.tabs = None
 | 
| +    self.extensions = None
 | 
|      self.browser_type = options.browser_type
 | 
|      self.is_content_shell = is_content_shell
 | 
|      self.options = options
 | 
| @@ -155,6 +216,8 @@ class BrowserBackend(object):
 | 
|  
 | 
|    def SetBrowser(self, browser):
 | 
|      self.tabs = TabController(browser, self)
 | 
| +    if not self.is_content_shell:
 | 
| +      self.extensions = ExtensionController(browser, self)
 | 
|  
 | 
|    def GetBrowserStartupArgs(self):
 | 
|      args = []
 | 
| 
 |