diff options
author | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 01:16:40 +0000 |
---|---|---|
committer | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 01:16:40 +0000 |
commit | c6cf710180d8c15bc11a313a2917a84c257eaf5c (patch) | |
tree | 74320b13f0e314f35566a7517931fd0f2dbd54ee | |
parent | 3b7a703bcfa46f7407aa846e667ee237cc78e55b (diff) | |
download | chromium_src-c6cf710180d8c15bc11a313a2917a84c257eaf5c.zip chromium_src-c6cf710180d8c15bc11a313a2917a84c257eaf5c.tar.gz chromium_src-c6cf710180d8c15bc11a313a2917a84c257eaf5c.tar.bz2 |
Docserver: Render the title and table of contents after rendering each page,
not before. This gains flexibility (importantly, the flexibility to infer the
title/TOC of page content served outside of the extensions/apps conventions)
for cachability. However, benchmarking shows negligible performance degradation.
BUG=320339
R=yoz@chromium.org
Review URL: https://codereview.chromium.org/89223002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237465 0039d316-1c4b-4281-b951-d872f2087c98
15 files changed, 237 insertions, 190 deletions
diff --git a/chrome/common/extensions/docs/server2/document_renderer.py b/chrome/common/extensions/docs/server2/document_renderer.py new file mode 100644 index 0000000..e4621ea --- /dev/null +++ b/chrome/common/extensions/docs/server2/document_renderer.py @@ -0,0 +1,34 @@ +# 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. + +from document_parser import ParseDocument + + +class DocumentRenderer(object): + '''Performs document-level rendering such as the title and table of contents: + pulling that data out of the document, then replacing the $(title) and + $(table_of_contents) tokens with them. + + This can be thought of as a parallel to TemplateRenderer; while + TemplateRenderer is responsible for interpreting templates and rendering files + within the template engine, DocumentRenderer is responsible for interpreting + higher-level document concepts like the title and TOC, then performing string + replacement for them. The syntax for this replacement is $(...) where ... is + the concept. Currently title and table_of_contents are supported. + ''' + + def __init__(self, table_of_contents_renderer): + self._table_of_contents_renderer = table_of_contents_renderer + + def Render(self, document, render_title=False): + parsed_document = ParseDocument(document, expect_title=render_title) + toc_text, toc_warnings = self._table_of_contents_renderer.Render( + parsed_document.sections) + + # Only 1 title and 1 table of contents substitution allowed; in the common + # case, save necessarily running over the entire file. + if parsed_document.title: + document = document.replace('$(title)', parsed_document.title, 1) + return (document.replace('$(table_of_contents)', toc_text, 1), + parsed_document.warnings + toc_warnings) diff --git a/chrome/common/extensions/docs/server2/document_renderer_test.py b/chrome/common/extensions/docs/server2/document_renderer_test.py new file mode 100755 index 0000000..5f9cb76 --- /dev/null +++ b/chrome/common/extensions/docs/server2/document_renderer_test.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# 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. + +import unittest + +from document_renderer import DocumentRenderer + + +class _FakeTableOfContentsRenderer(object): + def __init__(self, string): + self._string = string + + def Render(self, _): + return self._string, [] + + +class DocumentRendererUnittest(unittest.TestCase): + def setUp(self): + self._renderer = DocumentRenderer(_FakeTableOfContentsRenderer('t-o-c')) + + def testNothingToSubstitute(self): + document = 'hello world' + + text, warnings = self._renderer.Render(document) + self.assertEqual(document, text) + self.assertEqual([], warnings) + + text, warnings = self._renderer.Render(document, render_title=True) + self.assertEqual(document, text) + self.assertEqual(['Expected a title'], warnings) + + def testTitles(self): + document = '<h1>title</h1> then $(title) then another $(title)' + + text, warnings = self._renderer.Render(document) + self.assertEqual(document, text) + self.assertEqual(['Found unexpected title "title"'], warnings) + + text, warnings = self._renderer.Render(document, render_title=True) + self.assertEqual('<h1>title</h1> then title then another $(title)', text) + self.assertEqual([], warnings) + + def testTocs(self): + document = ('here is a toc $(table_of_contents) ' + 'and another $(table_of_contents)') + expected_document = 'here is a toc t-o-c and another $(table_of_contents)' + + text, warnings = self._renderer.Render(document) + self.assertEqual(expected_document, text) + self.assertEqual([], warnings) + + text, warnings = self._renderer.Render(document, render_title=True) + self.assertEqual(expected_document, text) + self.assertEqual(['Expected a title'], warnings) + + def testTitleAndToc(self): + document = '<h1>title</h1> $(title) and $(table_of_contents)' + + text, warnings = self._renderer.Render(document) + self.assertEqual('<h1>title</h1> $(title) and t-o-c', text) + self.assertEqual(['Found unexpected title "title"'], warnings) + + text, warnings = self._renderer.Render(document, render_title=True) + self.assertEqual('<h1>title</h1> title and t-o-c', text) + self.assertEqual([], warnings) + + +if __name__ == '__main__': + unittest.main() diff --git a/chrome/common/extensions/docs/server2/intro_data_source.py b/chrome/common/extensions/docs/server2/intro_data_source.py index c345a6e..67d2671 100644 --- a/chrome/common/extensions/docs/server2/intro_data_source.py +++ b/chrome/common/extensions/docs/server2/intro_data_source.py @@ -9,26 +9,13 @@ import re from data_source import DataSource from docs_server_utils import FormatKey -from document_parser import ParseDocument, RemoveTitle +from document_parser import ParseDocument from extensions_paths import INTROS_TEMPLATES, ARTICLES_TEMPLATES from file_system import FileNotFoundError from future import Future from third_party.handlebar import Handlebar -class _HandlebarWithContext(Handlebar): - '''Extends Handlebar with a get() method so that templates can not only - render intros with {{+intro}} but also access properties on them, like - {{intro.title}}, {{intro.toc}}, etc. - ''' - - def __init__(self, text, name, context): - Handlebar.__init__(self, text, name=name) - self._context = context - - def get(self, key): - return self._context.get(key) - # TODO(kalman): rename this HTMLDataSource or other, then have separate intro # article data sources created as instances of it. @@ -49,51 +36,9 @@ class IntroDataSource(DataSource): def _MakeIntro(self, intro_path, intro): # Guess the name of the API from the path to the intro. api_name = os.path.splitext(intro_path.split('/')[-1])[0] - intro_with_links = self._ref_resolver.ResolveAllLinks( - intro, namespace=api_name) - - # The templates generate a title for intros based on the API name. - expect_title = '/intros/' not in intro_path - - # TODO(kalman): In order to pick up every header tag, and therefore make a - # complete TOC, the render context of the Handlebar needs to be passed - # through to here. Even if there were a mechanism to do this it would - # break caching; we'd need to do the TOC parsing *after* rendering the full - # template, and that would probably be expensive. - parse_result = ParseDocument( - self._template_renderer.Render(Handlebar(intro_with_links), - self._request, - # Avoid nasty surprises. - data_sources=('partials', 'strings'), - warn=False), - expect_title=expect_title) - - if parse_result.warnings: - logging.warning('%s: %s' % (intro_path, '\n'.join(parse_result.warnings))) - - # Convert the TableOfContentEntries into the data the templates want. - def make_toc(entries): - return [{ - 'link': entry.attributes.get('id', ''), - 'subheadings': make_toc(entry.entries), - 'title': entry.name, - } for entry in entries] - - if expect_title: - intro_with_links, warning = RemoveTitle(intro_with_links) - if warning: - logging.warning('%s: %s' % (intro_path, warning)) - - if parse_result.sections: - # Only use the first section for now. - toc = make_toc(parse_result.sections[0].structure) - else: - toc = [] - - return _HandlebarWithContext(intro_with_links, intro_path, { - 'title': parse_result.title, - 'toc': toc - }) + return Handlebar( + self._ref_resolver.ResolveAllLinks(intro, namespace=api_name), + name=intro_path) def get(self, key): path = FormatKey(key) diff --git a/chrome/common/extensions/docs/server2/intro_data_source_test.py b/chrome/common/extensions/docs/server2/intro_data_source_test.py index 37f07f4..fd8be07 100755 --- a/chrome/common/extensions/docs/server2/intro_data_source_test.py +++ b/chrome/common/extensions/docs/server2/intro_data_source_test.py @@ -19,26 +19,12 @@ class IntroDataSourceTest(unittest.TestCase): intro_data = intro_data_source.get('test_intro') article_data = intro_data_source.get('test_article') - self.assertEqual('hi', article_data.get('title')) - self.assertEqual(None, intro_data.get('title')) + expected_intro = 'you<h2>first</h2><h3>inner</h3><h2>second</h2>' + # Article still has the header. + expected_article = '<h1>hi</h1>' + expected_intro - # TODO(kalman): test links. - expected_toc = [{ - 'link': '', - 'subheadings': [{'link': '', 'subheadings': [], 'title': u'inner'}], - 'title': u'first', - }, { - 'link': '', - 'subheadings': [], - 'title': u'second' - } - ] - self.assertEqual(expected_toc, article_data.get('toc')) - self.assertEqual(expected_toc, intro_data.get('toc')) - - expected_text = 'you<h2>first</h2><h3>inner</h3><h2>second</h2>' - self.assertEqual(expected_text, article_data.Render().text) - self.assertEqual(expected_text, intro_data.Render().text) + self.assertEqual(expected_intro, intro_data.Render().text) + self.assertEqual(expected_article, article_data.Render().text) if __name__ == '__main__': diff --git a/chrome/common/extensions/docs/server2/render_servlet.py b/chrome/common/extensions/docs/server2/render_servlet.py index e7ed877..10cc7ae 100644 --- a/chrome/common/extensions/docs/server2/render_servlet.py +++ b/chrome/common/extensions/docs/server2/render_servlet.py @@ -98,7 +98,12 @@ class RenderServlet(Servlet): content = content_and_type.content if isinstance(content, Handlebar): - content = server_instance.template_renderer.Render(content, self._request) + # HACK: the Google ID thing (google2ed...) doesn't have a title. + content, warnings = server_instance.document_renderer.Render( + server_instance.template_renderer.Render(content, self._request), + render_title=path != 'google2ed1af765c529f57.html') + if warnings: + logging.warning('\n'.join(warnings)) content_type = content_and_type.content_type if isinstance(content, unicode): diff --git a/chrome/common/extensions/docs/server2/server_instance.py b/chrome/common/extensions/docs/server2/server_instance.py index fb783da..b313bd1 100644 --- a/chrome/common/extensions/docs/server2/server_instance.py +++ b/chrome/common/extensions/docs/server2/server_instance.py @@ -9,6 +9,7 @@ from api_models import APIModels from availability_finder import AvailabilityFinder from compiled_file_system import CompiledFileSystem from content_providers import ContentProviders +from document_renderer import DocumentRenderer from empty_dir_file_system import EmptyDirFileSystem from environment import IsDevServer from features_bundle import FeaturesBundle @@ -20,6 +21,7 @@ from object_store_creator import ObjectStoreCreator from path_canonicalizer import PathCanonicalizer from reference_resolver import ReferenceResolver from samples_data_source import SamplesDataSource +from table_of_contents_renderer import TableOfContentsRenderer from template_renderer import TemplateRenderer from test_branch_utility import TestBranchUtility from test_object_store import TestObjectStore @@ -65,6 +67,10 @@ class ServerInstance(object): assert base_path.startswith('/') and base_path.endswith('/') self.base_path = base_path + self.document_renderer = DocumentRenderer(TableOfContentsRenderer( + host_fs_at_trunk, + compiled_fs_factory)) + self.host_file_system_iterator = HostFileSystemIterator( host_file_system_provider, branch_utility) diff --git a/chrome/common/extensions/docs/server2/table_of_contents_renderer.py b/chrome/common/extensions/docs/server2/table_of_contents_renderer.py new file mode 100644 index 0000000..71c94a0 --- /dev/null +++ b/chrome/common/extensions/docs/server2/table_of_contents_renderer.py @@ -0,0 +1,45 @@ +# 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. + +from extensions_paths import PRIVATE_TEMPLATES + + +class TableOfContentsRenderer(object): + '''Renders a table of contents pulled from a list of DocumentSections + returned from document_parser.ParseDocument. + + This performs key functionality of DocumentRenderer, pulled into its own + class for testability. + ''' + + def __init__(self, host_file_system, compiled_fs_factory): + self._templates = compiled_fs_factory.ForTemplates(host_file_system) + + def Render(self, sections): + '''Renders a list of DocumentSections |sections| and returns a tuple + (text, warnings). + ''' + table_of_contents_template = self._templates.GetFromFile( + '%s/table_of_contents.html' % PRIVATE_TEMPLATES).Get() + + def make_toc_items(entries): + return [{ + 'attributes': [(key, val) for key, val in entry.attributes.iteritems() + if key != 'id'], + 'link': entry.attributes.get('id', ''), + 'subheadings': make_toc_items(entry.entries), + 'title': entry.name, + } for entry in entries] + + toc_items = [] + for section in sections: + items_for_section = make_toc_items(section.structure) + if toc_items and items_for_section: + items_for_section[0]['separator'] = True + toc_items.extend(items_for_section) + + table_of_contents = table_of_contents_template.Render({ + 'items': toc_items + }) + return table_of_contents.text, table_of_contents.errors diff --git a/chrome/common/extensions/docs/static/css/site.css b/chrome/common/extensions/docs/static/css/site.css index 34fbcd5..40a158b 100644 --- a/chrome/common/extensions/docs/static/css/site.css +++ b/chrome/common/extensions/docs/static/css/site.css @@ -64,10 +64,6 @@ a:visited { color: #236bb2; } -#toc a { - color: black; -} - li { margin: .3em 0 0 1.5em; padding: 0; @@ -149,6 +145,14 @@ dd { padding: 0; } +section { + margin-top: 1em; + padding-top: 1em; +} +section + section { + border-top: 1px solid #F5F5F5; +} + #gc-container { height: auto; margin-top: 2em; @@ -311,11 +315,16 @@ button.google-button:active { line-height: 130%; } -#gc-pagecontent h2 { +#gc-pagecontent h2, +#api-reference-header { font-size: 170%; font-weight: normal; } +#api-reference-header { + font-weight: bold; +} + #gc-pagecontent h3 { font-size: 130%; } @@ -501,8 +510,14 @@ button.google-button:active { they float over. So we add this border to force the issue. */ border-left: 20px solid white; float: right; - margin: 5px 0px 0px 0px; - padding: 5px; + /* The spacing in the TOC is based around 10px: + * - The spacing between text and the box border is 10px, made up of a + * top/bottom padding of 10px here and a left/right margin on every + * TOC link of 10px. + * - The separator has a 5px top margin and 5px top padding for a total + * separation of 10px between each section. + */ + padding: 10px 0; position: relative; width: 250px; word-break: break-word; @@ -511,39 +526,27 @@ button.google-button:active { #toc * { list-style: none; + margin: 0; overflow: hidden; padding: 0; text-overflow: ellipsis; white-space: nowrap; } -#toc h2 { - border: none; - font-size: 100%; - font-weight: bold; - margin: 0; - padding: 0; -} - -#toc ol { - margin: 1em 0 0 0; -} - -#toc ol li { +#toc a { + color: black; line-height: 1.2em; - margin: .5em 0 .5em 1em; -} - -#toc ol li ol { - margin: 0; + margin: 5px 0 5px 10px; } -#toc ol li ol li { - margin: .5em 0 0 1em; +#toc li { + margin: 0 10px 0 10px; } -#toc .api-reference { +#toc .separator { border-top: 1px solid #e5e5e5; + margin-top: 5px; + padding-top: 5px; } .filtered_item { @@ -616,6 +619,10 @@ tabs content pre { * API references. */ +h2#apiReference { + font-size: 28pt; +} + .type_name, .variable, .property { diff --git a/chrome/common/extensions/docs/templates/private/api_reference.html b/chrome/common/extensions/docs/templates/private/api_reference.html index 612cfed..30dc4f96 100644 --- a/chrome/common/extensions/docs/templates/private/api_reference.html +++ b/chrome/common/extensions/docs/templates/private/api_reference.html @@ -1,5 +1,6 @@ -<h1 id="apiReference">{{+partials.api_title api:api/}} reference</h1> <div class="api_reference"> + {{- This is a span not a header-tag so that it doesn't show up in the TOC. -}} + <span id="api-reference-header">Reference</span> {{?api.types}} <h2 id="types">Types</h2> {{#t:api.types}} @@ -25,7 +26,7 @@ {{/}} {{/api.events}} {{?api.domEvents}} - <h2 id="dom_events">DOM Events</h2> + <h2 id="dom_events">DOM events</h2> <dd> Listeners can be added for these events using the standard HTML <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener">addEventListener</a> diff --git a/chrome/common/extensions/docs/templates/private/standard_apps_api.html b/chrome/common/extensions/docs/templates/private/standard_apps_api.html index c8b712c..cebd5f4 100644 --- a/chrome/common/extensions/docs/templates/private/standard_apps_api.html +++ b/chrome/common/extensions/docs/templates/private/standard_apps_api.html @@ -13,23 +13,19 @@ {{?api.channelWarning.trunk +partials.warning_trunk/}} {{?api.channelWarning.dev +partials.warning_dev/}} {{?api.channelWarning.beta +partials.warning_beta/}} - {{?intro}} - {{+partials.table_of_contents toc_items:intro.toc - api:api - samples_list:api.samples.apps - title:strings.apps_title/}} - {{:intro}} - {{+partials.table_of_contents toc_items:false - api:api - samples_list:api.samples.apps - title:strings.apps_title/}} - {{/intro}} + $(table_of_contents) {{+partials.intro_table api:api/}} + {{?intro}} + <section> {{- This is unindented because it contains <pre> tags -}} -{{?intro +intro platform:strings.app is_apps:true/}} +{{+intro platform:strings.app is_apps:true/}} + </section> + {{/intro}} + <section> {{+partials.api_reference samples_list:api.samples.apps title:strings.apps_title api:api/}} + </section> </div> </div> </body> diff --git a/chrome/common/extensions/docs/templates/private/standard_apps_article.html b/chrome/common/extensions/docs/templates/private/standard_apps_article.html index 5b775ee..91ed4d5 100644 --- a/chrome/common/extensions/docs/templates/private/standard_apps_article.html +++ b/chrome/common/extensions/docs/templates/private/standard_apps_article.html @@ -2,16 +2,14 @@ <html> <head> {{+partials.header_head/}} - <title>{{article.title}} - Google Chrome</title> + <title>$(title) - Google Chrome</title> </head> <body> {{+partials.header_body title:strings.apps_title platform:strings.app/}} <div id="gc-container"> {{+partials.sidenav items:sidenavs.apps/}} <div id="gc-pagecontent"> - <h1 class="page_title">{{article.title}}</h1> - {{+partials.table_of_contents toc_items:article.toc - title:strings.apps_title/}} + $(table_of_contents) {{- This may contain <pre> tags so it is not indented -}} {{+article platform:strings.app is_apps:true/}} </div> diff --git a/chrome/common/extensions/docs/templates/private/standard_extensions_api.html b/chrome/common/extensions/docs/templates/private/standard_extensions_api.html index a73b78b..15de398 100644 --- a/chrome/common/extensions/docs/templates/private/standard_extensions_api.html +++ b/chrome/common/extensions/docs/templates/private/standard_extensions_api.html @@ -13,23 +13,19 @@ {{?api.channelWarning.trunk +partials.warning_trunk/}} {{?api.channelWarning.dev +partials.warning_dev/}} {{?api.channelWarning.beta +partials.warning_beta/}} - {{?intro}} - {{+partials.table_of_contents toc_items:intro.toc - api:api - samples_list:api.samples.extensions - title:strings.extensions_title/}} - {{:intro}} - {{+partials.table_of_contents toc_items:false - api:api - samples_list:api.samples.extensions - title:strings.extensions_title/}} - {{/intro}} + $(table_of_contents) {{+partials.intro_table api:api/}} + {{?intro}} + <section> {{- This is unindented because it contains <pre> tags -}} -{{?intro +intro platform:strings.extension is_apps:false/}} +{{+intro platform:strings.extension is_apps:false/}} + </section> + {{/intro}} + <section> {{+partials.api_reference samples_list:api.samples.extensions title:strings.extensions_title api:api/}} + </section> </div> </div> </body> diff --git a/chrome/common/extensions/docs/templates/private/standard_extensions_article.html b/chrome/common/extensions/docs/templates/private/standard_extensions_article.html index 1ef7098..397289e 100644 --- a/chrome/common/extensions/docs/templates/private/standard_extensions_article.html +++ b/chrome/common/extensions/docs/templates/private/standard_extensions_article.html @@ -2,16 +2,14 @@ <html> <head> {{+partials.header_head/}} - <title>{{article.title}} - Google Chrome</title> + <title>$(title) - Google Chrome</title> </head> <body> {{+partials.header_body title:strings.extensions_title platform:strings.extension/}} <div id="gc-container"> {{+partials.sidenav items:sidenavs.extensions/}} <div id="gc-pagecontent"> - <h1 class="page_title">{{article.title}}</h1> - {{+partials.table_of_contents toc_items:article.toc - title:strings.extensions_title/}} + $(table_of_contents) {{- This may contain <pre> tags so it is not indented -}} {{+article platform:strings.extension is_apps:false /}} </div> diff --git a/chrome/common/extensions/docs/templates/private/table_of_contents.html b/chrome/common/extensions/docs/templates/private/table_of_contents.html index 51cae74..9331ef7 100644 --- a/chrome/common/extensions/docs/templates/private/table_of_contents.html +++ b/chrome/common/extensions/docs/templates/private/table_of_contents.html @@ -1,57 +1,16 @@ <div id="toc"> <ol> - {{?toc_items}} - {{#i:toc_items}} - <li> + {{#i:items}} + <li {{?i.separator}}class="separator"{{/}}> <a href="#{{i.link}}">{{{i.title}}}</a> {{?i.subheadings}} <ol> - {{#sh:i.subheadings}}<li><a href="#{{sh.link}}">{{{sh.title}}}</a></li>{{/}} + {{#sh:i.subheadings}} + <li><a href="#{{sh.link}}">{{{sh.title}}}</a></li> + {{/i.subheadings}} </ol> - {{/}} + {{/i.subheadings}} </li> - {{/toc_items}} - {{/toc_items}} - {{?api}} - <div class="api-reference"> - <li> - <a href="#apiReference">Reference</a> - <ol> - {{#a:api}} - {{?a.types}} - <li> - {{+partials.toc_types types:a.types/}} - </li> - {{/a.types}} - {{?a.properties}} - <li> - {{+partials.toc_properties properties:a.properties/}} - </li> - {{/a.properties}} - {{?a.functions}} - <li> - {{+partials.toc_functions functions:a.functions/}} - </li> - {{/a.functions}} - {{?a.events}} - <li> - {{+partials.toc_events events:a.events/}} - </li> - {{/a.events}} - {{?a.domEvents}} - <li> - {{+partials.toc_dom_events domEvents:a.domEvents/}} - </li> - {{/a.domEvents}} - {{?samples_list}} - <li> - {{+partials.toc_samples title:title/}} - </li> - {{/samples_list}} - {{/api}} - </ol> - </li> - </div> - {{/api}} + {{/items}} </ol> </div> diff --git a/chrome/common/extensions/docs/templates/private/title.html b/chrome/common/extensions/docs/templates/private/title.html index 7176bec..c7c734c 100644 --- a/chrome/common/extensions/docs/templates/private/title.html +++ b/chrome/common/extensions/docs/templates/private/title.html @@ -1,2 +1,2 @@ {{!api The API}} -<title>{{+partials.api_title api:api/}} - Google Chrome</title> +<title>$(title) - Google Chrome</title> |