Commit c1c2a503 authored by dzhioev's avatar dzhioev Committed by Commit bot

WUG (Web UI generator) is an utility toolkit which

facilitates WebUI creation.

All parts of WUG are implemented:
* Base classes for view (View) and view-model (ViewModel)
* WebUIView -- specialization of View for use in WebUI.
* View's CC, HTML and JS code generator
* Templates for GYP and GN which help to generate code and
  produce components from generated code.

Tests to follow.

TEST=none
TBR=avi@chromium.org,mark@chromium.org
BUG=459230

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

Cr-Commit-Position: refs/heads/master@{#318884}
parent cbca72f3
...@@ -110,8 +110,9 @@ group("all_components") { ...@@ -110,8 +110,9 @@ group("all_components") {
"//components/web_cache/common", "//components/web_cache/common",
"//components/web_cache/renderer", "//components/web_cache/renderer",
"//components/web_modal", "//components/web_modal",
"//components/webdata/common",
"//components/web_resource", "//components/web_resource",
"//components/webdata/common",
"//components/webui_generator",
"//components/wifi", "//components/wifi",
"//components/wifi_sync", "//components/wifi_sync",
] ]
...@@ -133,6 +134,7 @@ group("all_components") { ...@@ -133,6 +134,7 @@ group("all_components") {
deps -= [ deps -= [
"//components/history/content/browser", "//components/history/content/browser",
"//components/keyed_service/content", "//components/keyed_service/content",
"//components/webui_generator",
] ]
} }
......
...@@ -280,6 +280,9 @@ per-file web_resource.gypi=dbeam@chromium.org ...@@ -280,6 +280,9 @@ per-file web_resource.gypi=dbeam@chromium.org
per-file web_resource.gypi=rsesek@chromium.org per-file web_resource.gypi=rsesek@chromium.org
per-file web_resource.gypi=stevet@chromium.org per-file web_resource.gypi=stevet@chromium.org
per-file webui_generator.gypi=dzhioev@chromium.org
per-file webui_generator_strings.grdp=dzhioev@chromium.org
per-file wifi.gypi=mef@chromium.org per-file wifi.gypi=mef@chromium.org
per-file *.isolate=csharp@chromium.org per-file *.isolate=csharp@chromium.org
......
...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
'user_prefs.gypi', 'user_prefs.gypi',
'variations.gypi', 'variations.gypi',
'wallpaper.gypi', 'wallpaper.gypi',
'webdata.gypi',
'web_resource.gypi', 'web_resource.gypi',
'webdata.gypi',
], ],
'conditions': [ 'conditions': [
['OS != "ios"', { ['OS != "ios"', {
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
'web_cache.gypi', 'web_cache.gypi',
'web_contents_delegate_android.gypi', 'web_contents_delegate_android.gypi',
'web_modal.gypi', 'web_modal.gypi',
'webui_generator.gypi',
], ],
}], }],
['OS == "ios"', { ['OS == "ios"', {
......
...@@ -179,6 +179,7 @@ ...@@ -179,6 +179,7 @@
<part file="pdf_strings.grdp" /> <part file="pdf_strings.grdp" />
<part file="policy_strings.grdp" /> <part file="policy_strings.grdp" />
<part file="translate_strings.grdp" /> <part file="translate_strings.grdp" />
<part file="webui_generator_strings.grdp" />
<!-- Generic terms --> <!-- Generic terms -->
<message name="IDS_LEARN_MORE" desc="Learn more text"> <message name="IDS_LEARN_MORE" desc="Learn more text">
......
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
'<(DEPTH)/base/base.gyp:base', '<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/ui/base/ui_base.gyp:ui_base', '<(DEPTH)/ui/base/ui_base.gyp:ui_base',
], ],
'export_dependent_settings': [
'<(DEPTH)/base/base.gyp:base',
],
'defines': [ 'defines': [
'LOGIN_IMPLEMENTATION', 'LOGIN_IMPLEMENTATION',
], ],
......
...@@ -15,9 +15,12 @@ component("login") { ...@@ -15,9 +15,12 @@ component("login") {
defines = [ "LOGIN_IMPLEMENTATION" ] defines = [ "LOGIN_IMPLEMENTATION" ]
deps = [ deps = [
"//base",
"//ui/base", "//ui/base",
] ]
public_deps = [
"//base",
]
} }
source_set("unit_tests") { source_set("unit_tests") {
......
per-file data_reduction_proxy*=bengr@chromium.org
per-file data_reduction_proxy*=sclittle@chromium.org
per-file dom_distiller*=cjhopman@chromium.org per-file dom_distiller*=cjhopman@chromium.org
per-file dom_distiller*=nyquist@chromium.org per-file dom_distiller*=nyquist@chromium.org
per-file data_reduction_proxy*=bengr@chromium.org per-file webui_generator_resources.grdp=dzhioev@chromium.org
per-file data_reduction_proxy*=sclittle@chromium.org
\ No newline at end of file
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<part file="dom_distiller_resources.grdp" /> <part file="dom_distiller_resources.grdp" />
<part file="printing_resources.grdp" /> <part file="printing_resources.grdp" />
<part file="translate_resources.grdp" /> <part file="translate_resources.grdp" />
<part file="webui_generator_resources.grdp" />
</includes> </includes>
</release> </release>
</grit> </grit>
<?xml version="1.0" encoding="utf-8"?>
<grit-part>
<include name="IDR_WUG_CONTEXT_HTML" file="../webui_generator/resources/context.html" type="chrome_html" />
<include name="IDR_WUG_CONTEXT_JS" file="../webui_generator/resources/context.js" type="chrome_html" flattenhtml="true" />
<include name="IDR_WUG_WEBUI_VIEW_HTML" file="../webui_generator/resources/webui-view.html" type="chrome_html" />
<include name="IDR_WUG_WEBUI_VIEW_JS" file="../webui_generator/resources/webui-view.js" type="chrome_html" />
</grit-part>
# Copyright 2015 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.
{
'targets': [{
# GN version: //components/webui_generator
'target_name': 'webui_generator',
'type': '<(component)',
'dependencies': [
'login',
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/components/components_resources.gyp:components_resources',
'<(DEPTH)/content/content.gyp:content_browser',
'<(DEPTH)/ui/base/ui_base.gyp:ui_base',
],
'export_dependent_settings': [
'login',
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/content/content.gyp:content_browser',
],
'defines': [
'WUG_IMPLEMENTATION',
],
'sources': [
'webui_generator/data_source_util.cc',
'webui_generator/data_source_util.h',
'webui_generator/export.h',
'webui_generator/view.cc',
'webui_generator/view.h',
'webui_generator/view_model.cc',
'webui_generator/view_model.h',
'webui_generator/web_ui_view.cc',
'webui_generator/web_ui_view.h',
],
}],
}
# Copyright 2015 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.
# GYP version: components/webui_generator.gypi:webui_generator
component("webui_generator") {
sources = [
"data_source_util.cc",
"data_source_util.h",
"export.h",
"view.cc",
"view.h",
"view_model.cc",
"view_model.h",
"web_ui_view.cc",
"web_ui_view.h",
]
defines = [ "WUG_IMPLEMENTATION" ]
deps = [
"//components/resources",
"//ui/base",
]
public_deps = [
"//base",
"//components/login",
"//content/public/browser",
]
}
# Config for users of generated files.
config("wug_generated_config") {
include_dirs = [ "$root_gen_dir/wug" ]
}
include_rules = [
"+base",
"+content/public/browser",
"+grit/components_resources.h",
"+components/login",
]
// Copyright 2015 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.
#include "components/webui_generator/data_source_util.h"
#include "content/public/browser/web_ui_data_source.h"
#include "grit/components_resources.h"
namespace webui_generator {
void SetUpDataSource(content::WebUIDataSource* data_source) {
data_source->AddResourcePath("webui_generator/webui-view.html",
IDR_WUG_WEBUI_VIEW_HTML);
data_source->AddResourcePath("webui_generator/webui-view.js",
IDR_WUG_WEBUI_VIEW_JS);
data_source->AddResourcePath("webui_generator/context.js",
IDR_WUG_CONTEXT_JS);
data_source->AddResourcePath("webui_generator/context.html",
IDR_WUG_CONTEXT_HTML);
}
} // namespace webui_generator
// Copyright 2015 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.
#ifndef CHROME_BROWSER_UI_WEBUI_WUG_DATA_SOURCE_UTIL_H_
#define CHROME_BROWSER_UI_WEBUI_WUG_DATA_SOURCE_UTIL_H_
#include "components/webui_generator/export.h"
namespace content {
class WebUIDataSource;
}
namespace webui_generator {
// Adds common resources needed for WUG to |data_source|.
void WUG_EXPORT SetUpDataSource(content::WebUIDataSource* data_source);
} // namespace webui_generator
#endif // CHROME_BROWSER_UI_WEBUI_WUG_DATA_SOURCE_UTIL_H_
// Copyright 2015 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.
#ifndef COMPONENTS_WUG_EXPORT_H_
#define COMPONENTS_WUG_EXPORT_H_
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(WUG_IMPLEMENTATION)
#define WUG_EXPORT __declspec(dllexport)
#else
#define WUG_EXPORT __declspec(dllimport)
#endif // defined(WUG_IMPLEMENTATION)
#else // defined(WIN32)
#if defined(WUG_IMPLEMENTATION)
#define WUG_EXPORT __attribute__((visibility("default")))
#else
#define WUG_EXPORT
#endif // defined(WUG_IMPLEMENTATION)
#endif // defined(WIN32)
#else // defined(COMPONENT_BUILD)
#define WUG_EXPORT
#endif
#endif // COMPONENTS_WUG_EXPORT_H_
# Copyright 2015 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.
# Provides various information needed for GYP and GN to generate and build
# files.
import os
import sys
from declaration import Declaration
import export_h
import util
import view_model
import web_ui_view
def GetImportDependencies(declaration):
return set(child.build_target for child in declaration.children.itervalues())
parser = util.CreateArgumentParser()
parser.add_argument('--output',
choices=['view_cc', 'view_h', 'model_cc', 'model_h',
'export_h', 'dirname', 'target_name', 'imports',
'impl_macro', 'import_dependencies',
'list_outputs'],
required=True,
help='Type of output')
parser.add_argument('--gn', action='store_true',
help='Is called by GN')
args = parser.parse_args()
declaration_path = os.path.relpath(args.declaration, args.root)
os.chdir(args.root)
try:
declaration = Declaration(declaration_path)
except Exception as e:
print >> sys.stderr, e.message
sys.exit(1)
if args.output == 'view_cc':
print declaration.webui_view_cc_name
elif args.output == 'view_h':
print declaration.webui_view_h_name
elif args.output == 'model_cc':
print declaration.view_model_cc_name
elif args.output == 'model_h':
print declaration.view_model_h_name
elif args.output == 'export_h':
print declaration.export_h_name
elif args.output == 'dirname':
print os.path.dirname(declaration_path)
elif args.output == 'target_name':
print declaration.build_target
elif args.output == 'imports':
for i in declaration.imports:
print '//' + i
elif args.output == 'import_dependencies':
for d in GetImportDependencies(declaration):
print (':' if args.gn else '') + d
elif args.output == 'list_outputs':
outputs = web_ui_view.ListOutputs(declaration, args.destination) + \
view_model.ListOutputs(declaration, args.destination) + \
export_h.ListOutputs(declaration, args.destination)
for output in outputs:
print output
elif args.output == 'impl_macro':
print declaration.component_impl_macro
else:
assert False
# Copyright 2015 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 itertools
import json
import os.path
import re
import util
# Schema is described as follows:
# * for simple type:
# %simple_type%
# * for list:
# (list, %items_schema%)
# * for dict:
# (dict, { '%key_name%': (%is_key_required%, %value_schema%),
# ...
# })
DECLARATION_SCHEMA = (dict, {
'imports': (False, (list, unicode)),
'type': (True, unicode),
'children': (False, (list, (dict, {
'id': (True, unicode),
'type': (True, unicode),
'comment': (False, unicode)
}))),
'context': (False, (list, (dict, {
'name': (True, unicode),
'type': (False, unicode),
'default': (False, object),
'comment': (False, unicode)
}))),
'events': (False, (list, (dict, {
'name': (True, unicode),
'comment': (False, unicode)
}))),
'strings': (False, (list, (dict, {
'name': (True, unicode),
'comment': (False, unicode)
}))),
'comment': (False, unicode)
})
# Returns (True,) if |obj| matches |schema|.
# Otherwise returns (False, msg), where |msg| is a diagnostic message.
def MatchSchema(obj, schema):
expected_type = schema[0] if isinstance(schema, tuple) else schema
if not isinstance(obj, expected_type):
return (False, 'Wrong type, expected %s, got %s.' %
(expected_type, type(obj)))
if expected_type == dict:
obj_keys = set(obj.iterkeys())
allowed_keys = set(schema[1].iterkeys())
required_keys = set(kv[0] for kv in schema[1].iteritems() if kv[1][0])
if not obj_keys.issuperset(required_keys):
missing_keys = required_keys.difference(obj_keys)
return (False, 'Missing required key%s %s.' %
('s' if len(missing_keys) > 1 else '',
', '.join('\'%s\'' % k for k in missing_keys)))
if not obj_keys.issubset(allowed_keys):
unknown_keys = obj_keys.difference(allowed_keys)
return (False, 'Unknown key%s %s.' %
('s' if len(unknown_keys) > 1 else '',
', '.join('\'%s\'' % k for k in unknown_keys)))
for key in obj:
match = MatchSchema(obj[key], schema[1][key][1])
if not match[0]:
return (False, ('[\'%s\'] ' % key) + match[1])
elif expected_type == list:
for i, item in enumerate(obj):
match = MatchSchema(item, schema[1])
if not match[0]:
return (False, ('[%d] ' % i) + match[1])
return (True,)
def CheckDeclarationIsValid(declaration):
match = MatchSchema(declaration, DECLARATION_SCHEMA)
if not match[0]:
raise Exception('Declaration is not valid: ' + match[1])
def CheckFieldIsUnique(list_of_dicts, field):
seen = {}
for i, d in enumerate(list_of_dicts):
value = d[field]
if value in seen:
raise Exception(
'[%d] Object with "%s" equal to "%s" already exists. See [%d].' %
(i, field, value, seen[value]))
else:
seen[value] = i
class FieldDeclaration(object):
ALLOWED_TYPES = [
'boolean',
'integer',
'double',
'string',
'string_list'
]
DEFAULTS = {
'boolean': False,
'integer': 0,
'double': 0.0,
'string': u'',
'string_list': []
}
def __init__(self, declaration):
self.name = declaration['name']
self.id = util.ToLowerCamelCase(self.name)
self.type = declaration['type'] if 'type' in declaration else 'string'
if self.type not in self.ALLOWED_TYPES:
raise Exception('Unknown type of context field "%s": "%s"' %
(self.name, self.type))
self.default_value = declaration['default'] if 'default' in declaration \
else self.DEFAULTS[self.type]
if type(self.default_value) != type(self.DEFAULTS[self.type]):
raise Exception('Wrong type of default for field "%s": '
'expected "%s", got "%s"' %
(self.name,
type(self.DEFAULTS[self.type]),
type(self.default_value)))
class Declaration(object):
class InvalidDeclaration(Exception):
def __init__(self, path, message):
super(Exception, self).__init__(
'Invalid declaration file "%s": %s' % (path, message))
class DeclarationsStorage(object):
def __init__(self):
self.by_path = {}
self.by_type = {}
def Add(self, declaration):
assert declaration.path not in self.by_path
self.by_path[declaration.path] = declaration
if declaration.type in self.by_type:
raise Exception(
'Redefinition of type "%s". ' \
'Previous definition was in "%s".' % \
(declaration.type, self.by_type[declaration.type].path))
self.by_type[declaration.type] = declaration
def HasPath(self, path):
return path in self.by_path
def HasType(self, type):
return type in self.by_type
def GetByPath(self, path):
return self.by_path[path]
def GetByType(self, type):
return self.by_type[type]
def GetKnownPathes(self):
return self.by_path.keys()
def __init__(self, path, known_declarations=None):
if known_declarations is None:
known_declarations = Declaration.DeclarationsStorage()
self.path = path
try:
self.data = json.load(open(path, 'r'))
CheckDeclarationIsValid(self.data)
filename_wo_ext = os.path.splitext(os.path.basename(self.path))[0]
if self.data['type'] != filename_wo_ext:
raise Exception(
'File with declaration of type "%s" should be named "%s.json"' %
(self.data['type'], filename_wo_ext))
known_declarations.Add(self)
if 'imports' in self.data:
for import_path in self.data['imports']:
#TODO(dzhioev): detect circular dependencies.
if not known_declarations.HasPath(import_path):
Declaration(import_path, known_declarations)
for key in ['children', 'strings', 'context', 'events']:
if key in self.data:
unique_field = 'id' if key == 'children' else 'name'
try:
CheckFieldIsUnique(self.data[key], unique_field)
except Exception as e:
raise Exception('["%s"] %s' % (key, e.message))
else:
self.data[key] = []
self.children = {}
for child_data in self.data['children']:
child_type = child_data['type']
child_id = child_data['id']
if not known_declarations.HasType(child_type):
raise Exception('Unknown type "%s" for child "%s"' %
(child_type, child_id))
self.children[child_id] = known_declarations.GetByType(child_type)
self.fields = [FieldDeclaration(d) for d in self.data['context']]
fields_names = [field.name for field in self.fields]
if len(fields_names) > len(set(fields_names)):
raise Exception('Duplicate fields in declaration.')
self.known_declarations = known_declarations
except Declaration.InvalidDeclaration:
raise
except Exception as e:
raise Declaration.InvalidDeclaration(self.path, e.message)
@property
def type(self):
return self.data['type']
@property
def strings(self):
return (string['name'] for string in self.data['strings'])
@property
def events(self):
return (event['name'] for event in self.data['events'])
@property
def webui_view_basename(self):
return self.type + '_web_ui_view'
@property
def webui_view_h_name(self):
return self.webui_view_basename + '.h'
@property
def webui_view_cc_name(self):
return self.webui_view_basename + '.cc'
@property
def webui_view_include_path(self):
return os.path.join(os.path.dirname(self.path), self.webui_view_h_name)
@property
def export_h_name(self):
return self.type + '_export.h'
@property
def component_export_macro(self):
return "WUG_" + self.type.upper() + "_EXPORT"
@property
def component_impl_macro(self):
return "WUG_" + self.type.upper() + "_IMPLEMENTATION"
@property
def export_h_include_path(self):
return os.path.join(os.path.dirname(self.path), self.export_h_name)
@property
def view_model_basename(self):
return self.type + '_view_model'
@property
def view_model_h_name(self):
return self.view_model_basename + '.h'
@property
def view_model_cc_name(self):
return self.view_model_basename + '.cc'
@property
def view_model_include_path(self):
return os.path.join(os.path.dirname(self.path), self.view_model_h_name)
@property
def html_view_basename(self):
return '%s-view' % self.type.replace('_', '-')
@property
def html_view_html_name(self):
return self.html_view_basename + '.html'
@property
def html_view_js_name(self):
return self.html_view_basename + '.js'
@property
def html_view_html_include_path(self):
return os.path.join(os.path.dirname(self.path), self.html_view_html_name)
@property
def html_view_js_include_path(self):
return os.path.join(os.path.dirname(self.path), self.html_view_js_name)
@property
def build_target(self):
return self.type + "_wug_generated"
@property
def webui_view_class(self):
return util.ToUpperCamelCase(self.type) + 'WebUIView'
@property
def view_model_class(self):
return util.ToUpperCamelCase(self.type) + 'ViewModel'
@property
def imports(self):
res = set(self.known_declarations.GetKnownPathes())
res.remove(self.path)
return res
# Copyright 2015 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 datetime
import util
import os
H_FILE_TEMPLATE = \
"""// Copyright %(year)d 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.
// NOTE: this file is generated from "%(source)s". Do not modify directly.
#ifndef %(include_guard)s
#define %(include_guard)s
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(%(impl_macro)s)
#define %(export_macro)s __declspec(dllexport)
#else
#define %(export_macro)s __declspec(dllimport)
#endif // defined(%(impl_macro)s)
#else // defined(WIN32)
#if defined(%(impl_macro)s)
#define %(export_macro)s __attribute__((visibility("default")))
#else
#define %(export_macro)s
#endif // defined(%(impl_macro)s)
#endif // defined(WIN32)
#else // defined(COMPONENT_BUILD)
#define %(export_macro)s
#endif
#endif // %(include_guard)s
"""
def GenHFile(declaration):
subs = {}
subs['year'] = datetime.date.today().year
subs['source'] = declaration.path
subs['include_guard'] = util.PathToIncludeGuard(
declaration.export_h_include_path)
subs['export_macro'] = declaration.component_export_macro
subs['impl_macro'] = declaration.component_impl_macro
return H_FILE_TEMPLATE % subs
def ListOutputs(declaration, destination):
dirname = os.path.join(destination, os.path.dirname(declaration.path))
h_file_path = os.path.join(dirname, declaration.export_h_name)
return [h_file_path]
def Gen(declaration, destination):
h_file_path = ListOutputs(declaration, destination)[0]
util.CreateDirIfNotExists(os.path.dirname(h_file_path))
open(h_file_path, 'w').write(GenHFile(declaration))
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import os.path
import sys
from declaration import Declaration
import export_h
import util
import view_model
import web_ui_view
args = util.CreateArgumentParser().parse_args()
declaration_path = os.path.relpath(args.declaration, args.root)
destination = os.path.relpath(args.destination, args.root)
os.chdir(args.root)
try:
declaration = Declaration(declaration_path)
except Exception as e:
print >> sys.stderr, e.message
sys.exit(1)
view_model.Gen(declaration, destination)
web_ui_view.Gen(declaration, destination)
export_h.Gen(declaration, destination)
# Copyright 2015 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 util
import os
import datetime
HTML_FILE_TEMPLATE = \
"""<!-- Copyright %(year)d 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. -->
<!-- NOTE: this file is generated from "%(source)s". Do not modify directly. -->
<!doctype html>
<html><head>
<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
<link rel="import" href="/webui_generator/webui-view.html">
%(children_includes)s
</head><body>
<polymer-element name="%(element_name)s" extends="webui-view">
</polymer-element>
<script src="%(js_file_path)s"></script>
</body></html>
"""
JS_FILE_TEMPLATE = \
"""// Copyright %(year)d 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.
// NOTE: this file is generated from "%(source)s". Do not modify directly.
Polymer('%(element_name)s', (function() {
'use strict';
return {
getType: function() {
return '%(type)s';
},
initialize: function() {},
contextChanged: function(changedKeys) {},
initChildren_: function() {
%(init_children_body)s
},
initContext_: function() {
%(init_context_body)s
}
}
})());
"""
def GetCommonSubistitutions(declaration):
subs = {}
subs['year'] = datetime.date.today().year
subs['element_name'] = declaration.type.replace('_', '-') + '-view'
subs['source'] = declaration.path
return subs
def GenChildrenIncludes(children):
lines = []
for declaration in set(children.itervalues()):
lines.append('<link rel="import" href="/%s">' %
declaration.html_view_html_include_path)
return '\n'.join(lines)
def GenHTMLFile(declaration):
subs = GetCommonSubistitutions(declaration)
subs['js_file_path'] = subs['element_name'] + '.js'
subs['children_includes'] = GenChildrenIncludes(declaration.children)
return HTML_FILE_TEMPLATE % subs
def GenInitChildrenBody(children):
lines = []
lines.append(' var child = null;');
for id in children:
lines.append(' child = this.shadowRoot.querySelector(\'[wugid="%s"]\');'
% id);
lines.append(' if (!child)');
lines.append(' console.error(this.path_ + \'$%s not found.\');' % id);
lines.append(' else');
lines.append(' child.setPath_(this.path_ + \'$%s\');' % id)
return '\n'.join(lines)
def GenInitContextBody(fields):
lines = []
for field in fields:
value = ''
if field.type in ['integer', 'double']:
value = str(field.default_value)
elif field.type == 'boolean':
value = 'true' if field.default_value else 'false'
elif field.type == 'string':
value = '\'%s\'' % field.default_value
elif field.type == 'string_list':
value = '[' + \
', '.join('\'%s\'' % s for s in field.default_value) + ']'
lines.append(' this.context.set(\'%s\', %s);' % (field.id, value))
lines.append(' this.context.getChangesAndReset();')
return '\n'.join(lines)
def GenJSFile(declaration):
subs = GetCommonSubistitutions(declaration)
subs['type'] = declaration.type
subs['init_children_body'] = GenInitChildrenBody(declaration.children)
subs['init_context_body'] = GenInitContextBody(declaration.fields)
return JS_FILE_TEMPLATE % subs
# Copyright 2015 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 argparse
import os.path
import re
def CreateArgumentParser():
parser = argparse.ArgumentParser()
parser.add_argument('declaration', help='Path to declaration file.')
parser.add_argument('--root', default='.', help='Path to src/ dir.')
parser.add_argument('--destination',
help='Root directory for generated files.')
return parser
def ToUpperCamelCase(underscore_name):
return re.sub(r'(?:_|^)(.)', lambda m: m.group(1).upper(), underscore_name)
def ToLowerCamelCase(underscore_name):
return re.sub(r'_(.)', lambda m: m.group(1).upper(), underscore_name)
def PathToIncludeGuard(path):
return re.sub(r'[/.]', '_', path.upper()) + '_'
def CreateDirIfNotExists(path):
if not os.path.isdir(path):
if os.path.exists(path):
raise Exception('%s exists and is not a directory.' % path)
os.makedirs(path)
This diff is collapsed.
# Copyright 2015 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 datetime
import json
import os
import os.path
import util
import html_view
H_FILE_TEMPLATE = \
"""// Copyright %(year)d 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.
// NOTE: this file is generated from "%(source)s". Do not modify directly.
#ifndef %(include_guard)s
#define %(include_guard)s
#include "base/macros.h"
#include "components/webui_generator/web_ui_view.h"
#include "%(export_h_include_path)s"
namespace gen {
class %(export_macro)s %(class_name)s : public ::webui_generator::WebUIView {
public:
%(class_name)s(content::WebUI* web_ui);
%(class_name)s(content::WebUI* web_ui, const std::string& id);
protected:
void AddLocalizedValues(::login::LocalizedValuesBuilder* builder) override;
void AddResources(ResourcesMap* resources_map) override;
void CreateAndAddChildren() override;
::webui_generator::ViewModel* CreateViewModel() override;
std::string GetType() override;
private:
DISALLOW_COPY_AND_ASSIGN(%(class_name)s);
};
} // namespace gen
#endif // %(include_guard)s
"""
CC_FILE_TEMPLATE = \
"""// Copyright %(year)d 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.
// NOTE: this file is generated from "%(source)s". Do not modify directly.
#include "%(header_path)s"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_contents.h"
#include "components/login/localized_values_builder.h"
#include "grit/components_strings.h"
%(includes)s
namespace {
const char kHTMLDoc[] = "%(html_doc)s";
const char kJSDoc[] = "%(js_doc)s";
} // namespace
namespace gen {
%(class_name)s::%(class_name)s(content::WebUI* web_ui)
: webui_generator::WebUIView(web_ui, "WUG_ROOT") {
}
%(class_name)s::%(class_name)s(content::WebUI* web_ui, const std::string& id)
: webui_generator::WebUIView(web_ui, id) {
}
void %(class_name)s::AddLocalizedValues(
::login::LocalizedValuesBuilder* builder) {
%(add_localized_values_body)s
}
void %(class_name)s::AddResources(ResourcesMap* resources_map) {
%(add_resources_body)s
}
void %(class_name)s::CreateAndAddChildren() {
%(create_and_add_children_body)s
}
::webui_generator::ViewModel* %(class_name)s::CreateViewModel() {
%(create_view_model_body)s
}
std::string %(class_name)s::GetType() {
%(get_type_body)s
}
} // namespace gen
"""
ADD_LOCALIZED_VALUE_TEMPLATE = \
""" builder->Add("%(string_name)s", %(resource_id)s);"""
ADD_RESOURCE_TEMPLATE = \
""" (*resources_map)["%(path)s"] = scoped_refptr<base::RefCountedMemory>(
new base::RefCountedStaticMemory(%(const)s, arraysize(%(const)s) - 1));"""
CREATE_AND_ADD_CHILD_TEMPLATE = \
""" AddChild(new gen::%(child_class)s(web_ui(), "%(child_id)s"));"""
CREATE_VIEW_MODEL_BODY_TEMPLATE = \
""" return gen::%s::Create(web_ui()->GetWebContents()->GetBrowserContext());"""
def GetCommonSubistitutions(declaration):
subs = {}
subs['year'] = datetime.date.today().year
subs['class_name'] = declaration.webui_view_class
subs['source'] = declaration.path
return subs
def GenHFile(declaration):
subs = GetCommonSubistitutions(declaration)
subs['include_guard'] = util.PathToIncludeGuard(
declaration.webui_view_include_path)
subs['export_h_include_path'] = declaration.export_h_include_path
subs['export_macro'] = declaration.component_export_macro
return H_FILE_TEMPLATE % subs
def GenIncludes(declaration):
lines = []
lines.append('#include "%s"' % declaration.view_model_include_path)
children_declarations = set(declaration.children.itervalues())
for child in children_declarations:
lines.append('#include "%s"' % child.webui_view_include_path)
return '\n'.join(lines)
def GenAddLocalizedValuesBody(declaration):
lines = []
resource_id_prefix = "IDS_WUG_" + declaration.type.upper() + "_"
for name in declaration.strings:
resource_id = resource_id_prefix + name.upper()
subs = {
'string_name': util.ToLowerCamelCase(name),
'resource_id': resource_id
}
lines.append(ADD_LOCALIZED_VALUE_TEMPLATE % subs)
return '\n'.join(lines)
def GenAddResourcesBody(declaration):
lines = []
html_path = declaration.html_view_html_include_path
lines.append(ADD_RESOURCE_TEMPLATE % { 'path': html_path,
'const': 'kHTMLDoc' })
js_path = declaration.html_view_js_include_path
lines.append(ADD_RESOURCE_TEMPLATE % { 'path': js_path,
'const': 'kJSDoc' })
return '\n'.join(lines)
def GenCreateAndAddChildrenBody(children):
lines = []
for id, declaration in children.iteritems():
subs = {
'child_class': declaration.webui_view_class,
'child_id': id
}
lines.append(CREATE_AND_ADD_CHILD_TEMPLATE % subs)
return '\n'.join(lines)
def EscapeStringForCLiteral(string):
return json.dumps(string)[1:][:-1]
def GenCCFile(declaration):
subs = GetCommonSubistitutions(declaration)
subs['header_path'] = declaration.webui_view_include_path
subs['includes'] = GenIncludes(declaration)
subs['add_localized_values_body'] = \
GenAddLocalizedValuesBody(declaration)
subs['add_resources_body'] = \
GenAddResourcesBody(declaration)
subs['create_and_add_children_body'] = \
GenCreateAndAddChildrenBody(declaration.children)
subs['create_view_model_body'] = \
CREATE_VIEW_MODEL_BODY_TEMPLATE % declaration.view_model_class
subs['get_type_body'] = ' return "%s";' % declaration.type
subs['html_doc'] = EscapeStringForCLiteral(html_view.GenHTMLFile(declaration))
subs['js_doc'] = EscapeStringForCLiteral(html_view.GenJSFile(declaration))
return CC_FILE_TEMPLATE % subs
def ListOutputs(declaration, destination):
dirname = os.path.join(destination, os.path.dirname(declaration.path))
h_file_path = os.path.join(dirname, declaration.webui_view_h_name)
cc_file_path = os.path.join(dirname, declaration.webui_view_cc_name)
return [h_file_path, cc_file_path]
def Gen(declaration, destination):
h_file_path, cc_file_path = ListOutputs(declaration, destination)
util.CreateDirIfNotExists(os.path.dirname(h_file_path))
open(h_file_path, 'w').write(GenHFile(declaration))
open(cc_file_path, 'w').write(GenCCFile(declaration))
# Copyright 2015 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.
# Generates native and HTML/JS supporting code for Web UI element from element's
# declaration JSON file.
#
# Parameters:
#
# source (required)
# declaration file.
#
# Example:
# wug("some_type_wug_generated") {
# source = "some_type.json"
# }
#
# Target's name should be deduced from declaration file name by removing
# extension and adding '_wug_generated' prefix. This is needed to properly
# handle dependencies between declaration files and their imports.
#
# For declaration file with a full path 'src/full/path/some_type.json' 5 files
# will be generated and compiled:
# $root_gen_dir/wug/full/path/some_type_export.h
# $root_gen_dir/wug/full/path/some_type_view.h
# $root_gen_dir/wug/full/path/some_type_view_model.cc
# $root_gen_dir/wug/full/path/some_type_view_model.h
# $root_gen_dir/wug/full/path/some_type_webui_view.cc
template("wug") {
declaration_path = invoker.source
generator_dir = "//components/webui_generator/generator"
generator_path = "$generator_dir/gen_sources.py"
src_root = rebase_path("//", root_build_dir)
helper_path = "$generator_dir/build_helper.py"
target_name = "${target_name}"
action_name = target_name + "_gen"
out_dir = "$root_gen_dir/wug"
helper_args = [
rebase_path(declaration_path, root_build_dir),
"--destination",
out_dir,
"--root",
src_root,
"--gn",
"--output",
]
expected_target_name =
exec_script(helper_path, helper_args + [ "target_name" ], "trim string")
assert(target_name == expected_target_name,
"Wrong target name. " + "Expected '" + expected_target_name +
"', got '" + target_name + "'.")
action(action_name) {
script = generator_path
sources = [
"$generator_dir/declaration.py",
"$generator_dir/export_h.py",
"$generator_dir/html_view.py",
"$generator_dir/util.py",
"$generator_dir/view_model.py",
"$generator_dir/web_ui_view.py",
]
inputs = [
declaration_path,
]
inputs +=
exec_script(helper_path, helper_args + [ "imports" ], "list lines")
common_prefix = process_file_template(
[ declaration_path ],
"$out_dir/{{source_root_relative_dir}}/{{source_name_part}}_")
common_prefix = common_prefix[0]
outputs = [
common_prefix + "export.h",
common_prefix + "view_model.h",
common_prefix + "view_model.cc",
common_prefix + "web_ui_view.h",
common_prefix + "web_ui_view.cc",
]
args = [
rebase_path(declaration_path, root_build_dir),
"--root",
src_root,
"--destination",
out_dir,
]
}
component(target_name) {
sources = get_target_outputs(":$action_name")
defines = [ exec_script(helper_path,
helper_args + [ "impl_macro" ],
"trim string") ]
deps = [
"//base",
"//components/login",
"//components/strings",
]
deps += exec_script(helper_path,
helper_args + [ "import_dependencies" ],
"list lines")
public_deps = [
"//components/webui_generator",
]
all_dependent_configs =
[ "//components/webui_generator:wug_generated_config" ]
}
}
# Copyright (c) 2015 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.
# This file included into a targets definition creates a target that generates
# native and HTML/JS supporting code for Web UI element from element's
# declaration JSON file.
#
# Example:
# 'targets': [
# ...
# {
# 'variables': {
# 'declaration_file': 'path/to/file.json'
# }
# 'includes': ['path/to/this/file.gypi']
# },
# ...
# ]
#
# Such inclusion creates a target, which name is deduced from declaration file
# name by removing extension and adding '_wug_generated' prefix, e.g. for
# declaration file named 'some_type.json' will be created target named
# 'some_type_wug_generated'. This is needed to properly handle dependencies
# between declaration files and their imports.
#
# For declaration file with a full path 'src/full/path/some_type.json' 5 files
# will be generated and compiled:
# <(SHARED_INTERMEDIATE_DIR)/wug/full/path/some_type_export.h
# <(SHARED_INTERMEDIATE_DIR)/wug/full/path/some_type_view.h
# <(SHARED_INTERMEDIATE_DIR)/wug/full/path/some_type_view_model.cc
# <(SHARED_INTERMEDIATE_DIR)/wug/full/path/some_type_view_model.h
# <(SHARED_INTERMEDIATE_DIR)/wug/full/path/some_type_webui_view.cc
{
'variables': {
'generator_dir': '<(DEPTH)/components/webui_generator/generator',
'helper_path': '<(generator_dir)/build_helper.py',
'generator_path': '<(generator_dir)/gen_sources.py',
'out_dir': '<(SHARED_INTERMEDIATE_DIR)/wug',
'helper_cl': 'python <(helper_path) --root=<(DEPTH) <(declaration_file)',
'dirname': '<(out_dir)/<!(<(helper_cl) --output=dirname)',
'view_cc': '<(dirname)/<!(<(helper_cl) --output=view_cc)',
'view_h': '<(dirname)/<!(<(helper_cl) --output=view_h)',
'model_cc': '<(dirname)/<!(<(helper_cl) --output=model_cc)',
'model_h': '<(dirname)/<!(<(helper_cl) --output=model_h)',
'export_h': '<(dirname)/<!(<(helper_cl) --output=export_h)',
'export_h': '<(dirname)/<!(<(helper_cl) --output=export_h)',
'impl_macro': '<!(<(helper_cl) --output=impl_macro)',
},
'target_name': '<!(<(helper_cl) --output=target_name)',
'type': '<(component)',
'sources': [
'<(view_cc)',
'<(view_h)',
'<(model_cc)',
'<(model_h)',
'<(export_h)',
],
'defines': [
'<(impl_macro)',
],
'actions': [
{
'action_name': 'gen_files',
'inputs': [
'<(generator_path)',
'<(generator_dir)/declaration.py',
'<(generator_dir)/export_h.py',
'<(generator_dir)/html_view.py',
'<(generator_dir)/util.py',
'<(generator_dir)/view_model.py',
'<(generator_dir)/web_ui_view.py',
'<(declaration_file)',
],
'outputs': [
'<(view_cc)',
'<(view_h)',
'<(model_cc)',
'<(model_h)',
'<(export_h)',
],
'action': [
'python',
'<(generator_path)',
'--root=<(DEPTH)',
'--destination=<(out_dir)',
'<(declaration_file)'
],
'message': 'Generating C++ code from <(declaration_file).',
},
],
'dependencies': [
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/components/components.gyp:login',
'<(DEPTH)/components/components.gyp:webui_generator',
'<(DEPTH)/components/components_strings.gyp:components_strings',
'<(DEPTH)/skia/skia.gyp:skia',
'<!@(<(helper_cl) --output=import_dependencies)',
],
'include_dirs': [
'<(DEPTH)',
'<(out_dir)',
],
'all_dependent_settings': {
'include_dirs': [
'<(DEPTH)',
'<(out_dir)',
],
},
'export_dependent_settings': [
'<(DEPTH)/components/components.gyp:webui_generator',
'<(DEPTH)/components/components.gyp:login',
],
# This target exports a hard dependency because it generates header
# files.
'hard_dependency': 1,
}
<!-- Copyright 2015 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. -->
<script src="context.js"></script>
// Copyright (c) 2014 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(global) {
var has_cr = !!global.cr;
var cr_define_bckp = null;
if (has_cr && global.cr.define)
cr_define_bckp = global.cr.define;
if (!has_cr)
global.cr = {}
global.cr.define = function(_, define) {
global.wug = global.wug || {}
global.wug.Context = define().ScreenContext;
}
<include src="../../../chrome/browser/resources/chromeos/login/screen_context.js">
if (!has_cr)
delete global.cr;
if (cr_define_bckp)
global.cr.define = cr_define_bckp;
})(this);
<!-- Copyright 2015 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. -->
<link rel="import" href="chrome://resources/polymer/polymer/polymer.html">
<link rel="import" href="context.html">
<polymer-element name="webui-view"></polymer-element>
<script src="webui-view.js"></script>
// Copyright (c) 2014 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.
Polymer('webui-view', (function() {
/** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged';
/** @const */ var CALLBACK_READY = 'ready';
return {
/** @const */
CALLBACK_EVENT_FIRED: 'eventFired',
/**
* Dictionary of context observers that are methods of |this| bound to
* |this|.
*/
contextObservers_: null,
/**
* Full path to the element in views hierarchy.
*/
path_: null,
/**
* Whether DOM is ready.
*/
domReady_: false,
/**
* Context used for sharing data with native backend.
*/
context: null,
/**
* Internal storage of |this.context|.
* |C| bound to the native part of the context, that means that all the
* changes in the native part appear in |C| automatically. Reverse is not
* true, you should use:
* this.context.set(...);
* this.context.commitContextChanges();
* to send updates to the native part.
*/
C: null,
ready: function() {
this.context = new wug.Context;
this.C = this.context.storage_;
this.contextObservers_ = {};
},
/**
* One of element's lifecycle methods.
*/
domReady: function() {
var id = this.getAttribute('wugid');
if (!id) {
console.error('"wugid" attribute is missing.');
return;
}
if (id == 'WUG_ROOT')
this.setPath_('WUG_ROOT');
this.domReady_ = true;
if (this.path_)
this.init_();
},
setPath_: function(path) {
this.path_ = path;
if (this.domReady_)
this.init_();
},
init_: function() {
this.initContext_();
this.initChildren_();
window[this.path_ + '$contextChanged'] =
this.onContextChanged_.bind(this);
this.initialize();
this.send(CALLBACK_READY);
},
fireEvent: function(_, _, source) {
this.send(this.CALLBACK_EVENT_FIRED, source.getAttribute('event'));
},
i18n: function(args) {
if (!(args instanceof Array))
args = [args];
args[0] = this.getType() + '$' + args[0];
return loadTimeData.getStringF.apply(loadTimeData, args);
},
/**
* Sends message to Chrome, adding needed prefix to message name. All
* arguments after |messageName| are packed into message parameters list.
*
* @param {string} messageName Name of message without a prefix.
* @param {...*} varArgs parameters for message.
* @private
*/
send: function(messageName, varArgs) {
if (arguments.length == 0)
throw Error('Message name is not provided.');
var fullMessageName = this.path_ + '$' + messageName;
var payload = Array.prototype.slice.call(arguments, 1);
chrome.send(fullMessageName, payload);
},
/**
* Starts observation of property with |key| of the context attached to
* current screen. In contrast with "wug.Context.addObserver" this method
* can automatically detect if observer is method of |this| and make
* all needed actions to make it work correctly. So there is no need in
* binding method to |this| before adding it. For example, if |this| has
* a method |onKeyChanged_|, you can do:
*
* this.addContextObserver('key', this.onKeyChanged_);
* ...
* this.removeContextObserver('key', this.onKeyChanged_);
*
* instead of:
*
* this.keyObserver_ = this.onKeyChanged_.bind(this);
* this.addContextObserver('key', this.keyObserver_);
* ...
* this.removeContextObserver('key', this.keyObserver_);
*
* @private
*/
addContextObserver: function(key, observer) {
var realObserver = observer;
var propertyName = this.getPropertyNameOf_(observer);
if (propertyName) {
if (!this.contextObservers_.hasOwnProperty(propertyName))
this.contextObservers_[propertyName] = observer.bind(this);
realObserver = this.contextObservers_[propertyName];
}
this.context.addObserver(key, realObserver);
},
/**
* Removes |observer| from the list of context observers. Observer could be
* a method of |this| (see comment to |addContextObserver|).
* @private
*/
removeContextObserver: function(observer) {
var realObserver = observer;
var propertyName = this.getPropertyNameOf_(observer);
if (propertyName) {
if (!this.contextObservers_.hasOwnProperty(propertyName))
return;
realObserver = this.contextObservers_[propertyName];
delete this.contextObservers_[propertyName];
}
this.context.removeObserver(realObserver);
},
/**
* Sends recent context changes to C++ handler.
* @private
*/
commitContextChanges: function() {
if (!this.context.hasChanges())
return;
this.send(CALLBACK_CONTEXT_CHANGED, this.context.getChangesAndReset());
},
/**
* Called when context changed on C++ side.
*/
onContextChanged_: function(diff) {
var changedKeys = this.context.applyChanges(diff);
this.contextChanged(changedKeys);
},
/**
* If |value| is the value of some property of |this| returns property's
* name. Otherwise returns empty string.
* @private
*/
getPropertyNameOf_: function(value) {
for (var key in this)
if (this[key] === value)
return key;
return '';
}
};
})());
// Copyright 2015 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.
#include "components/webui_generator/view.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "components/webui_generator/view_model.h"
namespace webui_generator {
View::View(const std::string& id)
: parent_(NULL),
id_(id),
ready_children_(0),
view_model_ready_(false),
ready_(false),
weak_factory_(this) {
}
View::~View() {
}
void View::Init() {
CreateAndAddChildren();
if (!IsRootView())
path_ = parent_->path() + "$";
path_ += id_;
view_model_.reset(CreateViewModel());
view_model_->SetView(this);
for (auto& id_child : children_)
id_child.second->Init();
if (children_.empty())
OnChildrenReady();
}
bool View::IsRootView() const {
return !parent_;
}
ViewModel* View::GetViewModel() const {
return view_model_.get();
}
void View::OnViewModelReady() {
view_model_ready_ = true;
if (ready_children_ == static_cast<int>(children_.size()))
OnReady();
}
View* View::GetChild(const std::string& id) const {
auto it = children_.find(id);
if (it == children_.end())
return nullptr;
return it->second;
}
void View::OnReady() {
if (ready_)
return;
ready_ = true;
if (!IsRootView())
parent_->OnChildReady(this);
}
void View::UpdateContext(const base::DictionaryValue& diff) {
DCHECK(ready_);
view_model_->UpdateContext(diff);
}
void View::HandleEvent(const std::string& event) {
DCHECK(ready_);
view_model_->OnEvent(event);
}
void View::AddChild(View* child) {
DCHECK(children_.find(child->id()) == children_.end());
DCHECK(!child->id().empty());
children_.set(child->id(), make_scoped_ptr(child));
child->set_parent(this);
}
void View::OnChildReady(View* child) {
++ready_children_;
if (ready_children_ == static_cast<int>(children_.size()))
OnChildrenReady();
}
void View::OnChildrenReady() {
view_model_->OnChildrenReady();
if (view_model_ready_)
OnReady();
}
} // namespace webui_generator
// Copyright 2015 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.
#ifndef CHROME_BROWSER_UI_WEBUI_WUG_VIEW_H_
#define CHROME_BROWSER_UI_WEBUI_WUG_VIEW_H_
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "components/webui_generator/export.h"
namespace base {
class DictionaryValue;
}
namespace webui_generator {
class ViewModel;
/**
* Base block of Web UI (in terms of Web UI Generator). Every View instance is
* associated with single ViewModel instance which provides a context for the
* view. Views are hierarchical. Every child of view has an unique id.
*/
class WUG_EXPORT View {
public:
explicit View(const std::string& id);
virtual ~View();
// Should be called before using the view. This method creates children (using
// CreateAndAddChildren() method) and a view-model (using CreateViewModel()
// method). Then it recursively calls Init() for the children. When
// all the children and the view-model are ready, OnReady() method is called.
// Overridden methods should call the base implementation.
virtual void Init();
// Root view is a view without parent.
// Root view should have id equal to "WUG_ROOT".
bool IsRootView() const;
ViewModel* GetViewModel() const;
// Called by view-model when it is ready.
void OnViewModelReady();
const std::string& id() const { return id_; }
// Every view has an unique path in a view hierarchy which is:
// a) |id| for the root view;
// b) concatenation of parent's path, $-sign and |id| for not-root views.
const std::string& path() const { return path_; }
// Returns a child with a given |id|. Returns |nullptr| if such child doesn't
// exist.
View* GetChild(const std::string& id) const;
// Called by view-model when it changes the context.
virtual void OnContextChanged(const base::DictionaryValue& diff) = 0;
protected:
// Called when view is ready, which means view-model and all children are
// ready. Overridden methods should call the base implementation.
virtual void OnReady();
// Forwards context changes stored in |diff| to view-model.
void UpdateContext(const base::DictionaryValue& diff);
// Forwards |event| to view-model.
void HandleEvent(const std::string& event);
bool ready() const { return ready_; }
base::ScopedPtrHashMap<std::string, View>& children() { return children_; }
// Adds |child| to the list of children of |this|. Can be called only from
// CreateAndAddChildren() override.
void AddChild(View* child);
virtual std::string GetType() = 0;
// Implementation should create an instance of view-model for this view.
virtual ViewModel* CreateViewModel() = 0;
// Implementation should create and add children to |this| view. This method
// is an only place where it is allowed to add children.
virtual void CreateAndAddChildren() = 0;
private:
// Called by |child| view, when it is ready.
void OnChildReady(View* child);
// Called when all the children created by CreatedAndAddChildren() are ready.
void OnChildrenReady();
void set_parent(View* parent) { parent_ = parent; }
View* parent_;
std::string id_;
std::string path_;
// Number of child views that are ready.
int ready_children_;
bool view_model_ready_;
bool ready_;
base::ScopedPtrHashMap<std::string, View> children_;
scoped_ptr<ViewModel> view_model_;
base::WeakPtrFactory<View> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(View);
};
} // namespace webui_generator
#endif // CHROME_BROWSER_UI_WEBUI_WUG_VIEW_H_
// Copyright 2015 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.
#include "components/webui_generator/view_model.h"
#include "components/webui_generator/view.h"
namespace webui_generator {
ViewModel::ContextEditor::ContextEditor(ViewModel& view_model)
: view_model_(view_model), context_(view_model_.context()) {
}
ViewModel::ContextEditor::~ContextEditor() {
view_model_.CommitContextChanges();
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetBoolean(
const KeyType& key,
bool value) const {
context_.SetBoolean(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetInteger(
const KeyType& key,
int value) const {
context_.SetInteger(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetDouble(
const KeyType& key,
double value) const {
context_.SetDouble(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetString(
const KeyType& key,
const std::string& value) const {
context_.SetString(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetString(
const KeyType& key,
const base::string16& value) const {
context_.SetString(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetStringList(
const KeyType& key,
const StringList& value) const {
context_.SetStringList(key, value);
return *this;
}
const ViewModel::ContextEditor& ViewModel::ContextEditor::SetString16List(
const KeyType& key,
const String16List& value) const {
context_.SetString16List(key, value);
return *this;
}
ViewModel::ViewModel() : view_(nullptr) {
}
ViewModel::~ViewModel() {
}
void ViewModel::SetView(View* view) {
view_ = view;
Initialize();
}
void ViewModel::OnChildrenReady() {
OnAfterChildrenReady();
view_->OnViewModelReady();
}
void ViewModel::UpdateContext(const base::DictionaryValue& diff) {
std::vector<std::string> changed_keys;
context_.ApplyChanges(diff, &changed_keys);
OnContextChanged(changed_keys);
}
void ViewModel::OnEvent(const std::string& event) {
}
ViewModel::ContextEditor ViewModel::GetContextEditor() {
return ContextEditor(*this);
}
void ViewModel::CommitContextChanges() {
if (!context().HasChanges())
return;
base::DictionaryValue diff;
context().GetChangesAndReset(&diff);
view_->OnContextChanged(diff);
}
} // namespace webui_generator
// Copyright 2015 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.
#ifndef CHROME_BROWSER_UI_WEBUI_WUG_VIEW_MODEL_H_
#define CHROME_BROWSER_UI_WEBUI_WUG_VIEW_MODEL_H_
#include <vector>
#include "base/macros.h"
#include "components/login/screens/screen_context.h"
#include "components/webui_generator/export.h"
namespace base {
class DictionaryValue;
}
namespace webui_generator {
using Context = login::ScreenContext;
class View;
// Base class for view-model. Usually View is responsible for creating and
// initializing instances of this class.
class WUG_EXPORT ViewModel {
public:
ViewModel();
virtual ~ViewModel();
void SetView(View* view);
// Called by view to notify that children are ready.
void OnChildrenReady();
// Called by view when it became bound (meaning that all changes in context
// will be reflected in view right after they are made).
virtual void OnViewBound() = 0;
// Notifies view-model about context changes happended on view side.
// View-model corrects its copy of context according to changes in |diff|.
void UpdateContext(const base::DictionaryValue& diff);
// Notifies view-model about |event| sent from view.
virtual void OnEvent(const std::string& event);
protected:
// Scoped context editor, which automatically commits all pending
// context changes on destruction.
class ContextEditor {
public:
using KeyType = ::login::ScreenContext::KeyType;
using String16List = ::login::String16List;
using StringList = ::login::StringList;
explicit ContextEditor(ViewModel& screen);
~ContextEditor();
const ContextEditor& SetBoolean(const KeyType& key, bool value) const;
const ContextEditor& SetInteger(const KeyType& key, int value) const;
const ContextEditor& SetDouble(const KeyType& key, double value) const;
const ContextEditor& SetString(const KeyType& key,
const std::string& value) const;
const ContextEditor& SetString(const KeyType& key,
const base::string16& value) const;
const ContextEditor& SetStringList(const KeyType& key,
const StringList& value) const;
const ContextEditor& SetString16List(const KeyType& key,
const String16List& value) const;
private:
ViewModel& view_model_;
Context& context_;
};
// This method is called in a time appropriate for initialization. At this
// point it is allowed to make changes in the context, but it is not allowed
// to access child view-models.
virtual void Initialize() = 0;
// This method is called when all children are ready.
virtual void OnAfterChildrenReady() = 0;
// This method is called after context was changed on view side.
// |chanaged_keys| contains a list of keys of changed fields.
virtual void OnContextChanged(const std::vector<std::string>& changed_keys) {}
Context& context() { return context_; }
const Context& context() const { return context_; }
View* view() const { return view_; }
// Returns scoped context editor. The editor or it's copies should not outlive
// current BaseScreen instance.
ContextEditor GetContextEditor();
// Notifies view about changes made in context.
void CommitContextChanges();
private:
Context context_;
View* view_;
DISALLOW_COPY_AND_ASSIGN(ViewModel);
};
} // namespace webui_generator
#endif // CHROME_BROWSER_UI_WEBUI_WUG_VIEW_MODEL_H_
// Copyright 2015 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.
#include "components/webui_generator/web_ui_view.h"
#include "base/bind_helpers.h"
#include "base/memory/scoped_ptr.h"
#include "components/login/localized_values_builder.h"
#include "components/webui_generator/data_source_util.h"
#include "components/webui_generator/view_model.h"
namespace {
const char kEventCallback[] = "eventFired";
const char kContextChangedCallback[] = "contextChanged";
const char kReadyCallback[] = "ready";
const char kMethodContextChanged[] = "contextChanged";
} // namespace
namespace webui_generator {
WebUIView::WebUIView(content::WebUI* web_ui, const std::string& id)
: View(id), web_ui_(web_ui), html_ready_(false), view_bound_(false) {
}
WebUIView::~WebUIView() {
}
void WebUIView::Init() {
View::Init();
AddCallback(path() + "$" + kEventCallback, &WebUIView::HandleEvent);
AddCallback(path() + "$" + kContextChangedCallback,
&WebUIView::HandleContextChanged);
AddCallback(path() + "$" + kReadyCallback, &WebUIView::HandleHTMLReady);
}
void WebUIView::SetUpDataSource(content::WebUIDataSource* data_source) {
DCHECK(IsRootView());
webui_generator::SetUpDataSource(data_source);
ResourcesMap* resources_map = new ResourcesMap();
SetUpDataSourceInternal(data_source, resources_map);
data_source->SetRequestFilter(base::Bind(&WebUIView::HandleDataRequest,
base::Unretained(this),
base::Owned(resources_map)));
}
void WebUIView::OnReady() {
View::OnReady();
if (html_ready_)
Bind();
}
void WebUIView::OnContextChanged(const base::DictionaryValue& diff) {
if (view_bound_)
web_ui_->CallJavascriptFunction(path() + "$" + kMethodContextChanged, diff);
if (!pending_context_changes_)
pending_context_changes_.reset(new Context());
pending_context_changes_->ApplyChanges(diff, nullptr);
}
void WebUIView::SetUpDataSourceInternal(content::WebUIDataSource* data_source,
ResourcesMap* resources_map) {
base::DictionaryValue strings;
auto builder = make_scoped_ptr(
new ::login::LocalizedValuesBuilder(GetType() + "$", &strings));
AddLocalizedValues(builder.get());
data_source->AddLocalizedStrings(strings);
AddResources(resources_map);
for (auto& id_child : children()) {
static_cast<WebUIView*>(id_child.second)
->SetUpDataSourceInternal(data_source, resources_map);
}
}
bool WebUIView::HandleDataRequest(
const ResourcesMap* resources,
const std::string& path,
const content::WebUIDataSource::GotDataCallback& got_data_callback) {
auto it = resources->find(path);
if (it == resources->end())
return false;
got_data_callback.Run(it->second.get());
return true;
}
void WebUIView::HandleHTMLReady() {
html_ready_ = true;
if (ready())
Bind();
}
void WebUIView::HandleContextChanged(const base::DictionaryValue* diff) {
UpdateContext(*diff);
}
void WebUIView::Bind() {
view_bound_ = true;
if (pending_context_changes_)
OnContextChanged(pending_context_changes_->storage());
GetViewModel()->OnViewBound();
}
} // namespace webui_generator
// Copyright 2015 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.
#ifndef CHROME_BROWSER_UI_WEBUI_WUG_WEB_UI_VIEW_H_
#define CHROME_BROWSER_UI_WEBUI_WUG_WEB_UI_VIEW_H_
#include <string>
#include "base/bind.h"
#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_ptr.h"
#include "components/login/base_screen_handler_utils.h"
#include "components/login/screens/screen_context.h"
#include "components/webui_generator/export.h"
#include "components/webui_generator/view.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
namespace base {
class DictionaryValue;
}
namespace content {
class WebUI;
}
namespace login {
class LocalizedValuesBuilder;
}
namespace webui_generator {
using Context = login::ScreenContext;
/**
* View implementation for the WebUI. Represents a single HTML element derieved
* from <web-ui-view>.
*/
class WUG_EXPORT WebUIView : public View {
public:
WebUIView(content::WebUI* web_ui, const std::string& id);
~WebUIView() override;
// Should be called for |data_source|, where this views will be used.
// Sets up |data_source| for children of |this| as well, that is why this
// method only shouldn't be called for non-root views.
void SetUpDataSource(content::WebUIDataSource* data_source);
// Overridden from View:
void Init() override;
protected:
using ResourcesMap =
base::hash_map<std::string, scoped_refptr<base::RefCountedMemory>>;
content::WebUI* web_ui() { return web_ui_; }
template <typename T>
void AddCallback(const std::string& name, void (T::*method)()) {
base::Callback<void()> callback =
base::Bind(method, base::Unretained(static_cast<T*>(this)));
web_ui_->RegisterMessageCallback(
name, base::Bind(&::login::CallbackWrapper0, callback));
}
template <typename T, typename A1>
void AddCallback(const std::string& name, void (T::*method)(A1 arg1)) {
base::Callback<void(A1)> callback =
base::Bind(method, base::Unretained(static_cast<T*>(this)));
web_ui_->RegisterMessageCallback(
name, base::Bind(&::login::CallbackWrapper1<A1>, callback));
}
// Overridden from View:
void OnReady() override;
void OnContextChanged(const base::DictionaryValue& diff) override;
virtual void AddLocalizedValues(::login::LocalizedValuesBuilder* builder) = 0;
virtual void AddResources(ResourcesMap* resources_map) = 0;
private:
// Internal implementation of SetUpDataSource.
void SetUpDataSourceInternal(content::WebUIDataSource* data_source,
ResourcesMap* resources_map);
// Called when corresponding <web-ui-view> is ready.
void HandleHTMLReady();
// Called when context is changed on JS side.
void HandleContextChanged(const base::DictionaryValue* diff);
// Called when both |this| and corresponding <web-ui-view> are ready.
void Bind();
// Request filter for data source. Filter is needed to answer with a generated
// HTML and JS code which is not kept in resources.
bool HandleDataRequest(
const ResourcesMap* resources,
const std::string& path,
const content::WebUIDataSource::GotDataCallback& got_data_callback);
private:
content::WebUI* web_ui_;
bool html_ready_;
bool view_bound_;
scoped_ptr<Context> pending_context_changes_;
DISALLOW_COPY_AND_ASSIGN(WebUIView);
};
} // namespace webui_generator
#endif // CHROME_BROWSER_UI_WEBUI_WUG_WEB_UI_VIEW_H_
<?xml version="1.0" encoding="utf-8"?>
<!--
This strings are used in WebUI views created by WUG framework. Resource id is
deduced from string id and view's type. For example, if view's type is
'foo_bar' and string id is 'some_string', resource id should be
'IDS_WUG_FOO_BAR_SOME_STRING'.
-->
<grit-part>
</grit-part>
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