summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-19 20:44:04 +0000
committerkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-19 20:44:04 +0000
commit4105bf75ab0b68f2a9a117cca2e52ac01fb12f77 (patch)
treee5e7c21a1e51c66b4382aa4da057010c97001894
parentbd931cfa493b88c047e3a0720620331379cd7b4f (diff)
downloadchromium_src-4105bf75ab0b68f2a9a117cca2e52ac01fb12f77.zip
chromium_src-4105bf75ab0b68f2a9a117cca2e52ac01fb12f77.tar.gz
chromium_src-4105bf75ab0b68f2a9a117cca2e52ac01fb12f77.tar.bz2
Docserver: move to a single version served from trunk. Permanently redirect requests
from any channel to the non-channel version (served from trunk). Improve the redirect logic in other ways. Fix a few bugs in caching classes that cause this all not to work. Replace the channel switcher (now moot) with a platform switcher. R=cduvall@chromium.org BUG=233978 NOTRY=true Review URL: https://chromiumcodereview.appspot.com/15087006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@212646 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/common/extensions/docs/server2/app.yaml2
-rw-r--r--chrome/common/extensions/docs/server2/app_yaml_helper.py5
-rwxr-xr-xchrome/common/extensions/docs/server2/app_yaml_helper_test.py3
-rw-r--r--chrome/common/extensions/docs/server2/appengine_blobstore.py2
-rw-r--r--chrome/common/extensions/docs/server2/blob_reference_store.py5
-rw-r--r--chrome/common/extensions/docs/server2/branch_utility.py11
-rwxr-xr-xchrome/common/extensions/docs/server2/caching_file_system_test.py3
-rw-r--r--chrome/common/extensions/docs/server2/caching_rietveld_patcher.py16
-rwxr-xr-xchrome/common/extensions/docs/server2/caching_rietveld_patcher_test.py2
-rwxr-xr-xchrome/common/extensions/docs/server2/chained_compiled_file_system_test.py15
-rw-r--r--chrome/common/extensions/docs/server2/compiled_file_system.py8
-rwxr-xr-xchrome/common/extensions/docs/server2/compiled_file_system_test.py28
-rw-r--r--chrome/common/extensions/docs/server2/cron.yaml21
-rw-r--r--chrome/common/extensions/docs/server2/cron_servlet.py64
-rwxr-xr-xchrome/common/extensions/docs/server2/cron_servlet_test.py9
-rw-r--r--chrome/common/extensions/docs/server2/empty_dir_file_system.py3
-rw-r--r--chrome/common/extensions/docs/server2/file_system.py6
-rw-r--r--chrome/common/extensions/docs/server2/github_file_system.py23
-rwxr-xr-xchrome/common/extensions/docs/server2/github_file_system_test.py5
-rw-r--r--chrome/common/extensions/docs/server2/host_file_system_creator.py11
-rw-r--r--chrome/common/extensions/docs/server2/instance_servlet.py12
-rwxr-xr-xchrome/common/extensions/docs/server2/instance_servlet_test.py17
-rwxr-xr-xchrome/common/extensions/docs/server2/integration_test.py18
-rw-r--r--chrome/common/extensions/docs/server2/local_renderer.py14
-rw-r--r--chrome/common/extensions/docs/server2/object_store.py6
-rw-r--r--chrome/common/extensions/docs/server2/object_store_creator.py11
-rwxr-xr-xchrome/common/extensions/docs/server2/object_store_creator_test.py20
-rw-r--r--chrome/common/extensions/docs/server2/patch_servlet.py37
-rwxr-xr-xchrome/common/extensions/docs/server2/patch_servlet_test.py26
-rw-r--r--chrome/common/extensions/docs/server2/patched_file_system.py5
-rw-r--r--chrome/common/extensions/docs/server2/patcher.py18
-rw-r--r--chrome/common/extensions/docs/server2/path_canonicalizer.py124
-rwxr-xr-xchrome/common/extensions/docs/server2/path_canonicalizer_test.py144
-rwxr-xr-xchrome/common/extensions/docs/server2/preview.py7
-rw-r--r--chrome/common/extensions/docs/server2/render_servlet.py32
-rwxr-xr-xchrome/common/extensions/docs/server2/render_servlet_test.py16
-rw-r--r--chrome/common/extensions/docs/server2/rietveld_patcher.py3
-rw-r--r--chrome/common/extensions/docs/server2/samples_data_source.py15
-rwxr-xr-xchrome/common/extensions/docs/server2/samples_data_source_test.py2
-rw-r--r--chrome/common/extensions/docs/server2/server_instance.py25
-rw-r--r--chrome/common/extensions/docs/server2/servlet.py17
-rw-r--r--chrome/common/extensions/docs/server2/sidenav_data_source.py13
-rwxr-xr-xchrome/common/extensions/docs/server2/sidenav_data_source_test.py9
-rw-r--r--chrome/common/extensions/docs/server2/subversion_file_system.py5
-rw-r--r--chrome/common/extensions/docs/server2/template_data_source.py32
-rwxr-xr-xchrome/common/extensions/docs/server2/template_data_source_test.py1
-rw-r--r--chrome/common/extensions/docs/server2/test_data/sidenav_data_source/absolute_path_sidenav_expected.json10
-rw-r--r--chrome/common/extensions/docs/server2/test_patcher.py3
-rw-r--r--chrome/common/extensions/docs/static/css/site.css24
-rw-r--r--chrome/common/extensions/docs/static/js/branch.js33
-rw-r--r--chrome/common/extensions/docs/static/js/platform_chooser.js18
-rw-r--r--chrome/common/extensions/docs/templates/private/apps_footer.html3
-rw-r--r--chrome/common/extensions/docs/templates/private/extensions_footer.html3
-rw-r--r--chrome/common/extensions/docs/templates/private/footer.html2
-rw-r--r--chrome/common/extensions/docs/templates/private/header_body.html21
55 files changed, 466 insertions, 522 deletions
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
index 2ba3da9..ce627b1 100644
--- a/chrome/common/extensions/docs/server2/app.yaml
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -1,5 +1,5 @@
application: chrome-apps-doc
-version: 2-10-0
+version: 2-11-0
runtime: python27
api_version: 1
threadsafe: false
diff --git a/chrome/common/extensions/docs/server2/app_yaml_helper.py b/chrome/common/extensions/docs/server2/app_yaml_helper.py
index 08f7003..63a0cc7 100644
--- a/chrome/common/extensions/docs/server2/app_yaml_helper.py
+++ b/chrome/common/extensions/docs/server2/app_yaml_helper.py
@@ -20,8 +20,7 @@ class AppYamlHelper(object):
app_yaml_path,
file_system_at_head,
object_store_creator,
- host_file_system_creator,
- branch):
+ host_file_system_creator):
self._app_yaml_path = app_yaml_path
self._file_system_at_head = file_system_at_head
self._store = object_store_creator.Create(
@@ -29,7 +28,6 @@ class AppYamlHelper(object):
category=file_system_at_head.GetIdentity(),
start_empty=False)
self._host_file_system_creator = host_file_system_creator
- self._branch = branch
@staticmethod
def ExtractVersion(app_yaml, key='version'):
@@ -120,7 +118,6 @@ class AppYamlHelper(object):
logging.warning('All revisions are greater than %s' % app_version)
return 0
next_file_system = self._host_file_system_creator.Create(
- self._branch,
revision=found - 1)
if found is None:
diff --git a/chrome/common/extensions/docs/server2/app_yaml_helper_test.py b/chrome/common/extensions/docs/server2/app_yaml_helper_test.py
index 7401477..59e2014 100755
--- a/chrome/common/extensions/docs/server2/app_yaml_helper_test.py
+++ b/chrome/common/extensions/docs/server2/app_yaml_helper_test.py
@@ -95,8 +95,7 @@ class AppYamlHelperTest(unittest.TestCase):
helper = AppYamlHelper('server2/app.yaml',
file_system_at_head,
ObjectStoreCreator.ForTest(disable_wrappers=False),
- host_file_system_creator,
- 'trunk')
+ host_file_system_creator)
def assert_is_up_to_date(version):
self.assertTrue(helper.IsUpToDate(version),
diff --git a/chrome/common/extensions/docs/server2/appengine_blobstore.py b/chrome/common/extensions/docs/server2/appengine_blobstore.py
index 5781556..f367293 100644
--- a/chrome/common/extensions/docs/server2/appengine_blobstore.py
+++ b/chrome/common/extensions/docs/server2/appengine_blobstore.py
@@ -14,7 +14,7 @@ class AppEngineBlobstore(object):
datastore.
"""
def __init__(self):
- self._datastore = BlobReferenceStore('blobstore')
+ self._datastore = BlobReferenceStore()
def Set(self, key, blob, namespace):
"""Add a blob to the blobstore. |version| is used as part of the key so
diff --git a/chrome/common/extensions/docs/server2/blob_reference_store.py b/chrome/common/extensions/docs/server2/blob_reference_store.py
index 97c6827..7edf5ee 100644
--- a/chrome/common/extensions/docs/server2/blob_reference_store.py
+++ b/chrome/common/extensions/docs/server2/blob_reference_store.py
@@ -14,14 +14,11 @@ class _Model(db.Model):
class BlobReferenceStore(object):
"""A wrapper around the datastore API that can store blob keys.
"""
- def __init__(self, branch):
- self._branch = branch
-
def _Query(self, namespace, key):
return _Model.gql('WHERE key_ = :1', self._MakeKey(namespace, key)).get()
def _MakeKey(self, namespace, key):
- return '.'.join([self._branch, namespace, key])
+ return '.'.join((namespace, key))
def Set(self, namespace, key, value):
_Model(key_=self._MakeKey(namespace, key), value=value).put()
diff --git a/chrome/common/extensions/docs/server2/branch_utility.py b/chrome/common/extensions/docs/server2/branch_utility.py
index 4f49113..ab3dce2 100644
--- a/chrome/common/extensions/docs/server2/branch_utility.py
+++ b/chrome/common/extensions/docs/server2/branch_utility.py
@@ -18,13 +18,10 @@ class ChannelInfo(object):
class BranchUtility(object):
def __init__(self, fetch_url, history_url, fetcher, object_store_creator):
self._fetcher = fetcher
- # BranchUtility is obviously cross-channel, so set the channel to None.
- self._branch_object_store = object_store_creator.Create(BranchUtility,
- category='branch',
- channel=None)
- self._version_object_store = object_store_creator.Create(BranchUtility,
- category='version',
- channel=None)
+ def create_object_store(category):
+ return object_store_creator.Create(BranchUtility, category=category)
+ self._branch_object_store = create_object_store('branch')
+ self._version_object_store = create_object_store('version')
self._fetch_result = self._fetcher.FetchAsync(fetch_url)
self._history_result = self._fetcher.FetchAsync(history_url)
diff --git a/chrome/common/extensions/docs/server2/caching_file_system_test.py b/chrome/common/extensions/docs/server2/caching_file_system_test.py
index e7f7ecf..6bb09f8 100755
--- a/chrome/common/extensions/docs/server2/caching_file_system_test.py
+++ b/chrome/common/extensions/docs/server2/caching_file_system_test.py
@@ -37,8 +37,7 @@ class CachingFileSystemTest(unittest.TestCase):
if start_empty:
db.clear()
return TestObjectStore(namespace, init=db)
- object_store_creator = ObjectStoreCreator('test',
- start_empty=start_empty,
+ object_store_creator = ObjectStoreCreator(start_empty=start_empty,
store_type=store_type_constructor)
return CachingFileSystem(fs, object_store_creator)
diff --git a/chrome/common/extensions/docs/server2/caching_rietveld_patcher.py b/chrome/common/extensions/docs/server2/caching_rietveld_patcher.py
index dd49482..2c33b33 100644
--- a/chrome/common/extensions/docs/server2/caching_rietveld_patcher.py
+++ b/chrome/common/extensions/docs/server2/caching_rietveld_patcher.py
@@ -67,12 +67,13 @@ class CachingRietveldPatcher(Patcher):
object_store_creator,
test_datetime=datetime):
self._patcher = rietveld_patcher
- self._version_object_store = object_store_creator.Create(
- CachingRietveldPatcher, category='version')
- self._list_object_store = object_store_creator.Create(
- CachingRietveldPatcher, category='list')
- self._file_object_store = object_store_creator.Create(
- CachingRietveldPatcher, category='file')
+ def create_object_store(category):
+ return object_store_creator.Create(
+ CachingRietveldPatcher,
+ category='%s/%s' % (rietveld_patcher.GetIdentity(), category))
+ self._version_object_store = create_object_store('version')
+ self._list_object_store = create_object_store('list')
+ self._file_object_store = create_object_store('file')
self._datetime = test_datetime
def GetVersion(self):
@@ -124,3 +125,6 @@ class CachingRietveldPatcher(Patcher):
True,
version),
self._file_object_store)
+
+ def GetIdentity(self):
+ return self._patcher.GetIdentity()
diff --git a/chrome/common/extensions/docs/server2/caching_rietveld_patcher_test.py b/chrome/common/extensions/docs/server2/caching_rietveld_patcher_test.py
index 203392f..2984a57 100755
--- a/chrome/common/extensions/docs/server2/caching_rietveld_patcher_test.py
+++ b/chrome/common/extensions/docs/server2/caching_rietveld_patcher_test.py
@@ -37,7 +37,7 @@ class CachingRietveldPatcherTest(unittest.TestCase):
assert_binary=True)
self._patcher = CachingRietveldPatcher(
self._test_patcher,
- ObjectStoreCreator('test', start_empty=False),
+ ObjectStoreCreator(start_empty=False),
self._datetime)
def testGetVersion(self):
diff --git a/chrome/common/extensions/docs/server2/chained_compiled_file_system_test.py b/chrome/common/extensions/docs/server2/chained_compiled_file_system_test.py
index 2884751..face116d 100755
--- a/chrome/common/extensions/docs/server2/chained_compiled_file_system_test.py
+++ b/chrome/common/extensions/docs/server2/chained_compiled_file_system_test.py
@@ -28,18 +28,13 @@ _TEST_DATA_NEW = {
class ChainedCompiledFileSystemTest(unittest.TestCase):
def setUp(self):
- self._object_store_creator = ObjectStoreCreator(
- 'chained', start_empty=False)
- self._base_object_store_creator = ObjectStoreCreator(
- 'base', start_empty=False)
+ object_store_creator = ObjectStoreCreator(start_empty=False)
base_file_system = TestFileSystem(_TEST_DATA_BASE)
- self._base_factory = CompiledFileSystem.Factory(
- base_file_system,
- self._base_object_store_creator)
+ self._base_factory = CompiledFileSystem.Factory(base_file_system,
+ object_store_creator)
self._file_system = TestFileSystem(_TEST_DATA_NEW)
- self._patched_factory = CompiledFileSystem.Factory(
- self._file_system,
- self._object_store_creator)
+ self._patched_factory = CompiledFileSystem.Factory(self._file_system,
+ object_store_creator)
self._chained_factory = ChainedCompiledFileSystem.Factory(
[(self._patched_factory, self._file_system),
(self._base_factory, base_file_system)])
diff --git a/chrome/common/extensions/docs/server2/compiled_file_system.py b/chrome/common/extensions/docs/server2/compiled_file_system.py
index 087eea8..29b1363 100644
--- a/chrome/common/extensions/docs/server2/compiled_file_system.py
+++ b/chrome/common/extensions/docs/server2/compiled_file_system.py
@@ -26,12 +26,12 @@ class CompiledFileSystem(object):
"""
assert isinstance(cls, type)
assert not cls.__name__[0].islower() # guard against non-class types
- full_name = cls.__name__
+ full_name = [cls.__name__, self._file_system.GetIdentity()]
if category is not None:
- full_name = '%s/%s' % (full_name, category)
- def create_object_store(category):
+ full_name.append(category)
+ def create_object_store(my_category):
return self._object_store_creator.Create(
- CompiledFileSystem, category='%s/%s' % (full_name, category))
+ CompiledFileSystem, category='/'.join(full_name + [my_category]))
return CompiledFileSystem(self._file_system,
populate_function,
create_object_store('file'),
diff --git a/chrome/common/extensions/docs/server2/compiled_file_system_test.py b/chrome/common/extensions/docs/server2/compiled_file_system_test.py
index 557adec..8923808 100755
--- a/chrome/common/extensions/docs/server2/compiled_file_system_test.py
+++ b/chrome/common/extensions/docs/server2/compiled_file_system_test.py
@@ -30,8 +30,7 @@ _TEST_DATA = {
def _CreateFactory():
return CompiledFileSystem.Factory(
TestFileSystem(deepcopy(_TEST_DATA)),
- ObjectStoreCreator('test',
- start_empty=False,
+ ObjectStoreCreator(start_empty=False,
store_type=TestObjectStore,
disable_wrappers=True))
@@ -40,8 +39,9 @@ class CompiledFileSystemTest(unittest.TestCase):
factory = _CreateFactory()
compiled_fs = factory.CreateIdentity(CompiledFileSystemTest)
self.assertEqual(
- 'class=CompiledFileSystem&category=CompiledFileSystemTest/file&'
- 'channel=test&app_version=%s' % GetAppVersion(),
+ 'class=CompiledFileSystem&'
+ 'category=CompiledFileSystemTest/TestFileSystem/file&'
+ 'app_version=%s' % GetAppVersion(),
compiled_fs._file_object_store.namespace)
def testIdentityFromFile(self):
@@ -73,16 +73,20 @@ class CompiledFileSystemTest(unittest.TestCase):
factory = _CreateFactory()
f = lambda x: x
CheckNamespace(
- 'class=CompiledFileSystem&category=CompiledFileSystemTest/file&'
- 'channel=test&app_version=%s' % GetAppVersion(),
- 'class=CompiledFileSystem&category=CompiledFileSystemTest/list&'
- 'channel=test&app_version=%s' % GetAppVersion(),
+ 'class=CompiledFileSystem&'
+ 'category=CompiledFileSystemTest/TestFileSystem/file&'
+ 'app_version=%s' % GetAppVersion(),
+ 'class=CompiledFileSystem&'
+ 'category=CompiledFileSystemTest/TestFileSystem/list&'
+ 'app_version=%s' % GetAppVersion(),
factory.Create(f, CompiledFileSystemTest))
CheckNamespace(
- 'class=CompiledFileSystem&category=CompiledFileSystemTest/foo/file&'
- 'channel=test&app_version=%s' % GetAppVersion(),
- 'class=CompiledFileSystem&category=CompiledFileSystemTest/foo/list&'
- 'channel=test&app_version=%s' % GetAppVersion(),
+ 'class=CompiledFileSystem&'
+ 'category=CompiledFileSystemTest/TestFileSystem/foo/file&'
+ 'app_version=%s' % GetAppVersion(),
+ 'class=CompiledFileSystem&'
+ 'category=CompiledFileSystemTest/TestFileSystem/foo/list&'
+ 'app_version=%s' % GetAppVersion(),
factory.Create(f, CompiledFileSystemTest, category='foo'))
def testPopulateFromFile(self):
diff --git a/chrome/common/extensions/docs/server2/cron.yaml b/chrome/common/extensions/docs/server2/cron.yaml
index bbfa423..320ef9a 100644
--- a/chrome/common/extensions/docs/server2/cron.yaml
+++ b/chrome/common/extensions/docs/server2/cron.yaml
@@ -1,20 +1,5 @@
cron:
-- description: Load everything for trunk.
- url: /_cron/trunk
+- description: Repopulates all cached data.
+ url: /_cron
schedule: every 5 minutes
- target: 2-10-0
-
-- description: Load everything for dev.
- url: /_cron/dev
- schedule: every 5 minutes
- target: 2-10-0
-
-- description: Load everything for beta.
- url: /_cron/beta
- schedule: every 5 minutes
- target: 2-10-0
-
-- description: Load everything for stable.
- url: /_cron/stable
- schedule: every 5 minutes
- target: 2-10-0
+ target: 2-11-0
diff --git a/chrome/common/extensions/docs/server2/cron_servlet.py b/chrome/common/extensions/docs/server2/cron_servlet.py
index 915aae5..14d4b39 100644
--- a/chrome/common/extensions/docs/server2/cron_servlet.py
+++ b/chrome/common/extensions/docs/server2/cron_servlet.py
@@ -25,7 +25,7 @@ class _SingletonRenderServletDelegate(RenderServlet.Delegate):
def __init__(self, server_instance):
self._server_instance = server_instance
- def CreateServerInstanceForChannel(self, channel):
+ def CreateServerInstance(self):
return self._server_instance
class CronServlet(Servlet):
@@ -33,7 +33,6 @@ class CronServlet(Servlet):
'''
def __init__(self, request, delegate_for_test=None):
Servlet.__init__(self, request)
- self._channel = request.path.strip('/')
self._delegate = delegate_for_test or CronServlet.Delegate()
class Delegate(object):
@@ -70,8 +69,7 @@ class CronServlet(Servlet):
# the time these won't have changed since the last cron run, so it's a
# little wasteful, but hopefully rendering is really fast (if it isn't we
# have a problem).
- channel = self._channel
- logging.info('cron/%s: starting' % channel)
+ logging.info('cron: starting')
# This is returned every time RenderServlet wants to create a new
# ServerInstance.
@@ -87,8 +85,7 @@ class CronServlet(Servlet):
start_time = time.time()
files = dict(
CreateURLsFromPaths(server_instance.host_file_system, d, path_prefix))
- logging.info('cron/%s: rendering %s files from %s...' % (
- channel, len(files), d))
+ logging.info('cron: rendering %s files from %s...' % (len(files), d))
try:
for i, path in enumerate(files):
error = None
@@ -98,18 +95,17 @@ class CronServlet(Servlet):
error = 'Got %s response' % response.status
except DeadlineExceededError:
logging.error(
- 'cron/%s: deadline exceeded rendering %s (%s of %s): %s' % (
- channel, path, i + 1, len(files), traceback.format_exc()))
+ 'cron: deadline exceeded rendering %s (%s of %s): %s' % (
+ path, i + 1, len(files), traceback.format_exc()))
raise
except error:
pass
if error:
- logging.error('cron/%s: error rendering %s: %s' % (
- channel, path, error))
+ logging.error('cron: error rendering %s: %s' % (path, error))
success = False
finally:
- logging.info('cron/%s: rendering %s files from %s took %s seconds' % (
- channel, len(files), d, time.time() - start_time))
+ logging.info('cron: rendering %s files from %s took %s seconds' % (
+ len(files), d, time.time() - start_time))
return success
success = True
@@ -147,24 +143,23 @@ class CronServlet(Servlet):
for filename in server_instance.content_cache.GetFromFileListing(
svn_constants.EXAMPLES_PATH)
if filename.endswith(manifest_json)]
- logging.info('cron/%s: rendering %s example zips...' % (
- channel, len(example_zips)))
+ logging.info('cron: rendering %s example zips...' % len(example_zips))
start_time = time.time()
try:
success = success and all(
get_via_render_servlet('extensions/examples/%s' % z).status == 200
for z in example_zips)
finally:
- logging.info('cron/%s: rendering %s example zips took %s seconds' % (
- channel, len(example_zips), time.time() - start_time))
+ logging.info('cron: rendering %s example zips took %s seconds' % (
+ len(example_zips), time.time() - start_time))
except DeadlineExceededError:
success = False
- logging.info("cron/%s: running Redirector cron..." % channel)
+ logging.info('cron: running Redirector cron...')
server_instance.redirector.Cron()
- logging.info('cron/%s: finished' % channel)
+ logging.info('cron: finished (%s)' % ('success' if success else 'failure',))
return (Response.Ok('Success') if success else
Response.InternalError('Failure'))
@@ -174,16 +169,14 @@ class CronServlet(Servlet):
meaning the last revision that the current running version of the server
existed.
'''
- channel = self._channel
delegate = self._delegate
- server_instance_at_head = self._CreateServerInstance(channel, None)
+ server_instance_at_head = self._CreateServerInstance(None)
app_yaml_handler = AppYamlHelper(
svn_constants.APP_YAML_PATH,
server_instance_at_head.host_file_system,
server_instance_at_head.object_store_creator,
- server_instance_at_head.host_file_system_creator,
- self._GetBranchForChannel(channel))
+ server_instance_at_head.host_file_system_creator)
if app_yaml_handler.IsUpToDate(delegate.GetAppVersion()):
# TODO(kalman): return a new ServerInstance at an explicit revision in
@@ -195,37 +188,26 @@ class CronServlet(Servlet):
safe_revision = app_yaml_handler.GetFirstRevisionGreaterThan(
delegate.GetAppVersion()) - 1
- logging.info('cron/%s: app version %s is out of date, safe is %s' % (
- channel, delegate.GetAppVersion(), safe_revision))
+ logging.info('cron: app version %s is out of date, safe is %s' % (
+ delegate.GetAppVersion(), safe_revision))
- return self._CreateServerInstance(channel, safe_revision)
+ return self._CreateServerInstance(safe_revision)
- def _CreateObjectStoreCreator(self, channel):
- return ObjectStoreCreator(channel, start_empty=True)
-
- def _GetBranchForChannel(self, channel):
- object_store_creator = self._CreateObjectStoreCreator(channel)
- return (self._delegate.CreateBranchUtility(object_store_creator)
- .GetChannelInfo(channel).branch)
-
- def _CreateServerInstance(self, channel, revision):
- object_store_creator = self._CreateObjectStoreCreator(channel)
+ def _CreateServerInstance(self, revision):
+ object_store_creator = ObjectStoreCreator(start_empty=True)
branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
host_file_system_creator = self._delegate.CreateHostFileSystemCreator(
object_store_creator)
- host_file_system = host_file_system_creator.Create(
- branch_utility.GetChannelInfo(channel).branch,
- revision=revision)
+ host_file_system = host_file_system_creator.Create(revision=revision)
app_samples_file_system = self._delegate.CreateAppSamplesFileSystem(
object_store_creator)
compiled_host_fs_factory = CompiledFileSystem.Factory(
host_file_system,
object_store_creator)
- return ServerInstance(channel,
- object_store_creator,
+ return ServerInstance(object_store_creator,
host_file_system,
app_samples_file_system,
- '' if channel == 'stable' else '/%s' % channel,
+ '',
compiled_host_fs_factory,
branch_utility,
host_file_system_creator)
diff --git a/chrome/common/extensions/docs/server2/cron_servlet_test.py b/chrome/common/extensions/docs/server2/cron_servlet_test.py
index a5fb622..07775eb 100755
--- a/chrome/common/extensions/docs/server2/cron_servlet_test.py
+++ b/chrome/common/extensions/docs/server2/cron_servlet_test.py
@@ -31,8 +31,8 @@ class _TestDelegate(CronServlet.Delegate):
return TestBranchUtility.CreateWithCannedData()
def CreateHostFileSystemCreator(self, object_store_creator):
- def constructor(branch, revision=None):
- file_system = self._create_file_system(branch, revision)
+ def constructor(branch=None, revision=None):
+ file_system = self._create_file_system(revision)
self.file_systems.append(file_system)
return file_system
return HostFileSystemCreator(object_store_creator,
@@ -53,8 +53,7 @@ class CronServletTest(unittest.TestCase):
def testEverything(self):
# All these tests are dependent (see above comment) so lump everything in
# the one test.
- delegate = _TestDelegate(
- lambda _, __: MockFileSystem(LocalFileSystem.Create()))
+ delegate = _TestDelegate(lambda _: MockFileSystem(LocalFileSystem.Create()))
# Test that the cron runs successfully.
response = CronServlet(Request.ForTest('trunk'),
@@ -122,7 +121,7 @@ class CronServletTest(unittest.TestCase):
storage_html_path = 'docs/templates/public/apps/storage.html'
static_txt_path = 'docs/static/static.txt'
- def create_file_system(branch, revision=None):
+ def create_file_system(revision=None):
'''Creates a MockFileSystem at |revision| by applying that many |updates|
to it.
'''
diff --git a/chrome/common/extensions/docs/server2/empty_dir_file_system.py b/chrome/common/extensions/docs/server2/empty_dir_file_system.py
index 22f5484..50e660a 100644
--- a/chrome/common/extensions/docs/server2/empty_dir_file_system.py
+++ b/chrome/common/extensions/docs/server2/empty_dir_file_system.py
@@ -21,3 +21,6 @@ class EmptyDirFileSystem(FileSystem):
if not path.endswith('/'):
raise FileNotFoundError('EmptyDirFileSystem cannot stat %s' % path)
return StatInfo(0, child_versions=[])
+
+ def GetIdentity(self):
+ return self.__class__.__name__
diff --git a/chrome/common/extensions/docs/server2/file_system.py b/chrome/common/extensions/docs/server2/file_system.py
index d899826..3226ebe 100644
--- a/chrome/common/extensions/docs/server2/file_system.py
+++ b/chrome/common/extensions/docs/server2/file_system.py
@@ -51,7 +51,7 @@ class FileSystem(object):
and failing that as latin-1 (some extension docs use latin-1). If
binary=True then the contents will be a str.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def ReadSingle(self, path, binary=False):
'''Reads a single file from the FileSystem.
@@ -64,7 +64,7 @@ class FileSystem(object):
is a directory, |StatInfo| will have the versions of all the children of
the directory in |StatInfo.child_versions|.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def GetIdentity(self):
'''The identity of the file system, exposed for caching classes to
@@ -73,7 +73,7 @@ class FileSystem(object):
different to that of a SubversionFileSystem with a base path of /bar, is
different to a LocalFileSystem with a base path of /usr.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def Walk(self, root):
'''Recursively walk the directories in a file system, starting with root.
diff --git a/chrome/common/extensions/docs/server2/github_file_system.py b/chrome/common/extensions/docs/server2/github_file_system.py
index 1b9569a..c0ffb1e 100644
--- a/chrome/common/extensions/docs/server2/github_file_system.py
+++ b/chrome/common/extensions/docs/server2/github_file_system.py
@@ -10,6 +10,7 @@ from StringIO import StringIO
import appengine_blobstore as blobstore
from appengine_url_fetcher import AppEngineUrlFetcher
from appengine_wrappers import GetAppVersion, urlfetch
+from docs_server_utils import StringIdentity
from file_system import FileSystem, StatInfo
from future import Future
from object_store_creator import ObjectStoreCreator
@@ -70,17 +71,16 @@ class GithubFileSystem(FileSystem):
@staticmethod
def Create(object_store_creator):
return GithubFileSystem(
- AppEngineUrlFetcher(url_constants.GITHUB_URL),
+ url_constants.GITHUB_URL,
blobstore.AppEngineBlobstore(),
object_store_creator)
- def __init__(self, fetcher, blobstore, object_store_creator):
- # Password store doesn't depend on channel, and if we don't cancel the app
- # version then the whole advantage of having it in the first place is
- # greatly lessened (likewise it should always start populated).
+ def __init__(self, url, blobstore, object_store_creator):
+ # If we key the password store on the app version then the whole advantage
+ # of having it in the first place is greatly lessened (likewise it should
+ # always start populated).
password_store = object_store_creator.Create(
GithubFileSystem,
- channel=None,
app_version=None,
category='password',
start_empty=False)
@@ -92,12 +92,10 @@ class GithubFileSystem(FileSystem):
password_store.SetMulti({'username': USERNAME, 'password': PASSWORD})
self._username, self._password = (USERNAME, PASSWORD)
- self._fetcher = fetcher
+ self._url = url
+ self._fetcher = AppEngineUrlFetcher(url)
self._blobstore = blobstore
- # Github has no knowledge of Chrome channels, set channel to None.
- self._stat_object_store = object_store_creator.Create(
- GithubFileSystem,
- channel=None)
+ self._stat_object_store = object_store_creator.Create(GithubFileSystem)
self._version = None
self._GetZip(self.Stat(ZIP_KEY).version)
@@ -203,3 +201,6 @@ class GithubFileSystem(FileSystem):
logging.warning('Problem fetching commit hash from github.')
return self._DefaultStat(path)
return StatInfo(version)
+
+ def GetIdentity(self):
+ return '%s@%s' % (self.__class__.__name__, StringIdentity(self._url))
diff --git a/chrome/common/extensions/docs/server2/github_file_system_test.py b/chrome/common/extensions/docs/server2/github_file_system_test.py
index 65a6928..54f829d 100755
--- a/chrome/common/extensions/docs/server2/github_file_system_test.py
+++ b/chrome/common/extensions/docs/server2/github_file_system_test.py
@@ -22,10 +22,7 @@ class GithubFileSystemTest(unittest.TestCase):
self._base_path = os.path.join(sys.path[0],
'test_data',
'github_file_system')
- self._file_system = GithubFileSystem(
- AppEngineUrlFetcher(url_constants.GITHUB_URL),
- AppEngineBlobstore(),
- ObjectStoreCreator.ForTest())
+ self._file_system = GithubFileSystem.Create(ObjectStoreCreator.ForTest())
def _ReadLocalFile(self, filename):
with open(os.path.join(self._base_path, filename), 'r') as f:
diff --git a/chrome/common/extensions/docs/server2/host_file_system_creator.py b/chrome/common/extensions/docs/server2/host_file_system_creator.py
index 1fee20f..381e664 100644
--- a/chrome/common/extensions/docs/server2/host_file_system_creator.py
+++ b/chrome/common/extensions/docs/server2/host_file_system_creator.py
@@ -23,16 +23,17 @@ class HostFileSystemCreator(object):
# Provides custom create behavior, useful in tests.
self._constructor_for_test = constructor_for_test
- def Create(self, branch, revision=None):
+ def Create(self, branch='trunk', revision=None):
'''Creates either SVN file systems or specialized file systems from the
constructor passed into this instance. Wraps the resulting file system in
an Offline file system if the offline flag is set, and finally wraps it in a
Caching file system.
'''
if self._constructor_for_test is not None:
- file_system = self._constructor_for_test(branch, revision)
+ file_system = self._constructor_for_test(branch=branch, revision=revision)
else:
- file_system = SubversionFileSystem.Create(branch, revision=revision)
+ file_system = SubversionFileSystem.Create(branch=branch,
+ revision=revision)
if self._offline:
file_system = OfflineFileSystem(file_system)
return CachingFileSystem(file_system, self._object_store_creator)
@@ -43,7 +44,7 @@ class HostFileSystemCreator(object):
'''
return HostFileSystemCreator(
object_store_creator,
- constructor_for_test=lambda _, __: LocalFileSystem.Create())
+ constructor_for_test=lambda **_: LocalFileSystem.Create())
@staticmethod
def ForTest(file_system, object_store_creator):
@@ -53,4 +54,4 @@ class HostFileSystemCreator(object):
'''
return HostFileSystemCreator(
object_store_creator,
- constructor_for_test=lambda _, __: file_system)
+ constructor_for_test=lambda **_: file_system)
diff --git a/chrome/common/extensions/docs/server2/instance_servlet.py b/chrome/common/extensions/docs/server2/instance_servlet.py
index 2016e32..0382ce4 100644
--- a/chrome/common/extensions/docs/server2/instance_servlet.py
+++ b/chrome/common/extensions/docs/server2/instance_servlet.py
@@ -32,23 +32,21 @@ class _OfflineRenderServletDelegate(RenderServlet.Delegate):
self._delegate = delegate
@memoize
- def CreateServerInstanceForChannel(self, channel):
- object_store_creator = ObjectStoreCreator(channel, start_empty=False)
+ def CreateServerInstance(self):
+ object_store_creator = ObjectStoreCreator(start_empty=False)
branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
host_file_system_creator = self._delegate.CreateHostFileSystemCreator(
object_store_creator)
- host_file_system = host_file_system_creator.Create(
- branch_utility.GetChannelInfo(channel).branch)
+ host_file_system = host_file_system_creator.Create()
app_samples_file_system = self._delegate.CreateAppSamplesFileSystem(
object_store_creator)
compiled_host_fs_factory = CompiledFileSystem.Factory(
host_file_system,
object_store_creator)
- return ServerInstance(channel,
- object_store_creator,
+ return ServerInstance(object_store_creator,
host_file_system,
app_samples_file_system,
- '' if channel == 'stable' else '/%s' % channel,
+ '',
compiled_host_fs_factory,
branch_utility,
host_file_system_creator)
diff --git a/chrome/common/extensions/docs/server2/instance_servlet_test.py b/chrome/common/extensions/docs/server2/instance_servlet_test.py
index 99252e2..8d9abc3 100755
--- a/chrome/common/extensions/docs/server2/instance_servlet_test.py
+++ b/chrome/common/extensions/docs/server2/instance_servlet_test.py
@@ -12,6 +12,9 @@ from servlet import Request
from test_branch_utility import TestBranchUtility
from test_util import DisableLogging
+# XXX(kalman): what is this test supposed to be?
+# Create a test host file system creator which failz?
+
# NOTE(kalman): The ObjectStore created by the InstanceServlet is backed onto
# our fake AppEngine memcache/datastore, so the tests aren't isolated.
class _TestDelegate(InstanceServlet.Delegate):
@@ -35,19 +38,19 @@ class InstanceServletTest(unittest.TestCase):
def testHostFileSystemNotAccessed(self):
delegate = _TestDelegate(_FailOnAccessFileSystem)
constructor = InstanceServlet.GetConstructor(delegate_for_test=delegate)
- def test_path(path):
+ def test_path(path, status=404):
response = constructor(Request.ForTest(path)).Get()
- self.assertEqual(404, response.status)
+ self.assertEqual(status, response.status)
test_path('extensions/storage.html')
test_path('apps/storage.html')
test_path('extensions/examples/foo.zip')
test_path('extensions/examples/foo.html')
test_path('static/foo.css')
- test_path('beta/extensions/storage.html')
- test_path('beta/apps/storage.html')
- test_path('beta/extensions/examples/foo.zip')
- test_path('beta/extensions/examples/foo.html')
- test_path('beta/static/foo.css')
+ test_path('beta/extensions/storage.html', status=301)
+ test_path('beta/apps/storage.html', status=301)
+ test_path('beta/extensions/examples/foo.zip', status=301)
+ test_path('beta/extensions/examples/foo.html', status=301)
+ test_path('beta/static/foo.css', status=301)
if __name__ == '__main__':
unittest.main()
diff --git a/chrome/common/extensions/docs/server2/integration_test.py b/chrome/common/extensions/docs/server2/integration_test.py
index ea7f305..4721c3b 100755
--- a/chrome/common/extensions/docs/server2/integration_test.py
+++ b/chrome/common/extensions/docs/server2/integration_test.py
@@ -12,10 +12,12 @@ from itertools import groupby
from operator import itemgetter
import optparse
import os
+import posixpath
import sys
import time
import unittest
+from branch_utility import BranchUtility
from link_error_detector import LinkErrorDetector
from local_file_system import LocalFileSystem
from local_renderer import LocalRenderer
@@ -124,7 +126,23 @@ class IntegrationTest(unittest.TestCase):
# that render large files. At least it'll catch zero-length responses.
self.assertTrue(len(response.content) >= len(content),
'Content was "%s" when rendering %s' % (response.content, path))
+
check_result(Handler(Request.ForTest(path)).Get())
+
+ # Make sure that leaving out the .html will temporarily redirect to the
+ # path with the .html.
+ if path != '/404.html':
+ redirect_result = Handler(
+ Request.ForTest(posixpath.splitext(path)[0])).Get()
+ self.assertEqual((path, False), redirect_result.GetRedirect())
+
+ # Make sure including a channel will permanently redirect to the same
+ # path without a channel.
+ for channel in BranchUtility.GetAllChannelNames():
+ redirect_result = Handler(
+ Request.ForTest('%s/%s' % (channel, path))).Get()
+ self.assertEqual((path, True), redirect_result.GetRedirect())
+
# Samples are internationalized, test some locales.
if path.endswith('/samples.html'):
for lang in ['en-US', 'es', 'ar']:
diff --git a/chrome/common/extensions/docs/server2/local_renderer.py b/chrome/common/extensions/docs/server2/local_renderer.py
index 7819e12..be403a8 100644
--- a/chrome/common/extensions/docs/server2/local_renderer.py
+++ b/chrome/common/extensions/docs/server2/local_renderer.py
@@ -10,7 +10,7 @@ from server_instance import ServerInstance
from servlet import Request
class _LocalRenderServletDelegate(object):
- def CreateServerInstanceForChannel(self, channel):
+ def CreateServerInstance(self):
return ServerInstance.ForLocal()
class LocalRenderer(object):
@@ -19,13 +19,5 @@ class LocalRenderer(object):
@staticmethod
def Render(path):
assert not '\\' in path
- def render_path(path):
- return RenderServlet(Request(path, 'http://localhost', {}),
- _LocalRenderServletDelegate(),
- default_channel='trunk').Get()
- response = render_path(path)
- while response.status in [301, 302]:
- redirect = response.headers['Location']
- sys.stderr.write('<!-- Redirected %s to %s -->\n' % (path, redirect))
- response = render_path(redirect)
- return response
+ return RenderServlet(Request.ForTest(path),
+ _LocalRenderServletDelegate()).Get()
diff --git a/chrome/common/extensions/docs/server2/object_store.py b/chrome/common/extensions/docs/server2/object_store.py
index 106dd2d..f33aa91 100644
--- a/chrome/common/extensions/docs/server2/object_store.py
+++ b/chrome/common/extensions/docs/server2/object_store.py
@@ -25,7 +25,7 @@ class ObjectStore(object):
'''Gets a |Future| with values mapped to |keys| from the object store, with
any keys not in the object store omitted.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def Set(self, key, value):
'''Sets key -> value in the object store.
@@ -35,7 +35,7 @@ class ObjectStore(object):
def SetMulti(self, items):
'''Atomically sets the mapping of keys to values in the object store.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def Del(self, key):
'''Deletes a key from the object store.
@@ -45,4 +45,4 @@ class ObjectStore(object):
def DelMulti(self, keys):
'''Deletes |keys| from the object store.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
diff --git a/chrome/common/extensions/docs/server2/object_store_creator.py b/chrome/common/extensions/docs/server2/object_store_creator.py
index 5bf37f6..6c0d0d2 100644
--- a/chrome/common/extensions/docs/server2/object_store_creator.py
+++ b/chrome/common/extensions/docs/server2/object_store_creator.py
@@ -18,7 +18,6 @@ class ObjectStoreCreator(object):
via the variants of Create which do this automatically).
'''
def __init__(self,
- channel,
# TODO(kalman): rename start_dirty?
start_empty=_unspecified,
# Override for testing. A custom ObjectStore type to construct
@@ -29,7 +28,6 @@ class ObjectStoreCreator(object):
# useful to override when specific state tests/manipulations are
# being done on the underlying object store.
disable_wrappers=False):
- self._channel = channel
if start_empty is _unspecified:
raise ValueError('start_empty must be specified (typically False)')
self._start_empty = start_empty
@@ -42,8 +40,7 @@ class ObjectStoreCreator(object):
def ForTest(start_empty=False,
store_type=TestObjectStore,
disable_wrappers=True):
- return ObjectStoreCreator('test',
- start_empty=start_empty,
+ return ObjectStoreCreator(start_empty=start_empty,
store_type=store_type,
disable_wrappers=disable_wrappers)
@@ -52,20 +49,17 @@ class ObjectStoreCreator(object):
category=None,
# Override any of these for a custom configuration.
start_empty=_unspecified,
- channel=_unspecified,
app_version=_unspecified):
# Resolve namespace components.
if start_empty is not _unspecified:
start_empty = bool(start_empty)
else:
start_empty = self._start_empty
- if channel is _unspecified:
- channel = self._channel
if app_version is _unspecified:
app_version = GetAppVersion()
# Reserve & and = for namespace separators.
- for component in (category, channel, app_version):
+ for component in (category, app_version):
if component and ('&' in component or '=' in component):
raise ValueError('%s cannot be used in a namespace')
@@ -73,7 +67,6 @@ class ObjectStoreCreator(object):
'%s=%s' % (key, value)
for key, value in (('class', cls.__name__),
('category', category),
- ('channel', channel),
('app_version', app_version))
if value is not None)
diff --git a/chrome/common/extensions/docs/server2/object_store_creator_test.py b/chrome/common/extensions/docs/server2/object_store_creator_test.py
index 680bbff..71ca77e 100755
--- a/chrome/common/extensions/docs/server2/object_store_creator_test.py
+++ b/chrome/common/extensions/docs/server2/object_store_creator_test.py
@@ -14,35 +14,27 @@ class _FooClass(object):
class ObjectStoreCreatorTest(unittest.TestCase):
def setUp(self):
- self._creator = ObjectStoreCreator('trunk',
- start_empty=False,
+ self._creator = ObjectStoreCreator(start_empty=False,
store_type=TestObjectStore,
disable_wrappers=True)
def testVanilla(self):
store = self._creator.Create(_FooClass)
self.assertEqual(
- 'class=_FooClass&channel=trunk&app_version=%s' % GetAppVersion(),
+ 'class=_FooClass&app_version=%s' % GetAppVersion(),
store.namespace)
self.assertFalse(store.start_empty)
def testWithCategory(self):
store = self._creator.Create(_FooClass, category='hi')
self.assertEqual(
- 'class=_FooClass&category=hi&channel=trunk&app_version=%s' %
- GetAppVersion(),
+ 'class=_FooClass&category=hi&app_version=%s' % GetAppVersion(),
store.namespace)
self.assertFalse(store.start_empty)
- def testWithoutChannel(self):
- store = self._creator.Create(_FooClass, channel=None)
- self.assertEqual('class=_FooClass&app_version=%s' % GetAppVersion(),
- store.namespace)
- self.assertFalse(store.start_empty)
-
def testWithoutAppVersion(self):
store = self._creator.Create(_FooClass, app_version=None)
- self.assertEqual('class=_FooClass&channel=trunk', store.namespace)
+ self.assertEqual('class=_FooClass', store.namespace)
self.assertFalse(store.start_empty)
def testStartConfiguration(self):
@@ -50,12 +42,10 @@ class ObjectStoreCreatorTest(unittest.TestCase):
self.assertTrue(store.start_empty)
store = self._creator.Create(_FooClass, start_empty=False)
self.assertFalse(store.start_empty)
- self.assertRaises(ValueError, ObjectStoreCreator, 'foo')
+ self.assertRaises(ValueError, ObjectStoreCreator)
def testIllegalCharacters(self):
self.assertRaises(ValueError,
- self._creator.Create, _FooClass, channel='foo=')
- self.assertRaises(ValueError,
self._creator.Create, _FooClass, app_version='1&2')
self.assertRaises(ValueError,
self._creator.Create, _FooClass, category='a=&b')
diff --git a/chrome/common/extensions/docs/server2/patch_servlet.py b/chrome/common/extensions/docs/server2/patch_servlet.py
index 8704e38..8207e1d 100644
--- a/chrome/common/extensions/docs/server2/patch_servlet.py
+++ b/chrome/common/extensions/docs/server2/patch_servlet.py
@@ -28,24 +28,18 @@ class _PatchServletDelegate(RenderServlet.Delegate):
self._issue = issue
self._delegate = delegate
- def CreateServerInstanceForChannel(self, channel):
- base_object_store_creator = ObjectStoreCreator(channel,
- start_empty=False)
- branch_utility = self._delegate.CreateBranchUtility(
- base_object_store_creator)
+ def CreateServerInstance(self):
+ object_store_creator = ObjectStoreCreator(start_empty=False)
+ branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
host_file_system_creator = self._delegate.CreateHostFileSystemCreator(
- base_object_store_creator)
+ object_store_creator)
# TODO(fj): Use OfflineFileSystem here once all json/idl files in api/
# are pulled into data store by cron jobs.
- base_file_system = CachingFileSystem(
- host_file_system_creator.Create(
- branch_utility.GetChannelInfo(channel).branch),
- base_object_store_creator)
- base_compiled_fs_factory = CompiledFileSystem.Factory(
- base_file_system, base_object_store_creator)
+ base_file_system = CachingFileSystem(host_file_system_creator.Create(),
+ object_store_creator)
+ base_compiled_fs_factory = CompiledFileSystem.Factory(base_file_system,
+ object_store_creator)
- object_store_creator = ObjectStoreCreator('trunk@%s' % self._issue,
- start_empty=False)
rietveld_patcher = CachingRietveldPatcher(
RietveldPatcher(svn_constants.EXTENSIONS_PATH,
self._issue,
@@ -60,11 +54,10 @@ class _PatchServletDelegate(RenderServlet.Delegate):
[(patched_compiled_fs_factory, patched_file_system),
(base_compiled_fs_factory, base_file_system)])
- return ServerInstance(channel,
- object_store_creator,
+ return ServerInstance(object_store_creator,
patched_file_system,
self._delegate.CreateAppSamplesFileSystem(
- base_object_store_creator),
+ object_store_creator),
'/_patch/%s' % self._issue,
compiled_fs_factory,
branch_utility,
@@ -90,16 +83,16 @@ class PatchServlet(Servlet):
path_with_issue = self._request.path.lstrip('/')
if '/' in path_with_issue:
- issue, real_path = path_with_issue.split('/', 1)
+ issue, path_without_issue = path_with_issue.split('/', 1)
else:
return Response.NotFound('Malformed URL. It should look like ' +
'https://developer.chrome.com/_patch/12345/extensions/...')
- fake_path = '/trunk/%s' % real_path
-
try:
response = RenderServlet(
- Request(fake_path, self._request.host, self._request.headers),
+ Request(path_without_issue,
+ self._request.host,
+ self._request.headers),
_PatchServletDelegate(issue, self._delegate)).Get()
# Disable cache for patched content.
response.headers.pop('cache-control', None)
@@ -108,8 +101,6 @@ class PatchServlet(Servlet):
redirect_url, permanent = response.GetRedirect()
if redirect_url is not None:
- if redirect_url.startswith('/trunk/'):
- redirect_url = redirect_url.split('/trunk', 1)[1]
response = Response.Redirect('/_patch/%s%s' % (issue, redirect_url),
permanent)
return response
diff --git a/chrome/common/extensions/docs/server2/patch_servlet_test.py b/chrome/common/extensions/docs/server2/patch_servlet_test.py
index 1c38732..70a6f2f 100755
--- a/chrome/common/extensions/docs/server2/patch_servlet_test.py
+++ b/chrome/common/extensions/docs/server2/patch_servlet_test.py
@@ -18,7 +18,7 @@ from test_util import DisableLogging
_ALLOWED_HOST = 'https://chrome-apps-doc.appspot.com'
class _RenderServletDelegate(RenderServlet.Delegate):
- def CreateServerInstanceForChannel(self, channel):
+ def CreateServerInstance(self):
return ServerInstance.ForLocal()
class _PatchServletDelegate(RenderServlet.Delegate):
@@ -36,8 +36,8 @@ class PatchServletTest(unittest.TestCase):
ConfigureFakeFetchers()
def _RenderWithPatch(self, path, issue):
- real_path = '%s/%s' % (issue, path)
- return PatchServlet(Request.ForTest(real_path, host=_ALLOWED_HOST),
+ path_with_issue = '%s/%s' % (issue, path)
+ return PatchServlet(Request.ForTest(path_with_issue, host=_ALLOWED_HOST),
_PatchServletDelegate()).Get()
def _RenderWithoutPatch(self, path):
@@ -68,8 +68,10 @@ class PatchServletTest(unittest.TestCase):
@DisableLogging('warning')
def _AssertNotFound(self, path, issue):
- self.assertEqual(self._RenderWithPatch(path, issue).status, 404,
- 'Path %s with issue %s should have been removed.' % (path, issue))
+ response = self._RenderWithPatch(path, issue)
+ self.assertEqual(response.status, 404,
+ 'Path %s with issue %s should have been removed for %s.' % (
+ path, issue, response))
def _AssertOk(self, path, issue):
response = self._RenderWithPatch(path, issue)
@@ -79,17 +81,27 @@ class PatchServletTest(unittest.TestCase):
'Rendered result for path %s with issue %s should not be empty.' %
(path, issue))
+ def _AssertRedirect(self, path, issue, redirect_path):
+ response = self._RenderWithPatch(path, issue)
+ self.assertEqual(302, response.status)
+ self.assertEqual('/_patch/%s/%s' % (issue, redirect_path),
+ response.headers['Location'])
+
def testRender(self):
# '_patch' is not included in paths below because it's stripped by Handler.
issue = '14096030'
# extensions_sidenav.json is modified in the patch.
self._RenderAndAssertNotEqual('extensions/index.html', issue)
+
# apps_sidenav.json is not patched.
self._RenderAndAssertEqual('apps/about_apps.html', issue)
- # extensions/runtime.html is removed in the patch.
- self._AssertNotFound('extensions/runtime.html', issue)
+ # extensions/runtime.html is removed in the patch, should redirect to the
+ # apps version.
+ self._AssertRedirect('extensions/runtime.html', issue,
+ 'apps/runtime.html')
+
# apps/runtime.html is not removed.
self._RenderAndAssertEqual('apps/runtime.html', issue)
diff --git a/chrome/common/extensions/docs/server2/patched_file_system.py b/chrome/common/extensions/docs/server2/patched_file_system.py
index 5828a7c..c287c58 100644
--- a/chrome/common/extensions/docs/server2/patched_file_system.py
+++ b/chrome/common/extensions/docs/server2/patched_file_system.py
@@ -156,3 +156,8 @@ class PatchedFileSystem(FileSystem):
else:
raise FileNotFoundError('%s was not in child versions' % filename)
return stat_info
+
+ def GetIdentity(self):
+ return '%s(%s,%s)' % (self.__class__.__name__,
+ self._host_file_system.GetIdentity(),
+ self._patcher.GetIdentity())
diff --git a/chrome/common/extensions/docs/server2/patcher.py b/chrome/common/extensions/docs/server2/patcher.py
index 34fd17f..57d1e13 100644
--- a/chrome/common/extensions/docs/server2/patcher.py
+++ b/chrome/common/extensions/docs/server2/patcher.py
@@ -4,19 +4,25 @@
class Patcher(object):
def GetPatchedFiles(self, version=None):
- ''' Returns patched files as(added_files, deleted_files, modified_files)
+ '''Returns patched files as(added_files, deleted_files, modified_files)
from the patchset specified by |version|.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def GetVersion(self):
- ''' Returns patch version. Returns None when nothing is patched by the
+ '''Returns patch version. Returns None when nothing is patched by the
patcher.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
def Apply(self, paths, file_system, binary, version=None):
- ''' Apply the patch to added/modified files. Returns Future with patched
+ '''Apply the patch to added/modified files. Returns Future with patched
data. Throws FileNotFoundError if |paths| contains deleted files.
'''
- raise NotImplementedError()
+ raise NotImplementedError(self.__class__)
+
+ def GetIdentity(self):
+ '''Returns a string that identifies this patch. Typically it would be the
+ codereview server's ID for this patch.
+ '''
+ raise NotImplementedError(self.__class__)
diff --git a/chrome/common/extensions/docs/server2/path_canonicalizer.py b/chrome/common/extensions/docs/server2/path_canonicalizer.py
index 8b5f77c..b4498a7 100644
--- a/chrome/common/extensions/docs/server2/path_canonicalizer.py
+++ b/chrome/common/extensions/docs/server2/path_canonicalizer.py
@@ -2,48 +2,106 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import logging
import os
+import posixpath
+import traceback
+
+from branch_utility import BranchUtility
+from file_system import FileNotFoundError
from third_party.json_schema_compiler.model import UnixName
import svn_constants
+def _SimplifyFileName(file_name):
+ return (posixpath.splitext(file_name)[0]
+ .lower()
+ .replace('.', '')
+ .replace('-', '')
+ .replace('_', ''))
+
class PathCanonicalizer(object):
'''Transforms paths into their canonical forms. Since the dev server has had
many incarnations - e.g. there didn't use to be apps/ - there may be old
paths lying around the webs. We try to redirect those to where they are now.
'''
- def __init__(self, channel, compiled_fs_factory):
- self._channel = channel
- self._identity_fs = compiled_fs_factory.CreateIdentity(PathCanonicalizer)
+ def __init__(self, compiled_fs_factory):
+ # Map of simplified API names (for typo detection) to their real paths.
+ def make_public_apis(_, file_names):
+ return dict((_SimplifyFileName(name), name) for name in file_names)
+ self._public_apis = compiled_fs_factory.Create(make_public_apis,
+ PathCanonicalizer)
def Canonicalize(self, path):
- starts_with_channel, path_without_channel = (False, path)
- if path.startswith('%s/' % self._channel):
- starts_with_channel, path_without_channel = (
- True, path[len(self._channel) + 1:])
-
- if any(path.startswith(prefix)
- for prefix in ('extensions/', 'apps/', 'static/')):
- return path
-
- if '/' in path_without_channel or path_without_channel == '404.html':
- return path
-
- apps_templates = self._identity_fs.GetFromFileListing(
- '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps')))
- extensions_templates = self._identity_fs.GetFromFileListing(
- '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions')))
-
- if self._channel is None or not starts_with_channel:
- apps_path = 'apps/%s' % path_without_channel
- extensions_path = 'extensions/%s' % path_without_channel
+ '''Returns the canonical path for |path|, and whether that path is a
+ permanent canonicalisation (e.g. when we redirect from a channel to a
+ channel-less URL) or temporary (e.g. when we redirect from an apps-only API
+ to an extensions one - we may at some point enable it for extensions).
+ '''
+ class ReturnType(object):
+ def __init__(self, path, permanent):
+ self.path = path
+ self.permanent = permanent
+
+ # Catch incorrect comparisons by disabling ==/!=.
+ def __eq__(self, _): raise NotImplementedError()
+ def __ne__(self, _): raise NotImplementedError()
+
+ # Strip any channel info off it. There are no channels anymore.
+ for channel_name in BranchUtility.GetAllChannelNames():
+ channel_prefix = channel_name + '/'
+ if path.startswith(channel_prefix):
+ # Redirect now so that we can set the permanent-redirect bit. Channel
+ # redirects are the only things that should be permanent redirects;
+ # anything else *could* change, so is temporary.
+ return ReturnType(path[len(channel_prefix):], True)
+
+ # No further work needed for static.
+ if path.startswith('static/'):
+ return ReturnType(path, False)
+
+ # People go to just "extensions" or "apps". Redirect to the directory.
+ if path in ('extensions', 'apps'):
+ return ReturnType(path + '/', False)
+
+ # The rest of this function deals with trying to figure out what API page
+ # for extensions/apps to redirect to, if any. We see a few different cases
+ # here:
+ # - Unqualified names ("browserAction.html"). These are easy to resolve;
+ # figure out whether it's an extension or app API and redirect.
+ # - but what if it's both? Well, assume extensions. Maybe later we can
+ # check analytics and see which is more popular.
+ # - Wrong names ("apps/browserAction.html"). This really does happen,
+ # damn it, so do the above logic but record which is the default.
+ if path.startswith(('extensions/', 'apps/')):
+ default_platform, reference_path = path.split('/', 1)
+ else:
+ default_platform, reference_path = ('extensions', path)
+
+ try:
+ apps_public = self._public_apis.GetFromFileListing(
+ '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps')))
+ extensions_public = self._public_apis.GetFromFileListing(
+ '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions')))
+ except FileNotFoundError:
+ # Probably offline.
+ logging.warning(traceback.format_exc())
+ return ReturnType(path, False)
+
+ simple_reference_path = _SimplifyFileName(reference_path)
+ apps_path = apps_public.get(simple_reference_path)
+ extensions_path = extensions_public.get(simple_reference_path)
+
+ if apps_path is None:
+ if extensions_path is None:
+ # No idea. Just return the original path. It'll probably 404.
+ pass
+ else:
+ path = 'extensions/%s' % extensions_path
else:
- apps_path = '%s/apps/%s' % (self._channel, path_without_channel)
- extensions_path = '%s/extensions/%s' % (self._channel,
- path_without_channel)
-
- unix_path = UnixName(os.path.splitext(path_without_channel)[0])
- if unix_path in extensions_templates:
- return extensions_path
- if unix_path in apps_templates:
- return apps_path
- return extensions_path
+ if extensions_path is None:
+ path = 'apps/%s' % apps_path
+ else:
+ assert apps_path == extensions_path
+ path = '%s/%s' % (default_platform, apps_path)
+
+ return ReturnType(path, False)
diff --git a/chrome/common/extensions/docs/server2/path_canonicalizer_test.py b/chrome/common/extensions/docs/server2/path_canonicalizer_test.py
index 841dc98..18feb0b 100755
--- a/chrome/common/extensions/docs/server2/path_canonicalizer_test.py
+++ b/chrome/common/extensions/docs/server2/path_canonicalizer_test.py
@@ -3,92 +3,84 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-from compiled_file_system import CompiledFileSystem
-from path_canonicalizer import PathCanonicalizer
-import svn_constants
-from object_store_creator import ObjectStoreCreator
-from test_file_system import TestFileSystem
import unittest
-_TEST_DATA = TestFileSystem.MoveTo(svn_constants.PUBLIC_TEMPLATE_PATH, {
- 'extensions': {
- 'browserAction.html': 'yo',
- 'storage.html': 'dawg',
- },
- 'apps': {
- 'bluetooth': 'hey',
- 'storage.html': 'wassup',
- }
-})
+from path_canonicalizer import PathCanonicalizer
+from server_instance import ServerInstance
+import svn_constants
class PathCanonicalizerTest(unittest.TestCase):
def setUp(self):
- test_fs = TestFileSystem(_TEST_DATA)
- compiled_fs_factory = CompiledFileSystem.Factory(
- test_fs,
- ObjectStoreCreator.ForTest())
- self._path_canonicalizer = PathCanonicalizer('stable', compiled_fs_factory)
+ self._server_instance = ServerInstance.ForLocal()
+
+ def _Cze(self, path):
+ return self._server_instance.path_canonicalizer.Canonicalize(path)
+
+ def testSpecifyCorrectly(self):
+ self._AssertIdentity('extensions/browserAction.html')
+ self._AssertIdentity('extensions/storage.html')
+ self._AssertIdentity('extensions/blah.html')
+ self._AssertIdentity('extensions/index.html')
+ self._AssertIdentity('extensions/whats_new.html')
+ self._AssertIdentity('apps/storage.html')
+ self._AssertIdentity('apps/bluetooth.html')
+ self._AssertIdentity('apps/blah.html')
+ self._AssertIdentity('static/browserAction.html')
+ self._AssertIdentity('static/storage.html')
+ self._AssertIdentity('static/bluetooth.html')
+ self._AssertIdentity('static/blah.html')
+
+ def testSpecifyIncorrectly(self):
+ self._AssertTemporaryRedirect('extensions/browserAction.html',
+ 'apps/browserAction.html')
+ self._AssertTemporaryRedirect('apps/bluetooth.html',
+ 'extensions/bluetooth.html')
+ self._AssertTemporaryRedirect('extensions/index.html',
+ 'apps/index.html')
+
+ def testUnspecified(self):
+ self._AssertTemporaryRedirect('extensions/browserAction.html',
+ 'browserAction.html')
+ self._AssertTemporaryRedirect('apps/bluetooth.html',
+ 'bluetooth.html')
+ # Extensions are default for now.
+ self._AssertTemporaryRedirect('extensions/storage.html',
+ 'storage.html')
+ # Nonexistent APIs should be left alone.
+ self._AssertIdentity('blah.html')
- def _assertIdentity(self, path):
- self.assertEqual(path, self._path_canonicalizer.Canonicalize(path))
+ def testSpellingErrors(self):
+ for spelme in ('browseraction', 'browseraction.htm', 'BrowserAction',
+ 'BrowserAction.html', 'browseraction.html', 'Browseraction',
+ 'browser-action', 'Browser.action.html', 'browser_action',
+ 'browser-action.html', 'Browser_Action.html'):
+ self._AssertTemporaryRedirect('extensions/browserAction.html', spelme)
+ self._AssertTemporaryRedirect('extensions/browserAction.html',
+ 'extensions/%s' % spelme)
+ self._AssertTemporaryRedirect('extensions/browserAction.html',
+ 'apps/%s' % spelme)
- def testExtensions(self):
- self._assertIdentity('extensions/browserAction.html')
- self._assertIdentity('extensions/storage.html')
- self._assertIdentity('extensions/bluetooth.html')
- self._assertIdentity('extensions/blah.html')
- self._assertIdentity('stable/extensions/browserAction.html')
- self._assertIdentity('stable/extensions/storage.html')
- self._assertIdentity('stable/extensions/bluetooth.html')
- self._assertIdentity('stable/extensions/blah.html')
+ def testChannelRedirect(self):
+ def assert_channel_redirect(channel, path):
+ self._AssertPermanentRedirect(path, '%s/%s' % (channel, path))
+ for channel in ('stable', 'beta', 'dev', 'trunk'):
+ assert_channel_redirect(channel, 'extensions/browserAction.html')
+ assert_channel_redirect(channel, 'extensions/storage.html')
+ assert_channel_redirect(channel, 'apps/bluetooth.html')
+ assert_channel_redirect(channel, 'apps/storage.html')
- def testApps(self):
- self._assertIdentity('apps/browserAction.html')
- self._assertIdentity('apps/storage.html')
- self._assertIdentity('apps/bluetooth.html')
- self._assertIdentity('apps/blah.html')
- self._assertIdentity('stable/apps/browserAction.html')
- self._assertIdentity('stable/apps/storage.html')
- self._assertIdentity('stable/apps/bluetooth.html')
- self._assertIdentity('stable/apps/blah.html')
+ def _AssertIdentity(self, path):
+ self._AssertTemporaryRedirect(path, path)
- def testStatic(self):
- self._assertIdentity('static/browserAction.html')
- self._assertIdentity('static/storage.html')
- self._assertIdentity('static/bluetooth.html')
- self._assertIdentity('static/blah.html')
- self._assertIdentity('stable/static/browserAction.html')
- self._assertIdentity('stable/static/storage.html')
- self._assertIdentity('stable/static/bluetooth.html')
- self._assertIdentity('stable/static/blah.html')
+ def _AssertTemporaryRedirect(self, to, from_):
+ result = self._Cze(from_)
+ self.assertEqual(to, result.path)
+ self.assertFalse(result.permanent)
- def testNeither(self):
- self.assertEqual(
- 'extensions/browserAction.html',
- self._path_canonicalizer.Canonicalize('browserAction.html'))
- self.assertEqual(
- 'stable/extensions/browserAction.html',
- self._path_canonicalizer.Canonicalize('stable/browserAction.html'))
- self.assertEqual(
- 'extensions/storage.html',
- self._path_canonicalizer.Canonicalize('storage.html'))
- self.assertEqual(
- 'stable/extensions/storage.html',
- self._path_canonicalizer.Canonicalize('stable/storage.html'))
- self.assertEqual(
- 'apps/bluetooth.html',
- self._path_canonicalizer.Canonicalize('bluetooth.html'))
- self.assertEqual(
- 'stable/apps/bluetooth.html',
- self._path_canonicalizer.Canonicalize('stable/bluetooth.html'))
- # Assign non-existent paths to extensions because they came first, so such
- # paths are more likely to be for extensions.
- self.assertEqual(
- 'extensions/blah.html',
- self._path_canonicalizer.Canonicalize('blah.html'))
- self.assertEqual(
- 'stable/extensions/blah.html',
- self._path_canonicalizer.Canonicalize('stable/blah.html'))
+ def _AssertPermanentRedirect(self, to, from_):
+ result = self._Cze(from_)
+ self.assertEqual(to, result.path)
+ self.assertTrue(result.permanent)
if __name__ == '__main__':
unittest.main()
diff --git a/chrome/common/extensions/docs/server2/preview.py b/chrome/common/extensions/docs/server2/preview.py
index d645abb..f09f0cf 100755
--- a/chrome/common/extensions/docs/server2/preview.py
+++ b/chrome/common/extensions/docs/server2/preview.py
@@ -23,9 +23,6 @@
# ./preview.py -r extensions/tabs.html
#
# will output the documentation for the tabs API on stdout and exit immediately.
-#
-# Note: absolute paths into static content (e.g. /static/css/site.css) will be
-# relative paths (e.g. static/css/site.css) for convenient sandboxing.
# NOTE: RUN THIS FIRST. Or all third_party imports will fail.
import build_server
@@ -91,9 +88,7 @@ if __name__ == '__main__':
if opts.time:
print('Took %s seconds' % (time.time() - start_time))
else:
- # Static paths will show up as /trunk/static/foo but this only makes
- # sense from a webserver.
- print(response.content.ToString().replace('/trunk/', ''))
+ print(response.content.ToString())
exit()
print('Starting previewserver on port %s' % opts.port)
diff --git a/chrome/common/extensions/docs/server2/render_servlet.py b/chrome/common/extensions/docs/server2/render_servlet.py
index 5b29c3d..77245d6 100644
--- a/chrome/common/extensions/docs/server2/render_servlet.py
+++ b/chrome/common/extensions/docs/server2/render_servlet.py
@@ -8,7 +8,6 @@ import mimetypes
import traceback
from urlparse import urlsplit
-from branch_utility import BranchUtility
from file_system import FileNotFoundError
from servlet import Servlet, Response
import svn_constants
@@ -21,43 +20,34 @@ class RenderServlet(Servlet):
'''Servlet which renders templates.
'''
class Delegate(object):
- def CreateServerInstanceForChannel(self, channel):
- raise NotImplementedError()
+ def CreateServerInstance(self):
+ raise NotImplementedError(self.__class__)
- def __init__(self, request, delegate, default_channel='stable'):
+ def __init__(self, request, delegate):
Servlet.__init__(self, request)
self._delegate = delegate
- self._default_channel = default_channel
def Get(self):
''' Render the page for a request.
'''
- headers = self._request.headers
- channel, path = BranchUtility.SplitChannelNameFromPath(self._request.path)
+ # TODO(kalman): a consistent path syntax (even a Path class?) so that we
+ # can stop being so conservative with stripping and adding back the '/'s.
+ path = self._request.path.lstrip('/')
if path.split('/')[-1] == 'redirects.json':
return Response.Ok('')
- if channel == self._default_channel:
- return Response.Redirect('/' + path)
- if channel is None:
- channel = self._default_channel
-
- server_instance = self._delegate.CreateServerInstanceForChannel(channel)
+ server_instance = self._delegate.CreateServerInstance()
redirect = server_instance.redirector.Redirect(self._request.host, path)
if redirect is not None:
- if (channel != self._default_channel and
- not urlsplit(redirect).scheme in ('http', 'https')):
- redirect = '/%s%s' % (channel, redirect)
return Response.Redirect(redirect)
- canonical_path = server_instance.path_canonicalizer.Canonicalize(path)
- redirect = canonical_path.lstrip('/')
+ canonical_result = server_instance.path_canonicalizer.Canonicalize(path)
+ redirect = canonical_result.path.lstrip('/')
if path != redirect:
- if channel is not None:
- redirect = '%s/%s' % (channel, canonical_path)
- return Response.Redirect('/' + redirect)
+ return Response.Redirect('/' + redirect,
+ permanent=canonical_result.permanent)
templates = server_instance.template_data_source_factory.Create(
self._request, path)
diff --git a/chrome/common/extensions/docs/server2/render_servlet_test.py b/chrome/common/extensions/docs/server2/render_servlet_test.py
index 75c91fe..f188671 100755
--- a/chrome/common/extensions/docs/server2/render_servlet_test.py
+++ b/chrome/common/extensions/docs/server2/render_servlet_test.py
@@ -8,23 +8,25 @@ import unittest
from local_file_system import LocalFileSystem
from render_servlet import RenderServlet
from server_instance import ServerInstance
-from servlet import Request
+from servlet import Request, Response
from test_util import DisableLogging, ReadFile
class _RenderServletDelegate(RenderServlet.Delegate):
- def CreateServerInstanceForChannel(self, channel):
+ def CreateServerInstance(self):
return ServerInstance.ForTest(LocalFileSystem.Create())
class RenderServletTest(unittest.TestCase):
def testExtensionAppRedirect(self):
request = Request.ForTest('storage.html')
- response = RenderServlet(request, _RenderServletDelegate()).Get()
- self.assertEqual(302, response.status)
+ self.assertEqual(
+ Response.Redirect('/extensions/storage.html', permanent=False),
+ RenderServlet(request, _RenderServletDelegate()).Get())
- def testDefaultChannel(self):
+ def testChannelRedirect(self):
request = Request.ForTest('stable/extensions/storage.html')
- response = RenderServlet(request, _RenderServletDelegate()).Get()
- self.assertEqual(302, response.status)
+ self.assertEqual(
+ Response.Redirect('/extensions/storage.html', permanent=True),
+ RenderServlet(request, _RenderServletDelegate()).Get())
@DisableLogging('warning')
def testNotFound(self):
diff --git a/chrome/common/extensions/docs/server2/rietveld_patcher.py b/chrome/common/extensions/docs/server2/rietveld_patcher.py
index f480c0c..adc1b95 100644
--- a/chrome/common/extensions/docs/server2/rietveld_patcher.py
+++ b/chrome/common/extensions/docs/server2/rietveld_patcher.py
@@ -174,3 +174,6 @@ class RietveldPatcher(Patcher):
paths,
binary,
self._fetcher))
+
+ def GetIdentity(self):
+ return self._issue
diff --git a/chrome/common/extensions/docs/server2/samples_data_source.py b/chrome/common/extensions/docs/server2/samples_data_source.py
index 9f150e7..790a2cd7 100644
--- a/chrome/common/extensions/docs/server2/samples_data_source.py
+++ b/chrome/common/extensions/docs/server2/samples_data_source.py
@@ -13,7 +13,7 @@ import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
import third_party.json_schema_compiler.model as model
import url_constants
-DEFAULT_ICON_PATH = '/images/sample-default-icon.png'
+DEFAULT_ICON_PATH = 'images/sample-default-icon.png'
class SamplesDataSource(object):
'''Constructs a list of samples and their respective files and api calls.
@@ -23,18 +23,18 @@ class SamplesDataSource(object):
Requests.
'''
def __init__(self,
- channel,
host_file_system,
compiled_host_fs_factory,
app_samples_file_system,
compiled_app_samples_fs_factory,
ref_resolver_factory,
- extension_samples_path):
+ extension_samples_path,
+ base_path):
self._host_file_system = host_file_system
self._app_samples_file_system = app_samples_file_system
- self._static_path = '/%s/static' % channel
self._ref_resolver = ref_resolver_factory.Create()
self._extension_samples_path = extension_samples_path
+ self._base_path = base_path
self._extensions_cache = compiled_host_fs_factory.Create(
self._MakeSamplesList,
SamplesDataSource,
@@ -50,6 +50,7 @@ class SamplesDataSource(object):
return SamplesDataSource(self._extensions_cache,
self._apps_cache,
self._extension_samples_path,
+ self._base_path,
request)
def _GetAPIItems(self, js_file):
@@ -159,9 +160,9 @@ class SamplesDataSource(object):
download_url = sample_base_path + '.zip'
if manifest_data['icon'] is None:
- icon_path = self._static_path + DEFAULT_ICON_PATH
+ icon_path = '%s/static/%s' % (self._base_path, DEFAULT_ICON_PATH)
else:
- icon_path = icon_base + '/' + manifest_data['icon']
+ icon_path = '%s/%s' % (icon_base, manifest_data['icon'])
manifest_data.update({
'icon': icon_path,
'id': hashlib.md5(url).hexdigest(),
@@ -178,10 +179,12 @@ class SamplesDataSource(object):
extensions_cache,
apps_cache,
extension_samples_path,
+ base_path,
request):
self._extensions_cache = extensions_cache
self._apps_cache = apps_cache
self._extension_samples_path = extension_samples_path
+ self._base_path = base_path
self._request = request
def _GetAcceptedLanguages(self):
diff --git a/chrome/common/extensions/docs/server2/samples_data_source_test.py b/chrome/common/extensions/docs/server2/samples_data_source_test.py
index 1e6052c..7977ca0 100755
--- a/chrome/common/extensions/docs/server2/samples_data_source_test.py
+++ b/chrome/common/extensions/docs/server2/samples_data_source_test.py
@@ -25,7 +25,7 @@ class SamplesDataSourceTest(unittest.TestCase):
return json.loads(self._ReadLocalFile(key))
def testFilterSamples(self):
- sds = SamplesDataSource({}, {}, 'fake_path', Request.ForTest('/'))
+ sds = SamplesDataSource({}, {}, 'fake_path', '.', Request.ForTest('/'))
sds.get = self._FakeGet
self.assertEquals(json.loads(self._ReadLocalFile('expected.json')),
sds.FilterSamples('samples.json', 'bobaloo'))
diff --git a/chrome/common/extensions/docs/server2/server_instance.py b/chrome/common/extensions/docs/server2/server_instance.py
index 251cee5..379cf98 100644
--- a/chrome/common/extensions/docs/server2/server_instance.py
+++ b/chrome/common/extensions/docs/server2/server_instance.py
@@ -37,7 +37,6 @@ import url_constants
class ServerInstance(object):
def __init__(self,
- channel,
object_store_creator,
host_file_system,
app_samples_file_system,
@@ -45,8 +44,6 @@ class ServerInstance(object):
compiled_fs_factory,
branch_utility,
host_file_system_creator):
- self.channel = channel
-
self.object_store_creator = object_store_creator
self.host_file_system = host_file_system
@@ -88,14 +85,14 @@ class ServerInstance(object):
else:
extension_samples_fs = self.host_file_system
self.samples_data_source_factory = SamplesDataSource.Factory(
- channel,
extension_samples_fs,
CompiledFileSystem.Factory(extension_samples_fs, object_store_creator),
self.app_samples_file_system,
CompiledFileSystem.Factory(self.app_samples_file_system,
object_store_creator),
self.ref_resolver_factory,
- svn_constants.EXAMPLES_PATH)
+ svn_constants.EXAMPLES_PATH,
+ base_path)
self.api_data_source_factory.SetSamplesDataSourceFactory(
self.samples_data_source_factory)
@@ -107,11 +104,9 @@ class ServerInstance(object):
self.sidenav_data_source_factory = SidenavDataSource.Factory(
self.compiled_host_fs_factory,
- svn_constants.JSON_PATH,
- base_path)
+ svn_constants.JSON_PATH)
self.template_data_source_factory = TemplateDataSource.Factory(
- channel,
self.api_data_source_factory,
self.api_list_data_source_factory,
self.intro_data_source_factory,
@@ -130,9 +125,7 @@ class ServerInstance(object):
self.compiled_host_fs_factory,
svn_constants.DOCS_PATH)
- self.path_canonicalizer = PathCanonicalizer(
- channel,
- self.compiled_host_fs_factory)
+ self.path_canonicalizer = PathCanonicalizer(self.compiled_host_fs_factory)
# TODO(kalman): delete content cache.
self.content_cache = self.compiled_host_fs_factory.CreateIdentity(
@@ -146,8 +139,7 @@ class ServerInstance(object):
@staticmethod
def ForTest(file_system):
object_store_creator = ObjectStoreCreator.ForTest()
- return ServerInstance('test',
- object_store_creator,
+ return ServerInstance(object_store_creator,
file_system,
EmptyDirFileSystem(),
'',
@@ -159,15 +151,12 @@ class ServerInstance(object):
@staticmethod
def ForLocal():
- channel = 'trunk'
- object_store_creator = ObjectStoreCreator(channel,
- start_empty=False,
+ object_store_creator = ObjectStoreCreator(start_empty=False,
store_type=TestObjectStore)
host_file_system_creator = HostFileSystemCreator.ForLocal(
object_store_creator)
- trunk_file_system = host_file_system_creator.Create('trunk')
+ trunk_file_system = host_file_system_creator.Create()
return ServerInstance(
- channel,
object_store_creator,
trunk_file_system,
EmptyDirFileSystem(),
diff --git a/chrome/common/extensions/docs/server2/servlet.py b/chrome/common/extensions/docs/server2/servlet.py
index 35399b4..59ebb5b 100644
--- a/chrome/common/extensions/docs/server2/servlet.py
+++ b/chrome/common/extensions/docs/server2/servlet.py
@@ -15,8 +15,8 @@ class Request(object):
return Request(path, host, headers or {})
def __repr__(self):
- return 'Request(path=%s, host=%s, headers=%s entries)' % (
- self.path, self.host, len(self.headers.keys()))
+ return 'Request(path=%s, host=%s, headers=%s)' % (
+ self.path, self.host, self.headers)
def __str__(self):
return repr(self)
@@ -105,9 +105,18 @@ class Response(object):
return (None, None)
return (self.headers.get('Location'), self.status == 301)
+ def __eq__(self, other):
+ return (isinstance(other, self.__class__) and
+ str(other.content) == str(self.content) and
+ other.headers == self.headers and
+ other.status == self.status)
+
+ def __ne__(self, other):
+ return not (self == other)
+
def __repr__(self):
- return 'Response(content=%s bytes, status=%s, headers=%s entries)' % (
- len(self.content), self.status, len(self.headers.keys()))
+ return 'Response(content=%s bytes, status=%s, headers=%s)' % (
+ len(self.content), self.status, self.headers)
def __str__(self):
return repr(self)
diff --git a/chrome/common/extensions/docs/server2/sidenav_data_source.py b/chrome/common/extensions/docs/server2/sidenav_data_source.py
index 98d7fd2..bf46727 100644
--- a/chrome/common/extensions/docs/server2/sidenav_data_source.py
+++ b/chrome/common/extensions/docs/server2/sidenav_data_source.py
@@ -13,21 +13,17 @@ class SidenavDataSource(object):
menu.
"""
class Factory(object):
- def __init__(self, compiled_fs_factory, json_path, base_path):
+ def __init__(self, compiled_fs_factory, json_path):
self._cache = compiled_fs_factory.Create(self._CreateSidenavDict,
SidenavDataSource)
self._json_path = json_path
- self._base_path = base_path
def Create(self, path):
"""Create a SidenavDataSource, binding it to |path|. |path| is the url
of the page that is being rendered. It is used to determine which item
in the sidenav should be highlighted.
"""
- return SidenavDataSource(self._cache,
- self._json_path,
- path,
- self._base_path)
+ return SidenavDataSource(self._cache, self._json_path, path)
def _AddLevels(self, items, level):
"""Levels represent how deeply this item is nested in the sidenav. We
@@ -43,11 +39,10 @@ class SidenavDataSource(object):
self._AddLevels(items, 2);
return items
- def __init__(self, cache, json_path, path, base_path):
+ def __init__(self, cache, json_path, path):
self._cache = cache
self._json_path = json_path
self._href = '/' + path
- self._file_dir = base_path
def _AddSelected(self, items):
for item in items:
@@ -70,7 +65,7 @@ class SidenavDataSource(object):
if not href.startswith('/'):
logging.warn('Paths in sidenav must be qualified. %s is not.' % href)
href = '/' + href
- item['href'] = '%s%s' % (self._file_dir, href)
+ item['href'] = href
def get(self, key):
sidenav = copy.deepcopy(self._cache.GetFromFile(
diff --git a/chrome/common/extensions/docs/server2/sidenav_data_source_test.py b/chrome/common/extensions/docs/server2/sidenav_data_source_test.py
index 827801a5..e7eddc1 100755
--- a/chrome/common/extensions/docs/server2/sidenav_data_source_test.py
+++ b/chrome/common/extensions/docs/server2/sidenav_data_source_test.py
@@ -35,16 +35,14 @@ class SamplesDataSourceTest(unittest.TestCase):
def testLevels(self):
sidenav_data_source = SidenavDataSource.Factory(self._compiled_fs_factory,
- self._json_path,
- '').Create('')
+ self._json_path).Create('')
sidenav_json = sidenav_data_source.get('test')
self._CheckLevels(sidenav_json)
def testSelected(self):
sidenav_data_source = SidenavDataSource.Factory(
self._compiled_fs_factory,
- self._json_path,
- '').Create('www.b.com')
+ self._json_path).Create('www.b.com')
sidenav_json = sidenav_data_source.get('test')
# This will be prettier once JSON is loaded with an OrderedDict.
for item in sidenav_json:
@@ -60,8 +58,7 @@ class SamplesDataSourceTest(unittest.TestCase):
def testAbsolutePath(self):
sidenav_data_source = SidenavDataSource.Factory(
self._compiled_fs_factory,
- self._json_path,
- '/trunk').Create('absolute_path/test.html')
+ self._json_path).Create('absolute_path/test.html')
sidenav_json = sidenav_data_source.get('absolute_path')
self.assertEqual(
sidenav_json,
diff --git a/chrome/common/extensions/docs/server2/subversion_file_system.py b/chrome/common/extensions/docs/server2/subversion_file_system.py
index c0ff68c..62e0fc4 100644
--- a/chrome/common/extensions/docs/server2/subversion_file_system.py
+++ b/chrome/common/extensions/docs/server2/subversion_file_system.py
@@ -54,12 +54,11 @@ class SubversionFileSystem(FileSystem):
'''Class to fetch resources from src.chromium.org.
'''
@staticmethod
- def Create(branch, revision=None):
+ def Create(branch='trunk', revision=None):
if branch == 'trunk':
svn_path = 'trunk/src/%s' % svn_constants.EXTENSIONS_PATH
else:
- svn_path = 'branches/%s/src/%s' % (branch,
- svn_constants.EXTENSIONS_PATH)
+ svn_path = 'branches/%s/src/%s' % (branch, svn_constants.EXTENSIONS_PATH)
return SubversionFileSystem(
AppEngineUrlFetcher('%s/%s' % (url_constants.SVN_URL, svn_path)),
AppEngineUrlFetcher('%s/%s' % (url_constants.VIEWVC_URL, svn_path)),
diff --git a/chrome/common/extensions/docs/server2/template_data_source.py b/chrome/common/extensions/docs/server2/template_data_source.py
index 7a1f6e1..ca065d33 100644
--- a/chrome/common/extensions/docs/server2/template_data_source.py
+++ b/chrome/common/extensions/docs/server2/template_data_source.py
@@ -4,7 +4,6 @@
import logging
-from branch_utility import BranchUtility
from docs_server_utils import FormatKey
from file_system import FileNotFoundError
from third_party.handlebar import Handlebar
@@ -22,16 +21,6 @@ _STRING_CONSTANTS = {
'properties': 'properties',
}
-def _MakeChannelDict(channel_name):
- channel_dict = {
- 'channels': [{'name': name} for name in BranchUtility.GetAllChannelNames()],
- 'current': channel_name
- }
- for channel in channel_dict['channels']:
- if channel['name'] == channel_name:
- channel['isCurrent'] = True
- return channel_dict
-
class TemplateDataSource(object):
"""Renders Handlebar templates, providing them with the context in which to
render.
@@ -49,7 +38,6 @@ class TemplateDataSource(object):
individual Requests.
"""
def __init__(self,
- channel_name,
api_data_source_factory,
api_list_data_source_factory,
intro_data_source_factory,
@@ -60,7 +48,6 @@ class TemplateDataSource(object):
public_template_path,
private_template_path,
base_path):
- self._branch_info = _MakeChannelDict(channel_name)
self._api_data_source_factory = api_data_source_factory
self._api_list_data_source_factory = api_list_data_source_factory
self._intro_data_source_factory = intro_data_source_factory
@@ -71,7 +58,7 @@ class TemplateDataSource(object):
self._ref_resolver = ref_resolver_factory.Create()
self._public_template_path = public_template_path
self._private_template_path = private_template_path
- self._static_resources = '%s/static' % base_path
+ self._base_path = base_path
def _CreateTemplate(self, template_name, text):
return Handlebar(self._ref_resolver.ResolveAllLinks(text))
@@ -80,7 +67,6 @@ class TemplateDataSource(object):
"""Returns a new TemplateDataSource bound to |request|.
"""
return TemplateDataSource(
- self._branch_info,
self._api_data_source_factory.Create(request),
self._api_list_data_source_factory.Create(),
self._intro_data_source_factory.Create(),
@@ -89,10 +75,9 @@ class TemplateDataSource(object):
self._cache,
self._public_template_path,
self._private_template_path,
- self._static_resources)
+ self._base_path)
def __init__(self,
- branch_info,
api_data_source,
api_list_data_source,
intro_data_source,
@@ -101,8 +86,7 @@ class TemplateDataSource(object):
cache,
public_template_path,
private_template_path,
- static_resources):
- self._branch_info = branch_info
+ base_path):
self._api_list_data_source = api_list_data_source
self._intro_data_source = intro_data_source
self._samples_data_source = samples_data_source
@@ -111,7 +95,7 @@ class TemplateDataSource(object):
self._cache = cache
self._public_template_path = public_template_path
self._private_template_path = private_template_path
- self._static_resources = static_resources
+ self._base_path = base_path
def Render(self, template_name):
"""This method will render a template named |template_name|, fetching all
@@ -125,19 +109,13 @@ class TemplateDataSource(object):
render_data = template.render({
'api_list': self._api_list_data_source,
'apis': self._api_data_source,
- 'branchInfo': self._branch_info,
'intros': self._intro_data_source,
'sidenavs': self._sidenav_data_source,
'partials': self,
'samples': self._samples_data_source,
- 'static': self._static_resources,
'apps_samples_url': url_constants.GITHUB_BASE,
- # TODO(kalman): this is wrong, it's always getting from trunk, but meh
- # it hardly ever shows up (only in the "cannot fetch samples" message).
- # In fact I don't even know if it can show up anymore due the samples data
- # being persisent. In any case, when the channel distinctions are gone
- # this can go away, so, double meh.
'extensions_samples_url': url_constants.EXTENSIONS_SAMPLES,
+ 'static': self._base_path + '/static',
'strings': _STRING_CONSTANTS,
'true': True,
'false': False
diff --git a/chrome/common/extensions/docs/server2/template_data_source_test.py b/chrome/common/extensions/docs/server2/template_data_source_test.py
index 3de80ba..7ac39d0 100755
--- a/chrome/common/extensions/docs/server2/template_data_source_test.py
+++ b/chrome/common/extensions/docs/server2/template_data_source_test.py
@@ -66,7 +66,6 @@ class TemplateDataSourceTest(unittest.TestCase):
path = 'extensions/foo'
return factory.Create(Request.ForTest(path), path)
return create_from_factory(TemplateDataSource.Factory(
- 'fake_channel',
api_data_factory,
self._fake_api_list_data_source_factory,
self._fake_intro_data_source_factory,
diff --git a/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/absolute_path_sidenav_expected.json b/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/absolute_path_sidenav_expected.json
index 787a96d..4f24152 100644
--- a/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/absolute_path_sidenav_expected.json
+++ b/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/absolute_path_sidenav_expected.json
@@ -1,11 +1,11 @@
[
{
"title": "H1",
- "href": "/trunk/absolute_path/h1.html",
+ "href": "/absolute_path/h1.html",
"level": 2
},
{
- "href": "/trunk/absolute_path/h2.html",
+ "href": "/absolute_path/h2.html",
"title": "H2",
"level": 2,
"items": [
@@ -20,7 +20,7 @@
},
{
"title": "Y",
- "href": "/trunk/absolute_path/y.html",
+ "href": "/absolute_path/y.html",
"level": 4
},
{
@@ -30,7 +30,7 @@
},
{
"title": "X3",
- "href": "/trunk/test.html",
+ "href": "/test.html",
"level": 4
}
]
@@ -49,7 +49,7 @@
},
{
"title": "X3",
- "href": "/trunk/test.html",
+ "href": "/test.html",
"level": 2
}
]
diff --git a/chrome/common/extensions/docs/server2/test_patcher.py b/chrome/common/extensions/docs/server2/test_patcher.py
index e1516af..2e594df 100644
--- a/chrome/common/extensions/docs/server2/test_patcher.py
+++ b/chrome/common/extensions/docs/server2/test_patcher.py
@@ -33,3 +33,6 @@ class TestPatcher(Patcher):
for path in paths))
except KeyError:
raise FileNotFoundError('One of %s is deleted in the patch.' % paths)
+
+ def GetIdentity(self):
+ return self.__class__.__name__
diff --git a/chrome/common/extensions/docs/static/css/site.css b/chrome/common/extensions/docs/static/css/site.css
index 6a4aab7..5528b66 100644
--- a/chrome/common/extensions/docs/static/css/site.css
+++ b/chrome/common/extensions/docs/static/css/site.css
@@ -205,7 +205,6 @@ dd {
font-family: inherit;
font-size: inherit;
font-style: inherit;
- font-weight: bold;
margin: 0; padding: 0;
outline: none;
}
@@ -214,25 +213,12 @@ dd {
outline: 1px dotted;
}
-#branch-chooser {
+#platform-chooser {
display: inline;
position: relative;
- font-weight: bold;
-}
-#branch-chooser .stable {
- color: #0d68ae;
-}
-#branch-chooser .beta {
- color: #409544;
-}
-#branch-chooser .dev {
- color: #dba30d;
-}
-#branch-chooser .trunk {
- color: #e02629;
}
-#branch-chooser-popup {
+#platform-chooser-popup {
/* Make it appear to be an extension of the header. */
background-color: white;
border: 1px solid #F5F5F5;
@@ -245,14 +231,14 @@ dd {
left: -6px;
}
-#branch-chooser-popup button {
+#platform-chooser-popup button {
display: block;
padding: 6px;
width: 100%;
text-align: left;
}
-#branch-chooser-popup button:hover {
- background-color: #F5F5F5;
+#platform-chooser-popup button:hover {
+ color: #4787ed;
}
button.google-button {
diff --git a/chrome/common/extensions/docs/static/js/branch.js b/chrome/common/extensions/docs/static/js/branch.js
deleted file mode 100644
index bd21497..0000000
--- a/chrome/common/extensions/docs/static/js/branch.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.
-
-(function() {
-
-function init() {
- var branchChooser = document.getElementById('branch-chooser-popup');
- Array.prototype.forEach.call(branchChooser.getElementsByTagName('button'),
- function(button) {
- button.addEventListener('click', function(event) {
- choose(button.className);
- event.stopPropagation();
- });
- });
-}
-
-function choose(branch) {
- var currentBranch = window.bootstrap.branchInfo.current;
- var path = window.location.pathname.split('/');
- if (path[0] == '')
- path = path.slice(1);
- var index = path.indexOf(currentBranch);
- if (index != -1)
- path[index] = branch;
- else
- path.splice(0, 0, branch);
- window.location.pathname = '/' + path.join('/');
-}
-
-init();
-
-})()
diff --git a/chrome/common/extensions/docs/static/js/platform_chooser.js b/chrome/common/extensions/docs/static/js/platform_chooser.js
new file mode 100644
index 0000000..5492cff
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/platform_chooser.js
@@ -0,0 +1,18 @@
+// Copyright 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.
+
+// Intializes the platform chooser, the widget in the top left corner with the
+// 'Apps...Extensions' dropdown.
+(function() {
+
+var platformChooser = document.getElementById('platform-chooser-popup');
+Array.prototype.forEach.call(platformChooser.getElementsByTagName('button'),
+ function(button) {
+ button.addEventListener('click', function(event) {
+ window.location.assign(button.getAttribute('data-href'));
+ event.stopPropagation();
+ });
+});
+
+})()
diff --git a/chrome/common/extensions/docs/templates/private/apps_footer.html b/chrome/common/extensions/docs/templates/private/apps_footer.html
index f997b85..155c851 100644
--- a/chrome/common/extensions/docs/templates/private/apps_footer.html
+++ b/chrome/common/extensions/docs/templates/private/apps_footer.html
@@ -1,8 +1,7 @@
<script>
window.bootstrap = {
api_names: {{*api_list.apps.chrome}}.concat(
- {{*api_list.apps.experimental}}),
- branchInfo: {{*branchInfo}}
+ {{*api_list.apps.experimental}})
};
</script>
{{+partials.footer}}
diff --git a/chrome/common/extensions/docs/templates/private/extensions_footer.html b/chrome/common/extensions/docs/templates/private/extensions_footer.html
index 14d498a..c8f73aa 100644
--- a/chrome/common/extensions/docs/templates/private/extensions_footer.html
+++ b/chrome/common/extensions/docs/templates/private/extensions_footer.html
@@ -1,8 +1,7 @@
<script>
window.bootstrap = {
api_names: {{*api_list.extensions.chrome}}.concat(
- {{*api_list.extensions.experimental}}),
- branchInfo: {{*branchInfo}}
+ {{*api_list.extensions.experimental}})
};
</script>
{{+partials.footer}}
diff --git a/chrome/common/extensions/docs/templates/private/footer.html b/chrome/common/extensions/docs/templates/private/footer.html
index f713c97..7dd195c 100644
--- a/chrome/common/extensions/docs/templates/private/footer.html
+++ b/chrome/common/extensions/docs/templates/private/footer.html
@@ -11,7 +11,7 @@
<p>
©2013 Google
</p>
- <script src="{{static}}/js/branch.js" type="text/javascript"></script>
+ <script src="{{static}}/js/platform_chooser.js" type="text/javascript"></script>
<script src="{{static}}/js/popup.js" type="text/javascript"></script>
<script src="{{static}}/js/prettify.js" type="text/javascript"></script>
<script src="{{static}}/js/scroll.js" type="text/javascript"></script>
diff --git a/chrome/common/extensions/docs/templates/private/header_body.html b/chrome/common/extensions/docs/templates/private/header_body.html
index fa7149c..23f6bc9 100644
--- a/chrome/common/extensions/docs/templates/private/header_body.html
+++ b/chrome/common/extensions/docs/templates/private/header_body.html
@@ -1,21 +1,16 @@
<div id="gc-topnav">
<table><tr>
<td id="chrome-logo">
- <a href="." title="Google Chrome {{title}}">
- <img src="{{static}}/images/chrome-logo.png" alt="">
- chrome {{platform}}s
+ <a href="/{{platform}}s/" title="Google Chrome {{title}}">
+ <img src="{{static}}/images/chrome-logo.png" alt=""> chrome
</a>
- <div id="branch-chooser">
- <button id="branch-chooser-toggle"
- class="{{branchInfo.current}}" data-menu="branch-chooser-popup">
- {{branchInfo.current}} &#x25be;&#x202F;
+ <div id="platform-chooser">
+ <button id="platform-chooser-toggle" data-menu="platform-chooser-popup">
+ {{platform}}s &#x25be;&#x202F;
</button>
- <div id="branch-chooser-popup">
- {{#branchInfo.channels}}
- {{^isCurrent}}
- <button class="{{name}}">{{name}}</button>
- {{/isCurrent}}
- {{/branchInfo.channels}}
+ <div id="platform-chooser-popup">
+ <button class="apps" data-href="/apps/about_apps.html">apps</button>
+ <button class="extensions" data-href="/extensions">extensions</button>
</div>
</div>
</td>