summaryrefslogtreecommitdiffstats
path: root/components/webui_generator
diff options
context:
space:
mode:
authordzhioev <dzhioev@chromium.org>2015-03-03 08:31:47 -0800
committerCommit bot <commit-bot@chromium.org>2015-03-03 16:32:35 +0000
commitc1c2a503cdd7a68e7c25d36213a15c97e62a9ba7 (patch)
tree5d477d320bf0f3f45bc17642d9b2d7367aaa1d92 /components/webui_generator
parentcbca72f3c9e31309a28b98624e1e0147c5dca7a1 (diff)
downloadchromium_src-c1c2a503cdd7a68e7c25d36213a15c97e62a9ba7.zip
chromium_src-c1c2a503cdd7a68e7c25d36213a15c97e62a9ba7.tar.gz
chromium_src-c1c2a503cdd7a68e7c25d36213a15c97e62a9ba7.tar.bz2
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}
Diffstat (limited to 'components/webui_generator')
-rw-r--r--components/webui_generator/BUILD.gn36
-rw-r--r--components/webui_generator/DEPS6
-rw-r--r--components/webui_generator/OWNERS1
-rw-r--r--components/webui_generator/data_source_util.cc22
-rw-r--r--components/webui_generator/data_source_util.h21
-rw-r--r--components/webui_generator/export.h34
-rw-r--r--components/webui_generator/generator/build_helper.py69
-rw-r--r--components/webui_generator/generator/declaration.py312
-rw-r--r--components/webui_generator/generator/export_h.py66
-rw-r--r--components/webui_generator/generator/gen_sources.py27
-rw-r--r--components/webui_generator/generator/html_view.py111
-rw-r--r--components/webui_generator/generator/util.py30
-rw-r--r--components/webui_generator/generator/view_model.py329
-rw-r--r--components/webui_generator/generator/web_ui_view.py205
-rw-r--r--components/webui_generator/generator/wug.gni112
-rw-r--r--components/webui_generator/generator/wug.gypi118
-rw-r--r--components/webui_generator/resources/context.html5
-rw-r--r--components/webui_generator/resources/context.js21
-rw-r--r--components/webui_generator/resources/webui-view.html10
-rw-r--r--components/webui_generator/resources/webui-view.js189
-rw-r--r--components/webui_generator/view.cc102
-rw-r--r--components/webui_generator/view.h116
-rw-r--r--components/webui_generator/view_model.cc106
-rw-r--r--components/webui_generator/view_model.h108
-rw-r--r--components/webui_generator/web_ui_view.cc111
-rw-r--r--components/webui_generator/web_ui_view.h116
26 files changed, 2383 insertions, 0 deletions
diff --git a/components/webui_generator/BUILD.gn b/components/webui_generator/BUILD.gn
new file mode 100644
index 0000000..13e167f
--- /dev/null
+++ b/components/webui_generator/BUILD.gn
@@ -0,0 +1,36 @@
+# 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" ]
+}
diff --git a/components/webui_generator/DEPS b/components/webui_generator/DEPS
new file mode 100644
index 0000000..3985eef
--- /dev/null
+++ b/components/webui_generator/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+base",
+ "+content/public/browser",
+ "+grit/components_resources.h",
+ "+components/login",
+]
diff --git a/components/webui_generator/OWNERS b/components/webui_generator/OWNERS
new file mode 100644
index 0000000..df660f7
--- /dev/null
+++ b/components/webui_generator/OWNERS
@@ -0,0 +1 @@
+dzhioev@chromium.org
diff --git a/components/webui_generator/data_source_util.cc b/components/webui_generator/data_source_util.cc
new file mode 100644
index 0000000..a3103cd
--- /dev/null
+++ b/components/webui_generator/data_source_util.cc
@@ -0,0 +1,22 @@
+// 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
diff --git a/components/webui_generator/data_source_util.h b/components/webui_generator/data_source_util.h
new file mode 100644
index 0000000..95abe42
--- /dev/null
+++ b/components/webui_generator/data_source_util.h
@@ -0,0 +1,21 @@
+// 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_
diff --git a/components/webui_generator/export.h b/components/webui_generator/export.h
new file mode 100644
index 0000000..226cfd2
--- /dev/null
+++ b/components/webui_generator/export.h
@@ -0,0 +1,34 @@
+// 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_
diff --git a/components/webui_generator/generator/build_helper.py b/components/webui_generator/generator/build_helper.py
new file mode 100644
index 0000000..06232e6
--- /dev/null
+++ b/components/webui_generator/generator/build_helper.py
@@ -0,0 +1,69 @@
+# 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
+
diff --git a/components/webui_generator/generator/declaration.py b/components/webui_generator/generator/declaration.py
new file mode 100644
index 0000000..e9863b5
--- /dev/null
+++ b/components/webui_generator/generator/declaration.py
@@ -0,0 +1,312 @@
+# 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
+
+
+
diff --git a/components/webui_generator/generator/export_h.py b/components/webui_generator/generator/export_h.py
new file mode 100644
index 0000000..3c4eceb
--- /dev/null
+++ b/components/webui_generator/generator/export_h.py
@@ -0,0 +1,66 @@
+# 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))
diff --git a/components/webui_generator/generator/gen_sources.py b/components/webui_generator/generator/gen_sources.py
new file mode 100644
index 0000000..6b9a87a
--- /dev/null
+++ b/components/webui_generator/generator/gen_sources.py
@@ -0,0 +1,27 @@
+# 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)
+
diff --git a/components/webui_generator/generator/html_view.py b/components/webui_generator/generator/html_view.py
new file mode 100644
index 0000000..73fc8a9
--- /dev/null
+++ b/components/webui_generator/generator/html_view.py
@@ -0,0 +1,111 @@
+# 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
diff --git a/components/webui_generator/generator/util.py b/components/webui_generator/generator/util.py
new file mode 100644
index 0000000..28a20db
--- /dev/null
+++ b/components/webui_generator/generator/util.py
@@ -0,0 +1,30 @@
+# 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)
diff --git a/components/webui_generator/generator/view_model.py b/components/webui_generator/generator/view_model.py
new file mode 100644
index 0000000..a920807
--- /dev/null
+++ b/components/webui_generator/generator/view_model.py
@@ -0,0 +1,329 @@
+# 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 datetime
+import util
+
+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/view_model.h"
+#include "%(export_h_include_path)s"
+
+namespace content {
+class BrowserContext;
+}
+
+%(children_forward_declarations)s
+
+namespace gen {
+
+class %(export_macro)s %(class_name)s : public ::webui_generator::ViewModel {
+ public:
+ using FactoryFunction = %(class_name)s* (*)(content::BrowserContext*);
+
+%(context_keys)s
+
+ static %(class_name)s* Create(content::BrowserContext* context);
+ static void SetFactory(FactoryFunction factory);
+
+ %(class_name)s();
+
+%(children_getters)s
+
+%(context_getters)s
+
+%(event_handlers)s
+
+ void Initialize() override {}
+ void OnAfterChildrenReady() override {}
+ void OnViewBound() override {}
+
+ private:
+ void OnEvent(const std::string& event) final;
+
+ static FactoryFunction factory_function_;
+};
+
+} // namespace gen
+
+#endif // %(include_guard)s
+"""
+
+CHILD_FORWARD_DECLARATION_TEMPLATE = \
+"""namespace gen {
+ class %(child_type)s;
+}
+"""
+CONTEXT_KEY_DECLARATION_TEMPLATE = \
+""" static const char %s[];"""
+
+CHILD_GETTER_DECLARATION_TEMPLATE = \
+""" virtual gen::%(child_type)s* %(method_name)s() const;""";
+
+CONTEXT_VALUE_GETTER_DECLARATION_TEMPLATE = \
+""" %(type)s %(method_name)s() const;"""
+
+EVENT_HANDLER_DECLARATION_TEMPLATE = \
+""" virtual void %s() = 0;"""
+
+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 "base/logging.h"
+#include "components/webui_generator/view.h"
+%(children_includes)s
+
+namespace gen {
+
+%(context_keys)s
+
+%(class_name)s::FactoryFunction %(class_name)s::factory_function_;
+
+%(class_name)s* %(class_name)s::Create(content::BrowserContext* context) {
+ CHECK(factory_function_) << "Factory function for %(class_name)s was not "
+ "set.";
+ return factory_function_(context);
+}
+
+void %(class_name)s::SetFactory(FactoryFunction factory) {
+ factory_function_ = factory;
+}
+
+%(class_name)s::%(class_name)s() {
+%(constructor_body)s
+}
+
+%(children_getters)s
+
+%(context_getters)s
+
+void %(class_name)s::OnEvent(const std::string& event) {
+%(event_dispatcher_body)s
+ LOG(ERROR) << "Unknown event '" << event << "'";
+}
+
+} // namespace gen
+"""
+
+CONTEXT_KEY_DEFINITION_TEMPLATE = \
+"""const char %(class_name)s::%(name)s[] = "%(value)s";"""
+
+CHILD_GETTER_DEFINITION_TEMPLATE = \
+"""gen::%(child_type)s* %(class_name)s::%(method_name)s() const {
+ return static_cast<gen::%(child_type)s*>(
+ view()->GetChild("%(child_id)s")->GetViewModel());
+}
+"""
+
+CONTEXT_VALUE_GETTER_DEFINITION_TEMPLATE = \
+"""%(type)s %(class_name)s::%(method_name)s() const {
+ return context().%(context_getter)s(%(key_constant)s);
+}
+"""
+
+FIELD_TYPE_TO_GETTER_TYPE = {
+ 'boolean': 'bool',
+ 'integer': 'int',
+ 'double': 'double',
+ 'string': 'std::string',
+ 'string_list': 'login::StringList'
+}
+
+DISPATCH_EVENT_TEMPLATE = \
+""" if (event == "%(event_id)s") {
+ %(method_name)s();
+ return;
+ }"""
+
+def GetCommonSubistitutions(declaration):
+ subs = {}
+ subs['year'] = datetime.date.today().year
+ subs['class_name'] = declaration.view_model_class
+ subs['source'] = declaration.path
+ return subs
+
+def FieldNameToConstantName(field_name):
+ return 'kContextKey' + util.ToUpperCamelCase(field_name)
+
+def GenContextKeysDeclarations(fields):
+ return '\n'.join(
+ (CONTEXT_KEY_DECLARATION_TEMPLATE % \
+ FieldNameToConstantName(f.name) for f in fields))
+
+def GenChildrenForwardDeclarations(children):
+ lines = []
+ for declaration in set(children.itervalues()):
+ lines.append(CHILD_FORWARD_DECLARATION_TEMPLATE % {
+ 'child_type': declaration.view_model_class
+ })
+ return '\n'.join(lines)
+
+def ChildIdToChildGetterName(id):
+ return 'Get%s' % util.ToUpperCamelCase(id)
+
+def GenChildrenGettersDeclarations(children):
+ lines = []
+ for id, declaration in children.iteritems():
+ lines.append(CHILD_GETTER_DECLARATION_TEMPLATE % {
+ 'child_type': declaration.view_model_class,
+ 'method_name': ChildIdToChildGetterName(id)
+ })
+ return '\n'.join(lines)
+
+def FieldNameToGetterName(field_name):
+ return 'Get%s' % util.ToUpperCamelCase(field_name)
+
+def GenContextGettersDeclarations(context_fields):
+ lines = []
+ for field in context_fields:
+ lines.append(CONTEXT_VALUE_GETTER_DECLARATION_TEMPLATE % {
+ 'type': FIELD_TYPE_TO_GETTER_TYPE[field.type],
+ 'method_name': FieldNameToGetterName(field.name)
+ })
+ return '\n'.join(lines)
+
+def EventIdToMethodName(event):
+ return 'On' + util.ToUpperCamelCase(event)
+
+def GenEventHandlersDeclarations(events):
+ lines = []
+ for event in events:
+ lines.append(EVENT_HANDLER_DECLARATION_TEMPLATE %
+ EventIdToMethodName(event))
+ return '\n'.join(lines)
+
+def GenHFile(declaration):
+ subs = GetCommonSubistitutions(declaration)
+ subs['include_guard'] = \
+ util.PathToIncludeGuard(declaration.view_model_include_path)
+ subs['context_keys'] = GenContextKeysDeclarations(declaration.fields)
+ subs['children_forward_declarations'] = \
+ GenChildrenForwardDeclarations(declaration.children)
+ subs['children_getters'] = \
+ GenChildrenGettersDeclarations(declaration.children);
+ subs['context_getters'] = \
+ GenContextGettersDeclarations(declaration.fields);
+ subs['event_handlers'] = GenEventHandlersDeclarations(declaration.events)
+ subs['export_h_include_path'] = declaration.export_h_include_path
+ subs['export_macro'] = declaration.component_export_macro
+ return H_FILE_TEMPLATE % subs
+
+def GenContextKeysDefinitions(declaration):
+ lines = []
+ for field in declaration.fields:
+ definition = CONTEXT_KEY_DEFINITION_TEMPLATE % {
+ 'class_name': declaration.view_model_class,
+ 'name': FieldNameToConstantName(field.name),
+ 'value': field.id
+ }
+ lines.append(definition)
+ return '\n'.join(lines)
+
+def GenChildrenIncludes(children):
+ lines = []
+ for declaration in set(children.itervalues()):
+ lines.append('#include "%s"' % declaration.view_model_include_path)
+ return '\n'.join(lines)
+
+def GenContextFieldInitialization(field):
+ lines = []
+ key_constant = FieldNameToConstantName(field.name)
+ setter_method = 'Set' + util.ToUpperCamelCase(field.type)
+ if field.type == 'string_list':
+ lines.append(' {')
+ lines.append(' std::vector<std::string> defaults;')
+ for s in field.default_value:
+ lines.append(' defaults.push_back("%s");' % s)
+ lines.append(' context().%s(%s, defaults);' %
+ (setter_method, key_constant))
+ lines.append(' }')
+ else:
+ setter = ' context().%s(%s, ' % (setter_method, key_constant)
+ if field.type in ['integer', 'double']:
+ setter += str(field.default_value)
+ elif field.type == 'boolean':
+ setter += 'true' if field.default_value else 'false'
+ else:
+ assert field.type == 'string'
+ setter += '"%s"' % field.default_value
+ setter += ");"
+ lines.append(setter)
+ return '\n'.join(lines)
+
+def GenChildrenGettersDefenitions(declaration):
+ lines = []
+ for id, child in declaration.children.iteritems():
+ lines.append(CHILD_GETTER_DEFINITION_TEMPLATE % {
+ 'child_type': child.view_model_class,
+ 'class_name': declaration.view_model_class,
+ 'method_name': ChildIdToChildGetterName(id),
+ 'child_id': id
+ });
+ return '\n'.join(lines)
+
+def GenContextGettersDefinitions(declaration):
+ lines = []
+ for field in declaration.fields:
+ lines.append(CONTEXT_VALUE_GETTER_DEFINITION_TEMPLATE % {
+ 'type': FIELD_TYPE_TO_GETTER_TYPE[field.type],
+ 'class_name': declaration.view_model_class,
+ 'method_name': FieldNameToGetterName(field.name),
+ 'context_getter': 'Get' + util.ToUpperCamelCase(
+ field.type),
+ 'key_constant': FieldNameToConstantName(field.name)
+ });
+ return '\n'.join(lines)
+
+def GenEventDispatcherBody(events):
+ lines = []
+ for event in events:
+ lines.append(DISPATCH_EVENT_TEMPLATE % {
+ 'event_id': util.ToLowerCamelCase(event),
+ 'method_name': EventIdToMethodName(event)
+ });
+ return '\n'.join(lines)
+
+def GenCCFile(declaration):
+ subs = GetCommonSubistitutions(declaration)
+ subs['header_path'] = declaration.view_model_include_path
+ subs['context_keys'] = GenContextKeysDefinitions(declaration)
+ subs['children_includes'] = GenChildrenIncludes(declaration.children)
+ initializations = [GenContextFieldInitialization(field) \
+ for field in declaration.fields]
+ initializations.append(' base::DictionaryValue diff;');
+ initializations.append(' context().GetChangesAndReset(&diff);');
+ subs['constructor_body'] = '\n'.join(initializations)
+ subs['children_getters'] = GenChildrenGettersDefenitions(declaration)
+ subs['context_getters'] = GenContextGettersDefinitions(declaration)
+ subs['event_dispatcher_body'] = GenEventDispatcherBody(declaration.events)
+ 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.view_model_h_name)
+ cc_file_path = os.path.join(dirname, declaration.view_model_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))
+
diff --git a/components/webui_generator/generator/web_ui_view.py b/components/webui_generator/generator/web_ui_view.py
new file mode 100644
index 0000000..f534cae
--- /dev/null
+++ b/components/webui_generator/generator/web_ui_view.py
@@ -0,0 +1,205 @@
+# 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))
+
diff --git a/components/webui_generator/generator/wug.gni b/components/webui_generator/generator/wug.gni
new file mode 100644
index 0000000..a4b4a2d
--- /dev/null
+++ b/components/webui_generator/generator/wug.gni
@@ -0,0 +1,112 @@
+# 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" ]
+ }
+}
diff --git a/components/webui_generator/generator/wug.gypi b/components/webui_generator/generator/wug.gypi
new file mode 100644
index 0000000..a40577c
--- /dev/null
+++ b/components/webui_generator/generator/wug.gypi
@@ -0,0 +1,118 @@
+# 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,
+}
diff --git a/components/webui_generator/resources/context.html b/components/webui_generator/resources/context.html
new file mode 100644
index 0000000..e1631b2
--- /dev/null
+++ b/components/webui_generator/resources/context.html
@@ -0,0 +1,5 @@
+<!-- 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>
+
diff --git a/components/webui_generator/resources/context.js b/components/webui_generator/resources/context.js
new file mode 100644
index 0000000..0bd28f1
--- /dev/null
+++ b/components/webui_generator/resources/context.js
@@ -0,0 +1,21 @@
+// 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);
diff --git a/components/webui_generator/resources/webui-view.html b/components/webui_generator/resources/webui-view.html
new file mode 100644
index 0000000..b07fa5c
--- /dev/null
+++ b/components/webui_generator/resources/webui-view.html
@@ -0,0 +1,10 @@
+<!-- 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>
+
diff --git a/components/webui_generator/resources/webui-view.js b/components/webui_generator/resources/webui-view.js
new file mode 100644
index 0000000..99a033f
--- /dev/null
+++ b/components/webui_generator/resources/webui-view.js
@@ -0,0 +1,189 @@
+// 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 '';
+ }
+ };
+})());
+
diff --git a/components/webui_generator/view.cc b/components/webui_generator/view.cc
new file mode 100644
index 0000000..a335ef5
--- /dev/null
+++ b/components/webui_generator/view.cc
@@ -0,0 +1,102 @@
+// 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
diff --git a/components/webui_generator/view.h b/components/webui_generator/view.h
new file mode 100644
index 0000000..b43ad74
--- /dev/null
+++ b/components/webui_generator/view.h
@@ -0,0 +1,116 @@
+// 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_
diff --git a/components/webui_generator/view_model.cc b/components/webui_generator/view_model.cc
new file mode 100644
index 0000000..d81ce98
--- /dev/null
+++ b/components/webui_generator/view_model.cc
@@ -0,0 +1,106 @@
+// 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
diff --git a/components/webui_generator/view_model.h b/components/webui_generator/view_model.h
new file mode 100644
index 0000000..a7021e0
--- /dev/null
+++ b/components/webui_generator/view_model.h
@@ -0,0 +1,108 @@
+// 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_
diff --git a/components/webui_generator/web_ui_view.cc b/components/webui_generator/web_ui_view.cc
new file mode 100644
index 0000000..54b8668
--- /dev/null
+++ b/components/webui_generator/web_ui_view.cc
@@ -0,0 +1,111 @@
+// 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
diff --git a/components/webui_generator/web_ui_view.h b/components/webui_generator/web_ui_view.h
new file mode 100644
index 0000000..9fd26cf
--- /dev/null
+++ b/components/webui_generator/web_ui_view.h
@@ -0,0 +1,116 @@
+// 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_