Docserver: Display API features that are available to content scripts

BUG=278919
NOTRY=True

Review URL: https://codereview.chromium.org/375133002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284523 0039d316-1c4b-4281-b951-d872f2087c98
parent b857cda3
......@@ -8,11 +8,12 @@ import os
import posixpath
from data_source import DataSource
from docs_server_utils import StringIdentity
from docs_server_utils import StringIdentity, MarkFirstAndLast
from environment import IsPreviewServer, IsReleaseServer
from extensions_paths import JSON_TEMPLATES, PRIVATE_TEMPLATES
from file_system import FileNotFoundError
from future import Future, Collect
from operator import itemgetter
from platform_util import GetPlatforms
import third_party.json_schema_compiler.json_parse as json_parse
import third_party.json_schema_compiler.model as model
......@@ -278,12 +279,14 @@ class _JSCModel(object):
'''
def __init__(self,
content_script_apis,
namespace,
availability_finder,
json_cache,
template_cache,
features_bundle,
event_byname_future):
self._content_script_apis = content_script_apis
self._availability = availability_finder.GetAPIAvailability(namespace.name)
self._current_node = _APINodeCursor(availability_finder, namespace.name)
self._api_availabilities = json_cache.GetFromFile(
......@@ -556,7 +559,7 @@ class _JSCModel(object):
intro_rows = [
self._GetIntroDescriptionRow(),
self._GetIntroAvailabilityRow()
] + self._GetIntroDependencyRows()
] + self._GetIntroDependencyRows() + self._GetIntroContentScriptRow()
# Add rows using data from intro_tables.json, overriding any existing rows
# if they share the same 'title' attribute.
......@@ -580,6 +583,23 @@ class _JSCModel(object):
'version': version
}
def _GetIntroContentScriptRow(self):
content_script_support = self._content_script_apis.get(self._namespace.name)
if content_script_support is None:
return []
if content_script_support.restrictedTo:
content_script_support.restrictedTo.sort(key=itemgetter('node'))
MarkFirstAndLast(content_script_support.restrictedTo)
return [{
'title': 'Content Scripts',
'content': [{
'partial': self._template_cache.GetFromFile(
posixpath.join(PRIVATE_TEMPLATES,
'intro_tables',
'content_scripts.html')).Get(),
'contentScriptSupport': content_script_support.__dict__
}]
}]
def _GetAvailabilityTemplate(self):
'''Gets availability for the current node and returns an appropriate
template object.
......@@ -756,13 +776,15 @@ class APIDataSource(DataSource):
def _GetSchemaModel(self, platform, api_name):
object_store_key = '/'.join((platform, api_name))
api_models = self._platform_bundle.GetAPIModels(platform)
jsc_model_future = self._model_cache.Get(object_store_key)
model_future = self._platform_bundle.GetAPIModels(platform).GetModel(
api_name)
model_future = api_models.GetModel(api_name)
content_script_apis_future = api_models.GetContentScriptAPIs()
def resolve():
jsc_model = jsc_model_future.Get()
if jsc_model is None:
jsc_model = _JSCModel(
content_script_apis_future.Get(),
model_future.Get(),
self._platform_bundle.GetAvailabilityFinder(platform),
self._json_cache,
......
......@@ -120,7 +120,8 @@ class APIDataSourceTest(unittest.TestCase):
def testCreateId(self):
fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
dict_ = _JSCModel(self._api_models.GetModel('tester').Get(),
dict_ = _JSCModel(self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
......@@ -136,7 +137,8 @@ class APIDataSourceTest(unittest.TestCase):
def DISABLED_testToDict(self):
fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
expected_json = self._LoadJSON('expected_tester.json')
dict_ = _JSCModel(self._api_models.GetModel('tester').Get(),
dict_ = _JSCModel(self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
......@@ -146,7 +148,8 @@ class APIDataSourceTest(unittest.TestCase):
def testAddRules(self):
fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
dict_ = _JSCModel(self._api_models.GetModel('add_rules_tester').Get(),
dict_ = _JSCModel(self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('add_rules_tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
......@@ -170,7 +173,8 @@ class APIDataSourceTest(unittest.TestCase):
def testGetIntroList(self):
fake_avail_finder = _FakeAvailabilityFinder(self._fake_availability)
model = _JSCModel(self._api_models.GetModel('tester').Get(),
model = _JSCModel(self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
......@@ -206,6 +210,18 @@ class APIDataSourceTest(unittest.TestCase):
}
]
},
{ 'title': 'Content Scripts',
'content': [
{
'partial': 'handlebar chrome/common/extensions/docs' +
'/templates/private/intro_tables/content_scripts.html',
'contentScriptSupport': {
'name': 'tester',
'restrictedTo': None
}
}
]
},
{ 'title': 'Learn More',
'content': [
{ 'link': 'https://tester.test.com/welcome.html',
......@@ -219,12 +235,13 @@ class APIDataSourceTest(unittest.TestCase):
# Tests the same data with a scheduled availability.
fake_avail_finder = _FakeAvailabilityFinder(
AvailabilityInfo(ChannelInfo('beta', '1453', 27), scheduled=28))
model = _JSCModel(self._api_models.GetModel('tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
self._features_bundle,
None)
model = _JSCModel(self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('tester').Get(),
fake_avail_finder,
self._json_cache,
_FakeTemplateCache(),
self._features_bundle,
None)
expected_list[1] = {
'title': 'Availability',
'content': [
......@@ -262,6 +279,7 @@ class APIDataSourceWithoutNodeAvailabilityTest(unittest.TestCase):
}
for api_name, availability in api_availabilities.iteritems():
model_dict = _JSCModel(
self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel(api_name).Get(),
self._avail_finder,
self._json_cache,
......@@ -335,6 +353,7 @@ class APIDataSourceWithNodeAvailabilityTest(unittest.TestCase):
self.assertEquals(node_availabilities[node], actual)
model_dict = _JSCModel(
self._api_models.GetContentScriptAPIs().Get(),
self._api_models.GetModel('tabs').Get(),
self._avail_finder,
self._json_cache,
......
......@@ -7,7 +7,7 @@ from future import Future
from operator import itemgetter
from platform_util import GetPlatforms
from docs_server_utils import MarkLast
from docs_server_utils import MarkFirstAndLast, MarkLast
class APIListDataSource(DataSource):
""" This class creates a list of chrome.* APIs and chrome.experimental.* APIs
......@@ -31,6 +31,24 @@ class APIListDataSource(DataSource):
APIListDataSource, category=self._platform_bundle.GetIdentity())
def _GenerateAPIDict(self):
def make_list_for_content_scripts():
content_script_apis = self._platform_bundle.GetAPIModels(
'extensions').GetContentScriptAPIs().Get()
content_script_apis_list = [csa.__dict__ for api_name, csa
in content_script_apis.iteritems()
if self._platform_bundle.GetAPICategorizer(
'extensions').IsDocumented(api_name)]
content_script_apis_list.sort(key=itemgetter('name'))
for csa in content_script_apis_list:
restricted_nodes = csa['restrictedTo']
if restricted_nodes:
restricted_nodes.sort(key=itemgetter('node'))
MarkFirstAndLast(restricted_nodes)
else:
del csa['restrictedTo']
return content_script_apis_list
def make_dict_for_platform(platform):
platform_dict = {
'chrome': {'stable': [], 'beta': [], 'dev': [], 'trunk': []},
......@@ -77,8 +95,10 @@ class APIListDataSource(DataSource):
platform_dict[key] = apis
return platform_dict
return dict((platform, make_dict_for_platform(platform))
for platform in GetPlatforms())
api_dict = dict((platform, make_dict_for_platform(platform))
for platform in GetPlatforms())
api_dict['contentScripts'] = make_list_for_content_scripts()
return api_dict
def _GetCachedAPIData(self):
data_future = self._object_store.Get('api_data')
......
......@@ -7,6 +7,7 @@ import unittest
import json
from api_list_data_source import APIListDataSource
from api_models import ContentScriptAPI
from extensions_paths import CHROME_EXTENSIONS
from server_instance import ServerInstance
from test_file_system import TestFileSystem
......@@ -28,7 +29,8 @@ def _ToTestFeatures(names):
features = dict((name, {
'name': name,
'extension_types': platforms_to_extension_types(platforms),
}) for name, platforms in names)
'contexts': context
}) for name, platforms, context in names)
features['sockets.udp']['channel'] = 'dev'
return features
......@@ -46,17 +48,21 @@ def _ToTestAPISchema(names, apis):
_TEST_API_FEATURES = _ToTestFeatures([
('alarms', ['apps', 'extensions']),
('app.window', ['apps']),
('browserAction', ['extensions']),
('experimental.bluetooth', ['apps']),
('experimental.history', ['extensions'],),
('experimental.power', ['apps', 'extensions']),
('infobars', ['extensions']),
('something_internal', ['apps']),
('something_else_internal', ['extensions']),
('storage', ['apps', 'extensions']),
('sockets.udp', ['apps', 'extensions'])
('alarms', ['apps', 'extensions'], ['content_script']),
('app.window', ['apps'], []),
('browserAction', ['extensions'], []),
('experimental.bluetooth', ['apps'], []),
('experimental.history', ['extensions'], []),
('experimental.power', ['apps', 'extensions'], []),
('extension', ['extensions'], ['content_script']),
('extension.onRequest', ['extensions'], ['content_script']),
('extension.sendNativeMessage', ['extensions'], []),
('extension.sendRequest', ['extensions'], ['content_script']),
('infobars', ['extensions'], []),
('something_internal', ['apps'], []),
('something_else_internal', ['extensions'], []),
('storage', ['apps', 'extensions'], []),
('sockets.udp', ['apps', 'extensions'], [])
])
......@@ -67,6 +73,7 @@ _TEST_API_DATA = _ToTestAPIData([
('experimental.bluetooth', u'<code>experimental.bluetooth</code>'),
('experimental.history', u'<code>experimental.history</code>'),
('experimental.power', u'<code>experimental.power</code>'),
('extension', u'<code>extension</code>'),
('infobars', u'<code>infobars</code>'),
('something_internal', u'<code>something_internal</code>'),
('something_else_internal', u'<code>something_else_internal</code>'),
......@@ -82,6 +89,7 @@ _TEST_API_SCHEMA = [
('experimental.bluetooth', 'experimental_bluetooth.json'),
('experimental.history', 'experimental_history.json'),
('experimental.power', 'experimental_power.json'),
('extension', 'extension.json'),
('infobars', 'infobars.json'),
('something_internal', 'something_internal.json'),
('something_else_internal', 'something_else_internal.json'),
......@@ -117,6 +125,7 @@ _TEST_DATA = _ToTestAPISchema(_TEST_API_SCHEMA, {
'browserAction.html',
'experimental_history.html',
'experimental_power.html',
'extension.html',
'infobars.html',
'storage.html',
'sockets_udp.html'
......@@ -194,6 +203,11 @@ class APIListDataSourceTest(unittest.TestCase):
'version': 21,
'description': u'<code>browserAction</code>'
},
{
'name': 'extension',
'version': 5,
'description': u'<code>extension</code>'
},
{
'name': 'infobars',
'version': 5,
......@@ -227,5 +241,21 @@ class APIListDataSourceTest(unittest.TestCase):
'last': True
}], self._api_list.get('extensions').get('experimental'))
def testContentScripts(self):
self.assertEqual([{
'name': 'alarms',
},
{
'name': 'extension',
'restrictedTo': [{
'node': 'onRequest',
'first': True
},
{
'node': 'sendRequest',
'last': True
}]
}], self._api_list.get('contentScripts'))
if __name__ == '__main__':
unittest.main()
......@@ -5,17 +5,42 @@
import posixpath
from compiled_file_system import SingleFile, Unicode
from docs_server_utils import StringIdentity
from extensions_paths import API_PATHS
from features_bundle import HasParentFeature
from features_bundle import HasParent, GetParentName
from file_system import FileNotFoundError
from future import Collect, Future
from operator import itemgetter
from platform_util import PlatformToExtensionType
from schema_util import ProcessSchema
from third_party.json_schema_compiler.json_schema import DeleteNodes
from third_party.json_schema_compiler.model import Namespace, UnixName
class ContentScriptAPI(object):
'''Represents an API available to content scripts.
|name| is the name of the API or API node this object represents.
|restrictedTo| is a list of dictionaries representing the nodes
of this API that are available to content scripts, or None if the
entire API is available to content scripts.
'''
def __init__(self, name):
self.name = name
self.restrictedTo = None
def __eq__(self, o):
return self.name == o.name and self.restrictedTo == o.restrictedTo
def __ne__(self, o):
return not (self == o)
def __repr__(self):
return '<ContentScriptAPI name=%s, restrictedTo=%s>' % (name, restrictedTo)
def __str__(self):
return repr(self)
class APIModels(object):
'''Tracks APIs and their Models.
'''
......@@ -24,11 +49,13 @@ class APIModels(object):
features_bundle,
compiled_fs_factory,
file_system,
object_store_creator,
platform):
self._features_bundle = features_bundle
self._platform = PlatformToExtensionType(platform)
self._model_cache = compiled_fs_factory.Create(
file_system, self._CreateAPIModel, APIModels, category=self._platform)
self._object_store = object_store_creator.Create(APIModels)
@SingleFile
@Unicode
......@@ -51,7 +78,7 @@ class APIModels(object):
# APIs; runtime.onConnectNative is not).
api_features = self._features_bundle.GetAPIFeatures().Get()
return [name for name, feature in api_features.iteritems()
if not HasParentFeature(name, feature, api_features)]
if not HasParent(name, feature, api_features)]
def GetModel(self, api_name):
# By default |api_name| is assumed to be given without a path or extension,
......@@ -97,6 +124,45 @@ class APIModels(object):
futures[0].Get()
return Future(callback=resolve)
def GetContentScriptAPIs(self):
'''Creates a dict of APIs and nodes supported by content scripts in
this format:
{
'extension': '<ContentScriptAPI name='extension',
restrictedTo=[{'node': 'onRequest'}]>',
...
}
'''
content_script_apis_future = self._object_store.Get('content_script_apis')
api_features_future = self._features_bundle.GetAPIFeatures()
def resolve():
content_script_apis = content_script_apis_future.Get()
if content_script_apis is not None:
return content_script_apis
api_features = api_features_future.Get()
content_script_apis = {}
for name, feature in api_features.iteritems():
if 'content_script' not in feature.get('contexts', ()):
continue
parent = GetParentName(name, feature, api_features)
if parent is None:
content_script_apis[name] = ContentScriptAPI(name)
else:
# Creates a dict for the individual node.
node = {'node': name[len(parent) + 1:]}
if parent not in content_script_apis:
content_script_apis[parent] = ContentScriptAPI(parent)
if content_script_apis[parent].restrictedTo:
content_script_apis[parent].restrictedTo.append(node)
else:
content_script_apis[parent].restrictedTo = [node]
self._object_store.Set('content_script_apis', content_script_apis)
return content_script_apis
return Future(callback=resolve)
def Cron(self):
futures = [self.GetModel(name) for name in self.GetNames()]
return Collect(futures, except_pass=(FileNotFoundError, ValueError))
......
......@@ -66,6 +66,7 @@ class APIModelsTest(unittest.TestCase):
self._api_models = APIModels(features_bundle,
compiled_fs_factory,
self._mock_file_system,
object_store_creator,
'extensions')
def testGetNames(self):
......
application: chrome-apps-doc
version: 3-32-0
version: 3-33-0
runtime: python27
api_version: 1
threadsafe: false
......
......@@ -2,4 +2,4 @@ cron:
- description: Repopulates all cached data.
url: /_cron
schedule: every 5 minutes
target: 3-32-0
target: 3-33-0
......@@ -33,12 +33,24 @@ def StringIdentity(first, *more):
identity = encode(identity + m)
return identity[:8]
def MarkFirst(dicts):
'''Adds a property 'first' == True to the first element in a list of dicts.
'''
if len(dicts) > 0:
dicts[0]['first'] = True
def MarkLast(dicts):
'''Adds a property 'last' == True to the last element in a list of dicts.
'''
if len(dicts) > 0:
dicts[-1]['last'] = True
def MarkFirstAndLast(dicts):
'''Marks the first and last element in a list of dicts.
'''
MarkFirst(dicts)
MarkLast(dicts)
def ToUnicode(data):
'''Returns the str |data| as a unicode object. It's expected to be utf8, but
there are also latin-1 encodings in there for some reason. Fall back to that.
......
......@@ -20,7 +20,7 @@ _MANIFEST_FEATURES = '_manifest_features.json'
_PERMISSION_FEATURES = '_permission_features.json'
def HasParentFeature(feature_name, feature, all_feature_names):
def HasParent(feature_name, feature, all_feature_names):
# A feature has a parent if it has a . in its name, its parent exists,
# and it does not explicitly specify that it has no parent.
return ('.' in feature_name and
......@@ -28,11 +28,11 @@ def HasParentFeature(feature_name, feature, all_feature_names):
not feature.get('noparent'))
def GetParentFeature(feature_name, feature, all_feature_names):
def GetParentName(feature_name, feature, all_feature_names):
'''Returns the name of the parent feature, or None if it does not have a
parent.
'''
if not HasParentFeature(feature_name, feature, all_feature_names):
if not HasParent(feature_name, feature, all_feature_names):
return None
return feature_name.rsplit('.', 1)[0]
......@@ -122,7 +122,7 @@ def _ResolveFeature(feature_name,
channel = value.get('channel')
dependencies = value.get('dependencies', [])
parent = GetParentFeature(
parent = GetParentName(
feature_name, value, features_map[features_type]['all_names'])
if parent is not None:
# The parent data needs to be resolved so the child can inherit it.
......
......@@ -53,6 +53,7 @@ class PlatformBundle(object):
self.GetFeaturesBundle(platform),
self._compiled_fs_factory,
self._host_fs_at_trunk,
self._object_store_creator,
platform)
return self._platform_data[platform].api_models
......
......@@ -10,7 +10,12 @@ CANNED_TRUNK_FS_DATA = {
'_api_features.json': json.dumps({
'add_rules_tester': { 'dependencies': ['permission:add_rules_tester'] },
'ref_test': { 'dependencies': ['permission:ref_test'] },
'tester': { 'dependencies': ['permission:tester', 'manifest:tester'] }
'tester': {
'dependencies': ['permission:tester', 'manifest:tester'],
'contexts': ['content_script']
},
'tester.test1': {'contexts': ['content_script']},
'tester.test2': {}
}),
'_manifest_features.json': json.dumps({'tester': {}, 'ref_test': {}}),
'_permission_features.json': json.dumps({
......
......@@ -51,4 +51,10 @@
.availability {
color: #A03;
}
table#intro {
.title {
white-space: nowrap;
}
}
}
......@@ -27,9 +27,20 @@ They <b>cannot</b>:
<ul>
<li>
Use chrome.* APIs
(except for parts of
<a href="extension"><code>chrome.extension</code></a>)
Use chrome.* APIs, with the exception of:
<ul id="content_script_supported_nodes">
{{#api:content_scripts}}
<li>
$(ref:{{api.name}})
{{?api.restrictedTo}}
({{#n:api.restrictedTo}}
$(ref:{{api.name}}.{{n.node}} {{n.node}})
{{^n.last}},{{/n.last}}
{{/api.restrictedTo}})
{{/api.restrictedTo}}
</li>
{{/content_scripts}}
</ul>
</li>
<li>
Use variables or functions defined by their extension's pages
......
<h2 id="content scripts">Support for content scripts</h2>
<p>
Unlike the other chrome.* APIs,
parts of <code>chrome.extension</code>
can be used by content scripts:
</p>
<dl>
<dt>
$(ref:runtime.sendMessage) and
$(ref:runtime.onMessage)
</dt>
<dd>
Simple communication with extension pages
</dd>
<dt>
$(ref:runtime.connect) and
$(ref:runtime.onConnect)
</dt>
<dd>
Extended communication with extension pages
</dd>
<dt>
$(ref:extension.getURL)
</dt>
<dd>
Access to extension resources such as image files
</dd>
</dl>
<p>
For details, see
<a href="content_scripts">Content Scripts</a>.
</p>
{{?content.contentScriptSupport.restrictedTo}}
{{#n:content.contentScriptSupport.restrictedTo}}
{{^n.first}}
{{^n.last}},{{/n.last}}{{?n.last}} and {{/n.last}}
{{/n.first}}
$(ref:{{content.contentScriptSupport.name}}.{{n.node}} {{n.node}})
{{/content.contentScriptSupport.restrictedTo}} are supported.
{{:}}
Fully supported.
{{/content.contentScriptSupport.restrictedTo}}
<a href="content_scripts">Learn more</a>
{{+partials.standard_extensions_article article:articles.content_scripts/}}
{{+partials.standard_extensions_article
article:(articles.content_scripts
content_scripts:api_list.contentScripts)/}}
{{+partials.standard_extensions_api api:apis.extensions.extension intro:intros.extension/}}
{{+partials.standard_extensions_api api:apis.extensions.extension/}}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment