diff options
author | dzhioev <dzhioev@chromium.org> | 2015-03-03 08:31:47 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-03 16:32:35 +0000 |
commit | c1c2a503cdd7a68e7c25d36213a15c97e62a9ba7 (patch) | |
tree | 5d477d320bf0f3f45bc17642d9b2d7367aaa1d92 /components/webui_generator | |
parent | cbca72f3c9e31309a28b98624e1e0147c5dca7a1 (diff) | |
download | chromium_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')
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_ |