Commit ed1744f6 authored by achuith@chromium.org's avatar achuith@chromium.org

Telemetry support for extensions.

* Browser has new properties supports_extensions and extensions (of type ExtensionDict)
* BrowserBackend ctor takes additional arg supports_extensions. 
* BrowserBackend has new properties supports_extensions and extension_dict_backend (of type ExtensionDictBackend)
* BrowserBackend._WaitForBrowserToComeUp waits for all extensions to load and be interactive.
* BrowserBackend.ExtensionsNotSupportedException is raised for android browser and content shell.
* Add class ExtensionToLoad which is a wrapper for an extension_path.
* Add extension_to_load (of type ExtensionToLoad) to BrowserOptions.
* Add SupportsOptions to PossibleBrowser, to test extensions support. Returns False for android and content_shell when BrowserOptions.extensions_to_load is not empty 
* Add ExtensionPage which derives from WebContents
* Add ExtensionDictBackend which is a dictionary of ExtensionPage instances with extension ids as keys.
* Add ExtensionDict which passes through to ExtensionDictBackend, except it uses ExtensionToLoad instances as keys.
* Import crx_id for determining extension id from extension path.
* Simple extension manifest.json and background.js for unit tests.
* Unit tests for single and multiple extensions.

BUG=169954
TEST=browser test.
Review URL: https://codereview.chromium.org/11882033

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182316 0039d316-1c4b-4281-b951-d872f2087c98
parent 069fa64d
...@@ -17,9 +17,14 @@ from telemetry import browser_gone_exception ...@@ -17,9 +17,14 @@ from telemetry import browser_gone_exception
class AndroidBrowserBackend(browser_backend.BrowserBackend): class AndroidBrowserBackend(browser_backend.BrowserBackend):
"""The backend for controlling a browser instance running on Android. """The backend for controlling a browser instance running on Android.
""" """
def __init__(self, options, adb, package, def __init__(self, options, adb, package, is_content_shell,
is_content_shell, cmdline_file, activity, devtools_remote_port): cmdline_file, activity, devtools_remote_port):
super(AndroidBrowserBackend, self).__init__(is_content_shell, options) super(AndroidBrowserBackend, self).__init__(
is_content_shell=is_content_shell,
supports_extensions=False, options=options)
if len(options.extensions_to_load) > 0:
raise browser_backend.ExtensionsNotSupportedException(
'Android browser does not support extensions.')
# Initialize fields so that an explosion during init doesn't break in Close. # Initialize fields so that an explosion during init doesn't break in Close.
self._options = options self._options = options
self._adb = adb self._adb = adb
......
...@@ -44,8 +44,7 @@ CONTENT_SHELL_DEVTOOLS_REMOTE_PORT = ( ...@@ -44,8 +44,7 @@ CONTENT_SHELL_DEVTOOLS_REMOTE_PORT = (
class PossibleAndroidBrowser(possible_browser.PossibleBrowser): class PossibleAndroidBrowser(possible_browser.PossibleBrowser):
"""A launchable android browser instance.""" """A launchable android browser instance."""
def __init__(self, browser_type, options, *args): def __init__(self, browser_type, options, *args):
super(PossibleAndroidBrowser, self).__init__( super(PossibleAndroidBrowser, self).__init__(browser_type, options)
browser_type, options)
self._args = args self._args = args
def __repr__(self): def __repr__(self):
...@@ -61,6 +60,11 @@ class PossibleAndroidBrowser(possible_browser.PossibleBrowser): ...@@ -61,6 +60,11 @@ class PossibleAndroidBrowser(possible_browser.PossibleBrowser):
backend.SetBrowser(b) backend.SetBrowser(b)
return b return b
def SupportsOptions(self, options):
if len(options.extensions_to_load) != 0:
return False
return True
def FindAllAvailableBrowsers(options, logging=real_logging): def FindAllAvailableBrowsers(options, logging=real_logging):
"""Finds all the desktop browsers available on this machine.""" """Finds all the desktop browsers available on this machine."""
if not adb_commands.IsAndroidSupported(): if not adb_commands.IsAndroidSupported():
......
...@@ -3,13 +3,14 @@ ...@@ -3,13 +3,14 @@
# found in the LICENSE file. # found in the LICENSE file.
import os import os
from telemetry import browser_backend
from telemetry import browser_credentials from telemetry import browser_credentials
from telemetry import extension_dict
from telemetry import tab_list from telemetry import tab_list
from telemetry import temporary_http_server from telemetry import temporary_http_server
from telemetry import wpr_modes from telemetry import wpr_modes
from telemetry import wpr_server from telemetry import wpr_server
class Browser(object): class Browser(object):
"""A running browser instance that can be controlled in a limited way. """A running browser instance that can be controlled in a limited way.
...@@ -21,12 +22,16 @@ class Browser(object): ...@@ -21,12 +22,16 @@ class Browser(object):
with browser_to_create.Create() as browser: with browser_to_create.Create() as browser:
... do all your operations on browser here ... do all your operations on browser here
""" """
def __init__(self, browser_backend, platform): def __init__(self, backend, platform):
self._browser_backend = browser_backend self._browser_backend = backend
self._http_server = None self._http_server = None
self._wpr_server = None self._wpr_server = None
self._platform = platform self._platform = platform
self._tabs = tab_list.TabList(browser_backend.tab_list_backend) self._tabs = tab_list.TabList(backend.tab_list_backend)
self._extensions = None
if backend.supports_extensions:
self._extensions = extension_dict.ExtensionDict(
backend.extension_dict_backend)
self.credentials = browser_credentials.BrowserCredentials() self.credentials = browser_credentials.BrowserCredentials()
def __enter__(self): def __enter__(self):
...@@ -48,6 +53,10 @@ class Browser(object): ...@@ -48,6 +53,10 @@ class Browser(object):
"""Returns whether this browser is a content shell, only.""" """Returns whether this browser is a content shell, only."""
return self._browser_backend.is_content_shell return self._browser_backend.is_content_shell
@property
def supports_extensions(self):
return self._browser_backend.supports_extensions
@property @property
def supports_tab_control(self): def supports_tab_control(self):
return self._browser_backend.supports_tab_control return self._browser_backend.supports_tab_control
...@@ -56,6 +65,14 @@ class Browser(object): ...@@ -56,6 +65,14 @@ class Browser(object):
def tabs(self): def tabs(self):
return self._tabs return self._tabs
@property
def extensions(self):
"""Returns the extension dictionary if it exists."""
if not self.supports_extensions:
raise browser_backend.ExtensionsNotSupportedException(
'Extensions not supported')
return self._extensions
@property @property
def supports_tracing(self): def supports_tracing(self):
return self._browser_backend.supports_tracing return self._browser_backend.supports_tracing
......
...@@ -10,6 +10,7 @@ import re ...@@ -10,6 +10,7 @@ import re
import sys import sys
from telemetry import browser_gone_exception from telemetry import browser_gone_exception
from telemetry import extension_dict_backend
from telemetry import options_for_unittests from telemetry import options_for_unittests
from telemetry import tab_list_backend from telemetry import tab_list_backend
from telemetry import tracing_backend from telemetry import tracing_backend
...@@ -18,15 +19,19 @@ from telemetry import util ...@@ -18,15 +19,19 @@ from telemetry import util
from telemetry import wpr_modes from telemetry import wpr_modes
from telemetry import wpr_server from telemetry import wpr_server
class ExtensionsNotSupportedException(Exception):
pass
class BrowserBackend(object): class BrowserBackend(object):
"""A base class for browser backends. Provides basic functionality """A base class for browser backends. Provides basic functionality
once a remote-debugger port has been established.""" once a remote-debugger port has been established."""
WEBPAGEREPLAY_HOST = '127.0.0.1' WEBPAGEREPLAY_HOST = '127.0.0.1'
def __init__(self, is_content_shell, options): def __init__(self, is_content_shell, supports_extensions, options):
self.browser_type = options.browser_type self.browser_type = options.browser_type
self.is_content_shell = is_content_shell self.is_content_shell = is_content_shell
self._supports_extensions = supports_extensions
self.options = options self.options = options
self._browser = None self._browser = None
self._port = None self._port = None
...@@ -47,6 +52,10 @@ class BrowserBackend(object): ...@@ -47,6 +52,10 @@ class BrowserBackend(object):
'such as about:flags settings, cookies, and ' 'such as about:flags settings, cookies, and '
'extensions.\n') 'extensions.\n')
self._tab_list_backend = tab_list_backend.TabListBackend(self) self._tab_list_backend = tab_list_backend.TabListBackend(self)
self._extension_dict_backend = None
if supports_extensions:
self._extension_dict_backend = \
extension_dict_backend.ExtensionDictBackend(self)
def SetBrowser(self, browser): def SetBrowser(self, browser):
self._browser = browser self._browser = browser
...@@ -56,10 +65,19 @@ class BrowserBackend(object): ...@@ -56,10 +65,19 @@ class BrowserBackend(object):
def browser(self): def browser(self):
return self._browser return self._browser
@property
def supports_extensions(self):
"""True if this browser backend supports extensions."""
return self._supports_extensions
@property @property
def tab_list_backend(self): def tab_list_backend(self):
return self._tab_list_backend return self._tab_list_backend
@property
def extension_dict_backend(self):
return self._extension_dict_backend
def GetBrowserStartupArgs(self): def GetBrowserStartupArgs(self):
args = [] args = []
args.extend(self.options.extra_browser_args) args.extend(self.options.extra_browser_args)
...@@ -73,6 +91,12 @@ class BrowserBackend(object): ...@@ -73,6 +91,12 @@ class BrowserBackend(object):
self.webpagereplay_remote_https_port)) self.webpagereplay_remote_https_port))
args.extend(user_agent.GetChromeUserAgentArgumentFromType( args.extend(user_agent.GetChromeUserAgentArgumentFromType(
self.options.browser_user_agent_type)) self.options.browser_user_agent_type))
extensions = ','.join(
[extension.path for extension in self.options.extensions_to_load])
if len(self.options.extensions_to_load) > 0:
args.append('--load-extension=%s' % extensions)
return args return args
@property @property
...@@ -92,6 +116,17 @@ class BrowserBackend(object): ...@@ -92,6 +116,17 @@ class BrowserBackend(object):
except util.TimeoutException: except util.TimeoutException:
raise browser_gone_exception.BrowserGoneException() raise browser_gone_exception.BrowserGoneException()
def AllExtensionsLoaded():
for e in self.options.extensions_to_load:
extension_id = e.extension_id()
if not extension_id in self._extension_dict_backend:
return False
extension_object = self._extension_dict_backend[extension_id]
extension_object.WaitForDocumentReadyStateToBeInteractiveOrBetter()
return True
if self._supports_extensions:
util.WaitFor(AllExtensionsLoaded, timeout=30)
def _PostBrowserStartupInitialization(self): def _PostBrowserStartupInitialization(self):
# Detect version information. # Detect version information.
data = self.Request('version') data = self.Request('version')
......
...@@ -53,7 +53,7 @@ def FindBrowser(options): ...@@ -53,7 +53,7 @@ def FindBrowser(options):
return None return None
matching_browsers = [b for b in browsers matching_browsers = [b for b in browsers
if b.browser_type == options.browser_type] if b.browser_type == options.browser_type and b.SupportsOptions(options)]
if len(matching_browsers) == 1: if len(matching_browsers) == 1:
return matching_browsers[0] return matching_browsers[0]
......
...@@ -26,6 +26,7 @@ class BrowserOptions(optparse.Values): ...@@ -26,6 +26,7 @@ class BrowserOptions(optparse.Values):
self.extra_browser_args = [] self.extra_browser_args = []
self.extra_wpr_args = [] self.extra_wpr_args = []
self.show_stdout = False self.show_stdout = False
self.extensions_to_load = []
self.cros_remote = None self.cros_remote = None
self.wpr_mode = wpr_modes.WPR_OFF self.wpr_mode = wpr_modes.WPR_OFF
......
...@@ -9,11 +9,11 @@ from telemetry import browser_backend ...@@ -9,11 +9,11 @@ from telemetry import browser_backend
from telemetry import util from telemetry import util
class CrOSBrowserBackend(browser_backend.BrowserBackend): class CrOSBrowserBackend(browser_backend.BrowserBackend):
def __init__(self, browser_type, options, is_content_shell, cri): def __init__(self, browser_type, options, cri):
super(CrOSBrowserBackend, self).__init__(is_content_shell, options) super(CrOSBrowserBackend, self).__init__(is_content_shell=False,
supports_extensions=True, options=options)
# Initialize fields so that an explosion during init doesn't break in Close. # Initialize fields so that an explosion during init doesn't break in Close.
self._options = options self._options = options
assert not is_content_shell
self._cri = cri self._cri = cri
self._browser_type = browser_type self._browser_type = browser_type
......
...@@ -16,10 +16,9 @@ ALL_BROWSER_TYPES = ','.join([ ...@@ -16,10 +16,9 @@ ALL_BROWSER_TYPES = ','.join([
]) ])
class PossibleCrOSBrowser(possible_browser.PossibleBrowser): class PossibleCrOSBrowser(possible_browser.PossibleBrowser):
"""A launchable android browser instance.""" """A launchable chromeos browser instance."""
def __init__(self, browser_type, options, *args): def __init__(self, browser_type, options, *args):
super(PossibleCrOSBrowser, self).__init__( super(PossibleCrOSBrowser, self).__init__(browser_type, options)
browser_type, options)
self._args = args self._args = args
def __repr__(self): def __repr__(self):
...@@ -32,6 +31,9 @@ class PossibleCrOSBrowser(possible_browser.PossibleBrowser): ...@@ -32,6 +31,9 @@ class PossibleCrOSBrowser(possible_browser.PossibleBrowser):
backend.SetBrowser(b) backend.SetBrowser(b)
return b return b
def SupportsOptions(self, options):
return True
def FindAllAvailableBrowsers(options): def FindAllAvailableBrowsers(options):
"""Finds all the desktop browsers available on this machine.""" """Finds all the desktop browsers available on this machine."""
if options.cros_remote == None: if options.cros_remote == None:
...@@ -75,4 +77,4 @@ def FindAllAvailableBrowsers(options): ...@@ -75,4 +77,4 @@ def FindAllAvailableBrowsers(options):
if not cri.FileExistsOnDevice('/opt/google/chrome/chrome'): if not cri.FileExistsOnDevice('/opt/google/chrome/chrome'):
logging.warn('Could not find a chrome on ' % cri.hostname) logging.warn('Could not find a chrome on ' % cri.hostname)
return [PossibleCrOSBrowser('cros-chrome', options, False, cri)] return [PossibleCrOSBrowser('cros-chrome', options, cri)]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import absolute_import
import os
import sys
def __init__():
path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.append(path)
__init__()
from crx_id import crx_id # pylint: disable=F0401
GetCRXAppID = crx_id.GetCRXAppID
...@@ -14,7 +14,9 @@ class DesktopBrowserBackend(browser_backend.BrowserBackend): ...@@ -14,7 +14,9 @@ class DesktopBrowserBackend(browser_backend.BrowserBackend):
Mac or Windows. Mac or Windows.
""" """
def __init__(self, options, executable, is_content_shell): def __init__(self, options, executable, is_content_shell):
super(DesktopBrowserBackend, self).__init__(is_content_shell, options) super(DesktopBrowserBackend, self).__init__(
is_content_shell=is_content_shell,
supports_extensions=not is_content_shell, options=options)
# Initialize fields so that an explosion during init doesn't break in Close. # Initialize fields so that an explosion during init doesn't break in Close.
self._proc = None self._proc = None
...@@ -25,6 +27,10 @@ class DesktopBrowserBackend(browser_backend.BrowserBackend): ...@@ -25,6 +27,10 @@ class DesktopBrowserBackend(browser_backend.BrowserBackend):
if not self._executable: if not self._executable:
raise Exception('Cannot create browser, no executable found!') raise Exception('Cannot create browser, no executable found!')
if len(options.extensions_to_load) > 0 and is_content_shell:
raise browser_backend.ExtensionsNotSupportedException(
'Content shell does not support extensions.')
self._port = util.GetAvailableLocalPort() self._port = util.GetAvailableLocalPort()
args = [self._executable] args = [self._executable]
......
...@@ -40,6 +40,11 @@ class PossibleDesktopBrowser(possible_browser.PossibleBrowser): ...@@ -40,6 +40,11 @@ class PossibleDesktopBrowser(possible_browser.PossibleBrowser):
backend.SetBrowser(b) backend.SetBrowser(b)
return b return b
def SupportsOptions(self, options):
if (len(options.extensions_to_load) != 0) and self._is_content_shell:
return False
return True
def FindAllAvailableBrowsers(options): def FindAllAvailableBrowsers(options):
"""Finds all the desktop browsers available on this machine.""" """Finds all the desktop browsers available on this machine."""
browsers = [] browsers = []
...@@ -122,8 +127,7 @@ def FindAllAvailableBrowsers(options): ...@@ -122,8 +127,7 @@ def FindAllAvailableBrowsers(options):
pass pass
if found: if found:
browsers.append( browsers.append(
PossibleDesktopBrowser('system', options, PossibleDesktopBrowser('system', options, 'google-chrome', False))
'google-chrome', False))
# Win32-specific options. # Win32-specific options.
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
......
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from telemetry import extension_to_load
class ExtensionDict(object):
"""Dictionary of ExtensionPage instances, with extension_id as key"""
def __init__(self, extension_dict_backend):
self._extension_dict_backend = extension_dict_backend
def __getitem__(self, load_extension):
"""Given an ExtensionToLoad instance, returns the corresponding
ExtensionPage instance."""
if not isinstance(load_extension, extension_to_load.ExtensionToLoad):
raise Exception("Input param must be of type ExtensionToLoad")
return self._extension_dict_backend.__getitem__(
load_extension.extension_id())
def __contains__(self, load_extension):
"""Checks if this ExtensionToLoad instance has been loaded"""
if not isinstance(load_extension, extension_to_load.ExtensionToLoad):
raise Exception("Input param must be of type ExtensionToLoad")
return self._extension_dict_backend.__contains__(
load_extension.extension_id())
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import httplib
import json
import re
import socket
import urllib2
import weakref
from telemetry import browser_gone_exception
from telemetry import extension_page
from telemetry import inspector_backend
class BrowserConnectionGoneException(
browser_gone_exception.BrowserGoneException):
pass
class ExtensionDictBackend(object):
def __init__(self, browser_backend):
self._browser_backend = browser_backend
# Maps extension ids to ExtensionPage objects.
self._extension_dict = weakref.WeakValueDictionary()
def __getitem__(self, extension_id):
extension_object = self._extension_dict.get(extension_id)
if not extension_object:
extension_object = self._CreateExtensionObject(extension_id)
if extension_object:
self._extension_dict[extension_id] = extension_object
return extension_object
def __contains__(self, extension_id):
return extension_id in self._GetExtensionIds()
@staticmethod
def _ExtractExtensionId(url):
m = re.match(r"(chrome-extension://)([^/]+)", url)
assert m
return m.group(2)
@staticmethod
def _GetExtensionId(extension_info):
if 'url' not in extension_info:
return None
return ExtensionDictBackend._ExtractExtensionId(extension_info['url'])
def _CreateExtensionObject(self, extension_id):
extension_info = self._FindExtensionInfo(extension_id)
if not extension_info or not 'webSocketDebuggerUrl' in extension_info:
return None
return extension_page.ExtensionPage(
self._CreateInspectorBackendForDebuggerUrl(
extension_info['webSocketDebuggerUrl']))
def _CreateInspectorBackendForDebuggerUrl(self, debugger_url):
return inspector_backend.InspectorBackend(self._browser_backend.browser,
self._browser_backend,
debugger_url)
def _FindExtensionInfo(self, extension_id):
for extension_info in self._GetExtensionInfoList():
if self._GetExtensionId(extension_info) == extension_id:
return extension_info
return None
def _GetExtensionInfoList(self, timeout=None):
try:
data = self._browser_backend.Request('', timeout=timeout)
return self._FilterExtensions(json.loads(data))
except (socket.error, httplib.BadStatusLine, urllib2.URLError):
if not self._browser_backend.IsBrowserRunning():
raise browser_gone_exception.BrowserGoneException()
raise BrowserConnectionGoneException()
def _FilterExtensions(self, all_pages):
return [page_info for page_info in all_pages
if page_info['url'].startswith('chrome-extension://')]
def _GetExtensionIds(self):
return map(self._GetExtensionId, self._GetExtensionInfoList())
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from telemetry import web_contents
class ExtensionPage(web_contents.WebContents):
"""Represents a an extension page in the browser"""
def __init__(self, inspector_backend):
super(ExtensionPage, self).__init__(inspector_backend)
def __del__(self):
super(ExtensionPage, self).__del__()
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
from telemetry import crx_id
class ExtensionToLoad(object):
def __init__(self, path):
if not os.path.isdir(path):
raise Exception('Extension path not a directory %s' % path)
self.path = path
def extension_id(self):
return crx_id.GetCRXAppID(os.path.abspath(self.path))
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import shutil
import tempfile
import unittest
from telemetry import browser_finder
from telemetry import extension_to_load
from telemetry import options_for_unittests
class ExtensionTest(unittest.TestCase):
def testExtension(self):
extension_path = os.path.join(os.path.dirname(__file__),
'..', 'unittest_data', 'simple_extension')
load_extension = extension_to_load.ExtensionToLoad(extension_path)
options = options_for_unittests.GetCopy()
options.extensions_to_load = [load_extension]
browser_to_create = browser_finder.FindBrowser(options)
if not browser_to_create:
# Could not find a browser that supports extensions.
return
with browser_to_create.Create() as b:
extension = b.extensions[load_extension]
assert extension
extension.ExecuteJavaScript("setTestVar('abcdef')")
self.assertEquals('abcdef', extension.EvaluateJavaScript("_testVar"))
class MultipleExtensionTest(unittest.TestCase):
def setUp(self):
""" Copy the manifest and background.js files of simple_extension to a
number of temporary directories to load as extensions"""
self._extension_dirs = [tempfile.mkdtemp()
for i in range(3)] # pylint: disable=W0612
src_extension_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', 'unittest_data', 'simple_extension'))
manifest_path = os.path.join(src_extension_dir, 'manifest.json')
script_path = os.path.join(src_extension_dir, 'background.js')
for d in self._extension_dirs:
shutil.copy(manifest_path, d)
shutil.copy(script_path, d)
self._extensions_to_load = [extension_to_load.ExtensionToLoad(d)
for d in self._extension_dirs]
options = options_for_unittests.GetCopy()
options.extensions_to_load = self._extensions_to_load
browser_to_create = browser_finder.FindBrowser(options)
self._browser = None
# May not find a browser that supports extensions.
if browser_to_create:
self._browser = browser_to_create.Create()
def tearDown(self):
if self._browser:
self._browser.Close()
for d in self._extension_dirs:
shutil.rmtree(d)
def testMultipleExtensions(self):
if not self._browser:
return
# Test contains.
loaded_extensions = filter(lambda e: e in self._browser.extensions,
self._extensions_to_load)
self.assertEqual(len(loaded_extensions), len(self._extensions_to_load))
for load_extension in self._extensions_to_load:
extension = self._browser.extensions[load_extension]
assert extension
extension.ExecuteJavaScript("setTestVar('abcdef')")
self.assertEquals('abcdef', extension.EvaluateJavaScript("_testVar"))
...@@ -20,3 +20,7 @@ class PossibleBrowser(object): ...@@ -20,3 +20,7 @@ class PossibleBrowser(object):
def Create(self): def Create(self):
raise NotImplementedError() raise NotImplementedError()
def SupportsOptions(self, options):
"""Tests for extension support."""
raise NotImplementedError()
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var _testVar;
function setTestVar(x) {
_testVar = x;
}
{
"description": "Simple test extension which has just a background script",
"name": "Simple Telemetry Test Extension",
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2,
"version": "0.1"
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment