diff --git a/AUTHORS b/AUTHORS index 9f1e5d6..faf4eca 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -856,6 +856,7 @@ Xuefei Ren <xrenishere@gmail.com> Xueqing Huang <huangxueqing@xiaomi.com> Xun Sun <xun.sun@intel.com> +Xunran Ding <dingxunran@gmail.com> Yael Aharon <yael.aharon@intel.com> Yair Yogev <progame@chromium.org> Yan Wang <yan0422.wang@samsung.com>
diff --git a/DEPS b/DEPS index 070d393..02f64fd 100644 --- a/DEPS +++ b/DEPS
@@ -86,7 +86,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': '54a29ffd82e7782c764b5257365e7f148f48ca4a', + 'angle_revision': '361df070323f430aa0614c6eb04fb595dec3daa9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling build tools # and whatever else without interference from each other. @@ -98,7 +98,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': '1980f10ff2b869f14c409b712eea6744941ebd88', + 'pdfium_revision': '1f0d1fda6db83ee402561902c76ae8a6da124663', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling openmax_dl # and whatever else without interference from each other. @@ -130,7 +130,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '259a1ec4b05d169f5027345828ca77ef7bec6ec6', + 'catapult_revision': '9252115c46302971355d1d5fcbe1512f32d30a70', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. @@ -626,7 +626,7 @@ Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd', 'src/third_party/webgl/src': - Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'e4919fa03c74bd561dcabf3e61668fa3c7e54353', + Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '05591bbeae6592fd924caec8e728a4ea86cbb8c9', 'src/third_party/webrtc': Var('webrtc_git') + '/src.git' + '@' + '4e70a72571dd26b85c2385e9c618e343428df5d3', # commit position 20628
diff --git a/base/android/java/src/org/chromium/base/LocaleUtils.java b/base/android/java/src/org/chromium/base/LocaleUtils.java index 2f51455..85a62b98 100644 --- a/base/android/java/src/org/chromium/base/LocaleUtils.java +++ b/base/android/java/src/org/chromium/base/LocaleUtils.java
@@ -32,7 +32,7 @@ static { // A variation of this mapping also exists in: - // build/android/gyp/package_resources.py + // build/android/gyp/process_resources.py HashMap<String, String> mapForChromium = new HashMap<>(); mapForChromium.put("iw", "he"); // Hebrew mapForChromium.put("ji", "yi"); // Yiddish
diff --git a/base/files/file_win.cc b/base/files/file_win.cc index 6e7c383..3f5547d 100644 --- a/base/files/file_win.cc +++ b/base/files/file_win.cc
@@ -348,6 +348,8 @@ } if (!disposition) { + ::SetLastError(ERROR_INVALID_PARAMETER); + error_details_ = FILE_ERROR_FAILED; NOTREACHED(); return; }
diff --git a/build/android/gyp/package_resources.py b/build/android/gyp/package_resources.py deleted file mode 100755 index 2be10bb..0000000 --- a/build/android/gyp/package_resources.py +++ /dev/null
@@ -1,449 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 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. - -# pylint: disable=C0301 -"""Package resources into an apk. - -See https://android.googlesource.com/platform/tools/base/+/master/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java -and -https://android.googlesource.com/platform/sdk/+/master/files/ant/build.xml -""" -# pylint: enable=C0301 - -import multiprocessing.pool -import optparse -import os -import re -import shutil -import subprocess -import sys -import zipfile - -from util import build_utils - - -# A variation of this lists also exists in: -# //base/android/java/src/org/chromium/base/LocaleUtils.java -_CHROME_TO_ANDROID_LOCALE_MAP = { - 'en-GB': 'en-rGB', - 'en-US': 'en-rUS', - 'es-419': 'es-rUS', - 'fil': 'tl', - 'he': 'iw', - 'id': 'in', - 'pt-PT': 'pt-rPT', - 'pt-BR': 'pt-rBR', - 'yi': 'ji', - 'zh-CN': 'zh-rCN', - 'zh-TW': 'zh-rTW', -} - -# List is generated from the chrome_apk.apk_intermediates.ap_ via: -# unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \ -# | uniq | grep -- -tvdpi- | cut -c10- -# and then manually sorted. -# Note that we can't just do a cross-product of dimensions because the filenames -# become too big and aapt fails to create the files. -# This leaves all default drawables (mdpi) in the main apk. Android gets upset -# though if any drawables are missing from the default drawables/ directory. -DENSITY_SPLITS = { - 'hdpi': ( - 'hdpi-v4', # Order matters for output file names. - 'ldrtl-hdpi-v4', - 'sw600dp-hdpi-v13', - 'ldrtl-hdpi-v17', - 'ldrtl-sw600dp-hdpi-v17', - 'hdpi-v21', - ), - 'xhdpi': ( - 'xhdpi-v4', - 'ldrtl-xhdpi-v4', - 'sw600dp-xhdpi-v13', - 'ldrtl-xhdpi-v17', - 'ldrtl-sw600dp-xhdpi-v17', - 'xhdpi-v21', - ), - 'xxhdpi': ( - 'xxhdpi-v4', - 'ldrtl-xxhdpi-v4', - 'sw600dp-xxhdpi-v13', - 'ldrtl-xxhdpi-v17', - 'ldrtl-sw600dp-xxhdpi-v17', - 'xxhdpi-v21', - ), - 'xxxhdpi': ( - 'xxxhdpi-v4', - 'ldrtl-xxxhdpi-v4', - 'sw600dp-xxxhdpi-v13', - 'ldrtl-xxxhdpi-v17', - 'ldrtl-sw600dp-xxxhdpi-v17', - 'xxxhdpi-v21', - ), - 'tvdpi': ( - 'tvdpi-v4', - 'sw600dp-tvdpi-v13', - 'ldrtl-sw600dp-tvdpi-v17', - ), -} - - -_PNG_TO_WEBP_ARGS = [ - '-mt', '-quiet', '-m', '6', '-q', '100', '-lossless', '-o'] - - -def _ParseArgs(args): - """Parses command line options. - - Returns: - An options object as from optparse.OptionsParser.parse_args() - """ - parser = optparse.OptionParser() - build_utils.AddDepfileOption(parser) - parser.add_option('--android-sdk-jar', - help='path to the Android SDK jar.') - parser.add_option('--aapt-path', - help='path to the Android aapt tool') - parser.add_option('--debuggable', - action='store_true', - help='Whether to add android:debuggable="true"') - parser.add_option('--android-manifest', help='AndroidManifest.xml path') - parser.add_option('--version-code', help='Version code for apk.') - parser.add_option('--version-name', help='Version name for apk.') - parser.add_option( - '--shared-resources', - action='store_true', - help='Make a resource package that can be loaded by a different' - 'application at runtime to access the package\'s resources.') - parser.add_option( - '--app-as-shared-lib', - action='store_true', - help='Make a resource package that can be loaded as shared library') - parser.add_option('--resource-zips', - default='[]', - help='zip files containing resources to be packaged') - parser.add_option('--asset-dir', - help='directories containing assets to be packaged') - parser.add_option('--no-compress', help='disables compression for the ' - 'given comma separated list of extensions') - parser.add_option( - '--create-density-splits', - action='store_true', - help='Enables density splits') - parser.add_option('--language-splits', - default='[]', - help='GN list of languages to create splits for') - parser.add_option('--locale-whitelist', - default='[]', - help='GN list of languages to include. All other language ' - 'configs will be stripped out. List may include ' - 'a combination of Android locales or Chrome locales.') - parser.add_option('--apk-path', - help='Path to output (partial) apk.') - parser.add_option('--exclude-xxxhdpi', action='store_true', - help='Do not include xxxhdpi drawables.') - parser.add_option('--xxxhdpi-whitelist', - default='[]', - help='GN list of globs that say which xxxhdpi images to ' - 'include even when --exclude-xxxhdpi is set.') - parser.add_option('--png-to-webp', action='store_true', - help='Convert png files to webp format.') - parser.add_option('--webp-binary', default='', - help='Path to the cwebp binary.') - parser.add_option('--support-zh-hk', action='store_true', - help='Tell aapt to support zh-rHK.') - - options, positional_args = parser.parse_args(args) - - if positional_args: - parser.error('No positional arguments should be given.') - - # Check that required options have been provided. - required_options = ('android_sdk_jar', 'aapt_path', 'android_manifest', - 'version_code', 'version_name', 'apk_path') - - build_utils.CheckOptions(options, parser, required=required_options) - - options.resource_zips = build_utils.ParseGnList(options.resource_zips) - options.language_splits = build_utils.ParseGnList(options.language_splits) - options.locale_whitelist = build_utils.ParseGnList(options.locale_whitelist) - options.xxxhdpi_whitelist = build_utils.ParseGnList(options.xxxhdpi_whitelist) - return options - - -def _ToAaptLocales(locale_whitelist, support_zh_hk): - """Converts the list of Chrome locales to aapt config locales.""" - ret = set() - for locale in locale_whitelist: - locale = _CHROME_TO_ANDROID_LOCALE_MAP.get(locale, locale) - if locale is None or ('-' in locale and '-r' not in locale): - raise Exception('_CHROME_TO_ANDROID_LOCALE_MAP needs updating.' - ' Found: %s' % locale) - ret.add(locale) - # Always keep non-regional fall-backs. - language = locale.split('-')[0] - ret.add(language) - - # We don't actually support zh-HK in Chrome on Android, but we mimic the - # native side behavior where we use zh-TW resources when the locale is set to - # zh-HK. See https://crbug.com/780847. - if support_zh_hk: - assert not any('HK' in l for l in locale_whitelist), ( - 'Remove special logic if zh-HK is now supported (crbug.com/780847).') - ret.add('zh-rHK') - return sorted(ret) - - -def MoveImagesToNonMdpiFolders(res_root): - """Move images from drawable-*-mdpi-* folders to drawable-* folders. - - Why? http://crbug.com/289843 - """ - for src_dir_name in os.listdir(res_root): - src_components = src_dir_name.split('-') - if src_components[0] != 'drawable' or 'mdpi' not in src_components: - continue - src_dir = os.path.join(res_root, src_dir_name) - if not os.path.isdir(src_dir): - continue - dst_components = [c for c in src_components if c != 'mdpi'] - assert dst_components != src_components - dst_dir_name = '-'.join(dst_components) - dst_dir = os.path.join(res_root, dst_dir_name) - build_utils.MakeDirectory(dst_dir) - for src_file_name in os.listdir(src_dir): - if not src_file_name.endswith('.png'): - continue - src_file = os.path.join(src_dir, src_file_name) - dst_file = os.path.join(dst_dir, src_file_name) - assert not os.path.lexists(dst_file) - shutil.move(src_file, dst_file) - - -def PackageArgsForExtractedZip(d): - """Returns the aapt args for an extracted resources zip. - - A resources zip either contains the resources for a single target or for - multiple targets. If it is multiple targets merged into one, the actual - resource directories will be contained in the subdirectories 0, 1, 2, ... - """ - subdirs = [os.path.join(d, s) for s in os.listdir(d)] - subdirs = [s for s in subdirs if os.path.isdir(s)] - is_multi = any(os.path.basename(s).isdigit() for s in subdirs) - if is_multi: - res_dirs = sorted(subdirs, key=lambda p : int(os.path.basename(p))) - else: - res_dirs = [d] - package_command = [] - for d in res_dirs: - MoveImagesToNonMdpiFolders(d) - package_command += ['-S', d] - return package_command - - -def _GenerateDensitySplitPaths(apk_path): - for density, config in DENSITY_SPLITS.iteritems(): - src_path = '%s_%s' % (apk_path, '_'.join(config)) - dst_path = '%s_%s' % (apk_path, density) - yield src_path, dst_path - - -def _GenerateLanguageSplitOutputPaths(apk_path, languages): - for lang in languages: - yield '%s_%s' % (apk_path, lang) - - -def RenameDensitySplits(apk_path): - """Renames all density splits to have shorter / predictable names.""" - for src_path, dst_path in _GenerateDensitySplitPaths(apk_path): - shutil.move(src_path, dst_path) - - -def CheckForMissedConfigs(apk_path, check_density, languages): - """Raises an exception if apk_path contains any unexpected configs.""" - triggers = [] - if check_density: - triggers.extend(re.compile('-%s' % density) for density in DENSITY_SPLITS) - if languages: - triggers.extend(re.compile(r'-%s\b' % lang) for lang in languages) - with zipfile.ZipFile(apk_path) as main_apk_zip: - for name in main_apk_zip.namelist(): - for trigger in triggers: - if trigger.search(name) and not 'mipmap-' in name: - raise Exception(('Found config in main apk that should have been ' + - 'put into a split: %s\nYou need to update ' + - 'package_resources.py to include this new ' + - 'config (trigger=%s)') % (name, trigger.pattern)) - - -def _ConstructMostAaptArgs(options): - package_command = [ - options.aapt_path, - 'package', - '--version-code', options.version_code, - '--version-name', options.version_name, - '-M', options.android_manifest, - '--no-crunch', - '-f', - '--auto-add-overlay', - '--no-version-vectors', - '-I', options.android_sdk_jar, - '-F', options.apk_path, - '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN, - ] - - if options.no_compress: - for ext in options.no_compress.split(','): - package_command += ['-0', ext] - - if options.shared_resources: - package_command.append('--shared-lib') - - if options.app_as_shared_lib: - package_command.append('--app-as-shared-lib') - - if options.asset_dir and os.path.exists(options.asset_dir): - package_command += ['-A', options.asset_dir] - - if options.create_density_splits: - for config in DENSITY_SPLITS.itervalues(): - package_command.extend(('--split', ','.join(config))) - - if options.language_splits: - for lang in options.language_splits: - package_command.extend(('--split', lang)) - - if options.debuggable: - package_command += ['--debug-mode'] - - if options.locale_whitelist: - aapt_locales = _ToAaptLocales( - options.locale_whitelist, options.support_zh_hk) - package_command += ['-c', ','.join(aapt_locales)] - - return package_command - - -def _ResourceNameFromPath(path): - return os.path.splitext(os.path.basename(path))[0] - - -def _CreateExtractPredicate(dep_zips, exclude_xxxhdpi, xxxhdpi_whitelist): - if not exclude_xxxhdpi: - # Do not extract dotfiles (e.g. ".gitkeep"). aapt ignores them anyways. - return lambda path: os.path.basename(path)[0] != '.' - - # Returns False only for xxxhdpi non-mipmap, non-whitelisted drawables. - naive_predicate = lambda path: ( - not re.search(r'[/-]xxxhdpi[/-]', path) or - re.search(r'[/-]mipmap[/-]', path) or - build_utils.MatchesGlob(path, xxxhdpi_whitelist)) - - # Build a set of all non-xxxhdpi drawables to ensure that we never exclude any - # xxxhdpi drawable that does not exist in other densities. - non_xxxhdpi_drawables = set() - for resource_zip_path in dep_zips: - with zipfile.ZipFile(resource_zip_path) as zip_file: - for path in zip_file.namelist(): - if re.search(r'[/-]drawable[/-]', path) and naive_predicate(path): - non_xxxhdpi_drawables.add(_ResourceNameFromPath(path)) - - return lambda path: (naive_predicate(path) or - _ResourceNameFromPath(path) not in non_xxxhdpi_drawables) - - -def _ConvertToWebP(webp_binary, png_files): - pool = multiprocessing.pool.ThreadPool(10) - def convert_image(png_path): - root = os.path.splitext(png_path)[0] - webp_path = root + '.webp' - args = [webp_binary, png_path] + _PNG_TO_WEBP_ARGS + [webp_path] - subprocess.check_call(args) - os.remove(png_path) - # Android requires pngs for 9-patch images. - pool.map(convert_image, [f for f in png_files if not f.endswith('.9.png')]) - pool.close() - pool.join() - - -def _OnStaleMd5(package_command, options): - with build_utils.TempDir() as temp_dir: - if options.resource_zips: - dep_zips = options.resource_zips - extract_predicate = _CreateExtractPredicate( - dep_zips, options.exclude_xxxhdpi, options.xxxhdpi_whitelist) - png_paths = [] - package_subdirs = [] - for z in dep_zips: - subdir = os.path.join(temp_dir, os.path.basename(z)) - if os.path.exists(subdir): - raise Exception('Resource zip name conflict: ' + os.path.basename(z)) - extracted_files = build_utils.ExtractAll( - z, path=subdir, predicate=extract_predicate) - if extracted_files: - package_subdirs.append(subdir) - png_paths.extend(f for f in extracted_files if f.endswith('.png')) - if png_paths and options.png_to_webp: - _ConvertToWebP(options.webp_binary, png_paths) - for subdir in package_subdirs: - package_command += PackageArgsForExtractedZip(subdir) - - build_utils.CheckOutput( - package_command, print_stdout=False, print_stderr=False) - - if options.create_density_splits or options.language_splits: - CheckForMissedConfigs(options.apk_path, options.create_density_splits, - options.language_splits) - - if options.create_density_splits: - RenameDensitySplits(options.apk_path) - - -def main(args): - args = build_utils.ExpandFileArgs(args) - options = _ParseArgs(args) - - package_command = _ConstructMostAaptArgs(options) - - output_paths = [options.apk_path] - - if options.create_density_splits: - for _, dst_path in _GenerateDensitySplitPaths(options.apk_path): - output_paths.append(dst_path) - output_paths.extend( - _GenerateLanguageSplitOutputPaths(options.apk_path, - options.language_splits)) - - input_paths = [options.android_manifest] + options.resource_zips - - input_strings = [options.exclude_xxxhdpi] + options.xxxhdpi_whitelist - input_strings.extend(package_command) - if options.png_to_webp: - # This is necessary to ensure conversion if the option is toggled. - input_strings.append('png_to_webp') - if options.support_zh_hk: - input_strings.append('support_zh_hk') - - # The md5_check.py doesn't count file path in md5 intentionally, - # in order to repackage resources when assets' name changed, we need - # to put assets into input_strings, as we know the assets path isn't - # changed among each build if there is no asset change. - if options.asset_dir and os.path.exists(options.asset_dir): - asset_paths = [] - for root, _, filenames in os.walk(options.asset_dir): - asset_paths.extend(os.path.join(root, f) for f in filenames) - input_paths.extend(asset_paths) - input_strings.extend(sorted(asset_paths)) - - build_utils.CallAndWriteDepfileIfStale( - lambda: _OnStaleMd5(package_command, options), - options, - input_paths=input_paths, - input_strings=input_strings, - output_paths=output_paths) - - -if __name__ == '__main__': - main(sys.argv[1:])
diff --git a/build/android/gyp/process_resources.py b/build/android/gyp/process_resources.py index 0df462e9..31a87de 100755 --- a/build/android/gyp/process_resources.py +++ b/build/android/gyp/process_resources.py
@@ -12,10 +12,12 @@ import codecs import collections +import multiprocessing.pool import optparse import os import re import shutil +import subprocess import sys import xml.etree.ElementTree import zipfile @@ -35,6 +37,73 @@ ('java_type', 'resource_type', 'name', 'value')) +# A variation of this lists also exists in: +# //base/android/java/src/org/chromium/base/LocaleUtils.java +_CHROME_TO_ANDROID_LOCALE_MAP = { + 'en-GB': 'en-rGB', + 'en-US': 'en-rUS', + 'es-419': 'es-rUS', + 'fil': 'tl', + 'he': 'iw', + 'id': 'in', + 'pt-PT': 'pt-rPT', + 'pt-BR': 'pt-rBR', + 'yi': 'ji', + 'zh-CN': 'zh-rCN', + 'zh-TW': 'zh-rTW', +} + +# List is generated from the chrome_apk.apk_intermediates.ap_ via: +# unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \ +# | uniq | grep -- -tvdpi- | cut -c10- +# and then manually sorted. +# Note that we can't just do a cross-product of dimensions because the filenames +# become too big and aapt fails to create the files. +# This leaves all default drawables (mdpi) in the main apk. Android gets upset +# though if any drawables are missing from the default drawables/ directory. +_DENSITY_SPLITS = { + 'hdpi': ( + 'hdpi-v4', # Order matters for output file names. + 'ldrtl-hdpi-v4', + 'sw600dp-hdpi-v13', + 'ldrtl-hdpi-v17', + 'ldrtl-sw600dp-hdpi-v17', + 'hdpi-v21', + ), + 'xhdpi': ( + 'xhdpi-v4', + 'ldrtl-xhdpi-v4', + 'sw600dp-xhdpi-v13', + 'ldrtl-xhdpi-v17', + 'ldrtl-sw600dp-xhdpi-v17', + 'xhdpi-v21', + ), + 'xxhdpi': ( + 'xxhdpi-v4', + 'ldrtl-xxhdpi-v4', + 'sw600dp-xxhdpi-v13', + 'ldrtl-xxhdpi-v17', + 'ldrtl-sw600dp-xxhdpi-v17', + 'xxhdpi-v21', + ), + 'xxxhdpi': ( + 'xxxhdpi-v4', + 'ldrtl-xxxhdpi-v4', + 'sw600dp-xxxhdpi-v13', + 'ldrtl-xxxhdpi-v17', + 'ldrtl-sw600dp-xxxhdpi-v17', + 'xxxhdpi-v21', + ), + 'tvdpi': ( + 'tvdpi-v4', + 'sw600dp-tvdpi-v13', + 'ldrtl-sw600dp-tvdpi-v17', + ), +} + + + + def _ParseArgs(args): """Parses command line options. @@ -63,6 +132,7 @@ help='Make a resource package that can be loaded as shared library.') parser.add_option('--resource-dirs', + default='[]', help='Directories containing resources of this target.') parser.add_option('--dependencies-res-zips', help='Resources from dependents.') @@ -97,21 +167,44 @@ help='For each additional package, the R.txt file should contain a ' 'list of resources to be included in the R.java file in the format ' 'generated by aapt') - parser.add_option( - '--include-all-resources', - action='store_true', - help='Include every resource ID in every generated R.java file ' - '(ignoring R.txt).') - parser.add_option( - '--all-resources-zip-out', - help='Path for output of all resources. This includes resources in ' - 'dependencies.') parser.add_option('--support-zh-hk', action='store_true', help='Use zh-rTW resources for zh-rHK.') parser.add_option('--stamp', help='File to touch on success') + parser.add_option('--debuggable', + action='store_true', + help='Whether to add android:debuggable="true"') + parser.add_option('--version-code', help='Version code for apk.') + parser.add_option('--version-name', help='Version name for apk.') + parser.add_option('--no-compress', help='disables compression for the ' + 'given comma separated list of extensions') + parser.add_option( + '--create-density-splits', + action='store_true', + help='Enables density splits') + parser.add_option('--language-splits', + default='[]', + help='GN list of languages to create splits for') + parser.add_option('--locale-whitelist', + default='[]', + help='GN list of languages to include. All other language ' + 'configs will be stripped out. List may include ' + 'a combination of Android locales or Chrome locales.') + parser.add_option('--apk-path', + help='Path to output (partial) apk.') + parser.add_option('--exclude-xxxhdpi', action='store_true', + help='Do not include xxxhdpi drawables.') + parser.add_option('--xxxhdpi-whitelist', + default='[]', + help='GN list of globs that say which xxxhdpi images to ' + 'include even when --exclude-xxxhdpi is set.') + parser.add_option('--png-to-webp', action='store_true', + help='Convert png files to webp format.') + parser.add_option('--webp-binary', default='', + help='Path to the cwebp binary.') + options, positional_args = parser.parse_args(args) if positional_args: @@ -123,7 +216,6 @@ 'aapt_path', 'android_manifest', 'dependencies_res_zips', - 'resource_dirs', ) build_utils.CheckOptions(options, parser, required=required_options) @@ -131,6 +223,10 @@ options.dependencies_res_zips = ( build_utils.ParseGnList(options.dependencies_res_zips)) + options.language_splits = build_utils.ParseGnList(options.language_splits) + options.locale_whitelist = build_utils.ParseGnList(options.locale_whitelist) + options.xxxhdpi_whitelist = build_utils.ParseGnList(options.xxxhdpi_whitelist) + # Don't use [] as default value since some script explicitly pass "". if options.extra_res_packages: options.extra_res_packages = ( @@ -147,7 +243,7 @@ return options -def CreateRJavaFiles(srcjar_dir, main_r_txt_file, packages, r_txt_files, +def _CreateRJavaFiles(srcjar_dir, main_r_txt_file, packages, r_txt_files, shared_resources, non_constant_id): assert len(packages) == len(r_txt_files), 'Need one R.txt file per package' @@ -155,6 +251,7 @@ # Contains the correct values for resources. all_resources = {} for entry in _ParseTextSymbolsFile(main_r_txt_file): + entry = entry._replace(value=_FixPackageIds(entry.value)) all_resources[(entry.resource_type, entry.name)] = entry # Map of package_name->resource_type->entry @@ -214,6 +311,18 @@ return ret +def _FixPackageIds(resource_value): + # Resource IDs for resources belonging to regular APKs have their first byte + # as 0x7f (package id). However with webview, since it is not a regular apk + # but used as a shared library, aapt is passed the --shared-resources flag + # which changes some of the package ids to 0x02 and 0x00. This function just + # normalises all package ids to 0x7f, which the generated code in R.java + # changes to the correct package id at runtime. + # resource_value is a string with either, a single value '0x12345678', or an + # array of values like '{ 0xfedcba98, 0x01234567, 0x56789abc }' + return re.sub(r'0x(?!01)\d\d', r'0x7f', resource_value) + + def _CreateRJavaFile(package, resources_by_type, shared_resources, non_constant_id): """Generates the contents of a R.java file.""" @@ -277,7 +386,7 @@ final=final) -def CrunchDirectory(aapt, input_dir, output_dir): +def _CrunchDirectory(aapt, input_dir, output_dir): """Crunches the images in input_dir and its subdirectories into output_dir. If an image is already optimized, crunching often increases image size. In @@ -288,8 +397,8 @@ '-C', output_dir, '-S', input_dir, '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] - build_utils.CheckOutput(aapt_cmd, stderr_filter=FilterCrunchStderr, - fail_func=DidCrunchFail) + build_utils.CheckOutput(aapt_cmd, stderr_filter=_FilterCrunchStderr, + fail_func=_DidCrunchFail) # Check for images whose size increased during crunching and replace them # with their originals (except for 9-patches, which must be crunched). @@ -307,7 +416,7 @@ shutil.copyfile(original, crunched) -def FilterCrunchStderr(stderr): +def _FilterCrunchStderr(stderr): """Filters out lines from aapt crunch's stderr that can safely be ignored.""" filtered_lines = [] for line in stderr.splitlines(True): @@ -320,7 +429,7 @@ return ''.join(filtered_lines) -def DidCrunchFail(returncode, stderr): +def _DidCrunchFail(returncode, stderr): """Determines whether aapt crunch failed from its return code and output. Because aapt's return code cannot be trusted, any output to stderr is @@ -329,7 +438,7 @@ return returncode != 0 or stderr -def ZipResources(resource_dirs, zip_path): +def _ZipResources(resource_dirs, zip_path): # Python zipfile does not provide a way to replace a file (it just writes # another file with the same name). So, first collect all the files to put # in the zip (with proper overriding), and then zip them. @@ -346,103 +455,325 @@ build_utils.DoZip(files_to_zip.iteritems(), zip_path) -def CombineZips(zip_files, output_path, support_zh_hk): - # When packaging resources, if the top-level directories in the zip file are - # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a - # resources directory. While some resources just clobber others (image files, - # etc), other resources (particularly .xml files) need to be more - # intelligently merged. That merging is left up to aapt. - def path_transform(name, src_zip): - return '%d/%s' % (zip_files.index(src_zip), name) - - # We don't currently support zh-HK on Chrome for Android, but on the - # native side we resolve zh-HK resources to zh-TW. This logic is - # duplicated here by just copying the zh-TW res folders to zh-HK. - # See https://crbug.com/780847. - with build_utils.TempDir() as temp_dir: - if support_zh_hk: - zip_files = _DuplicateZhResources(zip_files, temp_dir) - build_utils.MergeZips(output_path, zip_files, path_transform=path_transform) - - -def _DuplicateZhResources(zip_files, temp_dir): - new_zip_files = [] - for i, zip_path in enumerate(zip_files): - # We use zh-TW resources for zh-HK (if we have zh-TW resources). If no - # zh-TW resources exists (ex. api specific resources), then just use the - # original zip. - if not _ZipContains(zip_path, r'zh-r(HK|TW)'): - new_zip_files.append(zip_path) - continue - - resource_dir = os.path.join(temp_dir, str(i)) - new_zip_path = os.path.join(temp_dir, str(i) + '.zip') - - # Exclude existing zh-HK resources so that we don't mess up any resource - # IDs. This can happen if the type IDs in the existing resources don't - # align with ours (since they've already been generated at this point). - build_utils.ExtractAll( - zip_path, path=resource_dir, predicate=lambda x: not 'zh-rHK' in x) +def _DuplicateZhResources(resource_dirs): + for resource_dir in resource_dirs: + # We use zh-TW resources for zh-HK (if we have zh-TW resources). for path in build_utils.IterFiles(resource_dir): if 'zh-rTW' in path: hk_path = path.replace('zh-rTW', 'zh-rHK') - build_utils.Touch(hk_path) + build_utils.MakeDirectory(os.path.dirname(hk_path)) shutil.copyfile(path, hk_path) - build_utils.ZipDir(new_zip_path, resource_dir) - new_zip_files.append(new_zip_path) - return new_zip_files - - -def _ZipContains(path, pattern): - with zipfile.ZipFile(path, 'r') as z: - return any(re.search(pattern, f) for f in z.namelist()) - - def _ExtractPackageFromManifest(manifest_path): doc = xml.etree.ElementTree.parse(manifest_path) return doc.getroot().get('package') +def _ToAaptLocales(locale_whitelist, support_zh_hk): + """Converts the list of Chrome locales to aapt config locales.""" + ret = set() + for locale in locale_whitelist: + locale = _CHROME_TO_ANDROID_LOCALE_MAP.get(locale, locale) + if locale is None or ('-' in locale and '-r' not in locale): + raise Exception('_CHROME_TO_ANDROID_LOCALE_MAP needs updating.' + ' Found: %s' % locale) + ret.add(locale) + # Always keep non-regional fall-backs. + language = locale.split('-')[0] + ret.add(language) + + # We don't actually support zh-HK in Chrome on Android, but we mimic the + # native side behavior where we use zh-TW resources when the locale is set to + # zh-HK. See https://crbug.com/780847. + if support_zh_hk: + assert not any('HK' in l for l in locale_whitelist), ( + 'Remove special logic if zh-HK is now supported (crbug.com/780847).') + ret.add('zh-rHK') + return sorted(ret) + + +def _MoveImagesToNonMdpiFolders(res_root): + """Move images from drawable-*-mdpi-* folders to drawable-* folders. + + Why? http://crbug.com/289843 + """ + for src_dir_name in os.listdir(res_root): + src_components = src_dir_name.split('-') + if src_components[0] != 'drawable' or 'mdpi' not in src_components: + continue + src_dir = os.path.join(res_root, src_dir_name) + if not os.path.isdir(src_dir): + continue + dst_components = [c for c in src_components if c != 'mdpi'] + assert dst_components != src_components + dst_dir_name = '-'.join(dst_components) + dst_dir = os.path.join(res_root, dst_dir_name) + build_utils.MakeDirectory(dst_dir) + for src_file_name in os.listdir(src_dir): + if not src_file_name.endswith('.png'): + continue + src_file = os.path.join(src_dir, src_file_name) + dst_file = os.path.join(dst_dir, src_file_name) + assert not os.path.lexists(dst_file) + shutil.move(src_file, dst_file) + + +def _GenerateDensitySplitPaths(apk_path): + for density, config in _DENSITY_SPLITS.iteritems(): + src_path = '%s_%s' % (apk_path, '_'.join(config)) + dst_path = '%s_%s' % (apk_path, density) + yield src_path, dst_path + + +def _GenerateLanguageSplitOutputPaths(apk_path, languages): + for lang in languages: + yield '%s_%s' % (apk_path, lang) + + +def _RenameDensitySplits(apk_path): + """Renames all density splits to have shorter / predictable names.""" + for src_path, dst_path in _GenerateDensitySplitPaths(apk_path): + shutil.move(src_path, dst_path) + + +def _CheckForMissedConfigs(apk_path, check_density, languages): + """Raises an exception if apk_path contains any unexpected configs.""" + triggers = [] + if check_density: + triggers.extend(re.compile('-%s' % density) for density in _DENSITY_SPLITS) + if languages: + triggers.extend(re.compile(r'-%s\b' % lang) for lang in languages) + with zipfile.ZipFile(apk_path) as main_apk_zip: + for name in main_apk_zip.namelist(): + for trigger in triggers: + if trigger.search(name) and not 'mipmap-' in name: + raise Exception(('Found config in main apk that should have been ' + + 'put into a split: %s\nYou need to update ' + + 'package_resources.py to include this new ' + + 'config (trigger=%s)') % (name, trigger.pattern)) + + +def _CreatePackageApkArgs(options): + package_command = [ + '--version-code', options.version_code, + '--version-name', options.version_name, + '-f', + '-F', options.apk_path, + ] + + if options.proguard_file: + package_command += ['-G', options.proguard_file] + if options.proguard_file_main_dex: + package_command += ['-D', options.proguard_file_main_dex] + + if options.no_compress: + for ext in options.no_compress.split(','): + package_command += ['-0', ext] + + if options.shared_resources: + package_command.append('--shared-lib') + if options.app_as_shared_lib: + package_command.append('--app-as-shared-lib') + + if options.create_density_splits: + for config in _DENSITY_SPLITS.itervalues(): + package_command.extend(('--split', ','.join(config))) + + if options.language_splits: + for lang in options.language_splits: + package_command.extend(('--split', lang)) + + if options.debuggable: + package_command += ['--debug-mode'] + + if options.locale_whitelist: + aapt_locales = _ToAaptLocales( + options.locale_whitelist, options.support_zh_hk) + package_command += ['-c', ','.join(aapt_locales)] + + return package_command + + +def _ResourceNameFromPath(path): + return os.path.splitext(os.path.basename(path))[0] + + +def _CreateKeepPredicate(resource_dirs, exclude_xxxhdpi, xxxhdpi_whitelist): + if not exclude_xxxhdpi: + # Do not extract dotfiles (e.g. ".gitkeep"). aapt ignores them anyways. + return lambda path: os.path.basename(path)[0] != '.' + + # Returns False only for xxxhdpi non-mipmap, non-whitelisted drawables. + naive_predicate = lambda path: ( + not re.search(r'[/-]xxxhdpi[/-]', path) or + re.search(r'[/-]mipmap[/-]', path) or + build_utils.MatchesGlob(path, xxxhdpi_whitelist)) + + # Build a set of all non-xxxhdpi drawables to ensure that we never exclude any + # xxxhdpi drawable that does not exist in other densities. + non_xxxhdpi_drawables = set() + for resource_dir in resource_dirs: + for path in build_utils.IterFiles(resource_dir): + if re.search(r'[/-]drawable[/-]', path) and naive_predicate(path): + non_xxxhdpi_drawables.add(_ResourceNameFromPath(path)) + + return lambda path: (naive_predicate(path) or + _ResourceNameFromPath(path) not in non_xxxhdpi_drawables) + + +def _ConvertToWebP(webp_binary, png_files): + pool = multiprocessing.pool.ThreadPool(10) + def convert_image(png_path): + root = os.path.splitext(png_path)[0] + webp_path = root + '.webp' + args = [webp_binary, png_path, '-mt', '-quiet', '-m', '6', '-q', '100', + '-lossless', '-o', webp_path] + subprocess.check_call(args) + os.remove(png_path) + # Android requires pngs for 9-patch images. + pool.map(convert_image, [f for f in png_files if not f.endswith('.9.png')]) + pool.close() + pool.join() + + +def _PackageApk(options, package_command, dep_subdirs): + _DuplicateZhResources(dep_subdirs) + + package_command += _CreatePackageApkArgs(options) + + keep_predicate = _CreateKeepPredicate( + dep_subdirs, options.exclude_xxxhdpi, options.xxxhdpi_whitelist) + png_paths = [] + for directory in dep_subdirs: + for f in build_utils.IterFiles(directory): + if not keep_predicate(f): + os.remove(f) + elif f.endswith('.png'): + png_paths.append(f) + if png_paths and options.png_to_webp: + _ConvertToWebP(options.webp_binary, png_paths) + for directory in dep_subdirs: + _MoveImagesToNonMdpiFolders(directory) + + # Creates a .zip with AndroidManifest.xml, resources.arsc, res/* + # Also creates R.txt + build_utils.CheckOutput( + package_command, print_stdout=False, print_stderr=False) + + if options.create_density_splits or options.language_splits: + _CheckForMissedConfigs(options.apk_path, options.create_density_splits, + options.language_splits) + + if options.create_density_splits: + _RenameDensitySplits(options.apk_path) + + +def _PackageLibrary(options, package_command, temp_dir): + v14_dir = os.path.join(temp_dir, 'v14') + build_utils.MakeDirectory(v14_dir) + + input_resource_dirs = options.resource_dirs + + for d in input_resource_dirs: + package_command += ['-S', d] + + if not options.v14_skip: + for resource_dir in input_resource_dirs: + generate_v14_compatible_resources.GenerateV14Resources( + resource_dir, + v14_dir) + + # This is the list of directories with resources to put in the final .zip + # file. The order of these is important so that crunched/v14 resources + # override the normal ones. + zip_resource_dirs = input_resource_dirs + [v14_dir] + + base_crunch_dir = os.path.join(temp_dir, 'crunch') + # Crunch image resources. This shrinks png files and is necessary for + # 9-patch images to display correctly. 'aapt crunch' accepts only a single + # directory at a time and deletes everything in the output directory. + for idx, input_dir in enumerate(input_resource_dirs): + crunch_dir = os.path.join(base_crunch_dir, str(idx)) + build_utils.MakeDirectory(crunch_dir) + zip_resource_dirs.append(crunch_dir) + _CrunchDirectory(options.aapt_path, input_dir, crunch_dir) + + if options.resource_zip_out: + _ZipResources(zip_resource_dirs, options.resource_zip_out) + + # Only creates an R.txt + build_utils.CheckOutput( + package_command, print_stdout=False, print_stderr=False) + + +def _CreateRTxtAndSrcJar(options, r_txt_path, srcjar_dir): + # When an empty res/ directory is passed, aapt does not write an R.txt. + if not os.path.exists(r_txt_path): + build_utils.Touch(r_txt_path) + + if options.r_text_in: + r_txt_path = options.r_text_in + + packages = list(options.extra_res_packages) + r_txt_files = list(options.extra_r_text_files) + + cur_package = options.custom_package + if not options.custom_package: + cur_package = _ExtractPackageFromManifest(options.android_manifest) + + # Don't create a .java file for the current resource target when: + # - no package name was provided (either by manifest or build rules), + # - there was already a dependent android_resources() with the same + # package (occurs mostly when an apk target and resources target share + # an AndroidManifest.xml) + if cur_package != 'org.dummy' and cur_package not in packages: + packages.append(cur_package) + r_txt_files.append(r_txt_path) + + if packages: + shared_resources = options.shared_resources or options.app_as_shared_lib + _CreateRJavaFiles(srcjar_dir, r_txt_path, packages, r_txt_files, + shared_resources, options.non_constant_id) + + if options.srcjar_out: + build_utils.ZipDir(options.srcjar_out, srcjar_dir) + + if options.r_text_out: + shutil.copyfile(r_txt_path, options.r_text_out) + + +def _ExtractDeps(dep_zips, deps_dir): + dep_subdirs = [] + for z in dep_zips: + subdir = os.path.join(deps_dir, os.path.basename(z)) + if os.path.exists(subdir): + raise Exception('Resource zip name conflict: ' + os.path.basename(z)) + build_utils.ExtractAll(z, path=subdir) + dep_subdirs.append(subdir) + return dep_subdirs + + def _OnStaleMd5(options): - aapt = options.aapt_path with build_utils.TempDir() as temp_dir: deps_dir = os.path.join(temp_dir, 'deps') build_utils.MakeDirectory(deps_dir) - v14_dir = os.path.join(temp_dir, 'v14') - build_utils.MakeDirectory(v14_dir) - gen_dir = os.path.join(temp_dir, 'gen') build_utils.MakeDirectory(gen_dir) r_txt_path = os.path.join(gen_dir, 'R.txt') srcjar_dir = os.path.join(temp_dir, 'java') - input_resource_dirs = options.resource_dirs - - if not options.v14_skip: - for resource_dir in input_resource_dirs: - generate_v14_compatible_resources.GenerateV14Resources( - resource_dir, - v14_dir) - - dep_zips = options.dependencies_res_zips - dep_subdirs = [] - for z in dep_zips: - subdir = os.path.join(deps_dir, os.path.basename(z)) - if os.path.exists(subdir): - raise Exception('Resource zip name conflict: ' + os.path.basename(z)) - build_utils.ExtractAll(z, path=subdir) - dep_subdirs.append(subdir) + dep_subdirs = _ExtractDeps(options.dependencies_res_zips, deps_dir) # Generate R.java. This R.java contains non-final constants and is used only # while compiling the library jar (e.g. chromium_content.jar). When building # an apk, a new R.java file with the correct resource -> ID mappings will be # generated by merging the resources from all libraries and the main apk # project. - package_command = [aapt, + package_command = [options.aapt_path, 'package', '-m', '-M', options.android_manifest, + '--no-crunch', '--auto-add-overlay', '--no-version-vectors', '-I', options.android_sdk_jar, @@ -450,31 +781,6 @@ '-J', gen_dir, # Required for R.txt generation. '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] - # aapt supports only the "--include-all-resources" mode, where each R.java - # file ends up with all symbols, rather than only those that it had at the - # time it was originally generated. This subtle difference makes no - # difference when compiling, but can lead to increased unused symbols in the - # resulting R.class files. - # TODO(agrieve): See if proguard makes this difference actually translate - # into a size difference. If not, we can delete all of our custom R.java - # template code above (and make include_all_resources the default). - if options.include_all_resources: - srcjar_dir = gen_dir - if options.extra_res_packages: - colon_separated = ':'.join(options.extra_res_packages) - package_command += ['--extra-packages', colon_separated] - if options.non_constant_id: - package_command.append('--non-constant-id') - if options.custom_package: - package_command += ['--custom-package', options.custom_package] - if options.shared_resources: - package_command.append('--shared-lib') - if options.app_as_shared_lib: - package_command.append('--app-as-shared-lib') - - for d in input_resource_dirs: - package_command += ['-S', d] - # Adding all dependencies as sources is necessary for @type/foo references # to symbols within dependencies to resolve. However, it has the side-effect # that all Java symbols from dependencies are copied into the new R.java. @@ -484,74 +790,12 @@ for d in dep_subdirs: package_command += ['-S', d] - if options.proguard_file: - package_command += ['-G', options.proguard_file] - if options.proguard_file_main_dex: - package_command += ['-D', options.proguard_file_main_dex] - build_utils.CheckOutput(package_command, print_stderr=False) + if options.apk_path: + _PackageApk(options, package_command, dep_subdirs) + else: + _PackageLibrary(options, package_command, temp_dir) - # When an empty res/ directory is passed, aapt does not write an R.txt. - if not os.path.exists(r_txt_path): - build_utils.Touch(r_txt_path) - - if not options.include_all_resources: - # --include-all-resources can only be specified for generating final R - # classes for APK. It makes no sense for APK to have pre-generated R.txt - # though, because aapt-generated already lists all available resources. - if options.r_text_in: - r_txt_path = options.r_text_in - - packages = list(options.extra_res_packages) - r_txt_files = list(options.extra_r_text_files) - - cur_package = options.custom_package - if not options.custom_package: - cur_package = _ExtractPackageFromManifest(options.android_manifest) - - # Don't create a .java file for the current resource target when: - # - no package name was provided (either by manifest or build rules), - # - there was already a dependent android_resources() with the same - # package (occurs mostly when an apk target and resources target share - # an AndroidManifest.xml) - if cur_package != 'org.dummy' and cur_package not in packages: - packages.append(cur_package) - r_txt_files.append(r_txt_path) - - if packages: - shared_resources = options.shared_resources or options.app_as_shared_lib - CreateRJavaFiles(srcjar_dir, r_txt_path, packages, r_txt_files, - shared_resources, options.non_constant_id) - - # This is the list of directories with resources to put in the final .zip - # file. The order of these is important so that crunched/v14 resources - # override the normal ones. - zip_resource_dirs = input_resource_dirs + [v14_dir] - - base_crunch_dir = os.path.join(temp_dir, 'crunch') - - # Crunch image resources. This shrinks png files and is necessary for - # 9-patch images to display correctly. 'aapt crunch' accepts only a single - # directory at a time and deletes everything in the output directory. - for idx, input_dir in enumerate(input_resource_dirs): - crunch_dir = os.path.join(base_crunch_dir, str(idx)) - build_utils.MakeDirectory(crunch_dir) - zip_resource_dirs.append(crunch_dir) - CrunchDirectory(aapt, input_dir, crunch_dir) - - if options.resource_zip_out: - ZipResources(zip_resource_dirs, options.resource_zip_out) - - if options.all_resources_zip_out: - all_zips = [options.resource_zip_out] if options.resource_zip_out else [] - all_zips += dep_zips - CombineZips(all_zips, - options.all_resources_zip_out, options.support_zh_hk) - - if options.srcjar_out: - build_utils.ZipDir(options.srcjar_out, srcjar_dir) - - if options.r_text_out: - shutil.copyfile(r_txt_path, options.r_text_out) + _CreateRTxtAndSrcJar(options, r_txt_path, srcjar_dir) def main(args): @@ -561,8 +805,8 @@ # Order of these must match order specified in GN so that the correct one # appears first in the depfile. possible_output_paths = [ + options.apk_path, options.resource_zip_out, - options.all_resources_zip_out, options.r_text_out, options.srcjar_out, options.proguard_file, @@ -570,18 +814,30 @@ ] output_paths = [x for x in possible_output_paths if x] + if options.apk_path and options.create_density_splits: + for _, dst_path in _GenerateDensitySplitPaths(options.apk_path): + output_paths.append(dst_path) + if options.apk_path and options.language_splits: + output_paths.extend( + _GenerateLanguageSplitOutputPaths(options.apk_path, + options.language_splits)) + # List python deps in input_strings rather than input_paths since the contents # of them does not change what gets written to the depsfile. input_strings = options.extra_res_packages + [ options.app_as_shared_lib, options.custom_package, - options.include_all_resources, options.non_constant_id, options.shared_resources, options.v14_skip, + options.exclude_xxxhdpi, + options.xxxhdpi_whitelist, + str(options.png_to_webp), + str(options.support_zh_hk), ] - if options.support_zh_hk: - input_strings.append('support_zh_hk') + + if options.apk_path: + input_strings.extend(_CreatePackageApkArgs(options)) input_paths = [ options.aapt_path,
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni index cd2941d..daa081f 100644 --- a/build/config/android/internal_rules.gni +++ b/build/config/android/internal_rules.gni
@@ -1383,7 +1383,19 @@ # Runs process_resources.py template("process_resources") { - action(target_name) { + _process_resources_target_name = target_name + if (defined(invoker.output)) { + _post_process = defined(invoker.post_process_script) + _packaged_resources_path = invoker.output + if (_post_process) { + _process_resources_target_name = "${target_name}__intermediate" + _packaged_resources_path = + get_path_info(_packaged_resources_path, "dir") + "/" + + get_path_info(_packaged_resources_path, "name") + + ".intermediate.ap_" + } + } + action(_process_resources_target_name) { set_sources_assignment_filter([]) forward_variables_from(invoker, [ @@ -1426,6 +1438,7 @@ } inputs = [ + android_default_aapt_path, invoker.build_config, invoker.android_manifest, _android_aapt_path, @@ -1445,12 +1458,41 @@ rebase_path(_android_aapt_path, root_build_dir), "--android-manifest", rebase_path(invoker.android_manifest, root_build_dir), - "--resource-dirs=$_rebased_all_resource_dirs", "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)", "--extra-res-packages=@FileArg($_rebased_build_config:resources:extra_package_names)", "--extra-r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)", ] + if (_rebased_all_resource_dirs != []) { + args += [ "--resource-dirs=$_rebased_all_resource_dirs" ] + } + + if (defined(invoker.version_code)) { + args += [ + "--version-code", + invoker.version_code, + ] + } + if (defined(invoker.version_name)) { + args += [ + "--version-name", + invoker.version_name, + ] + } + if (defined(_packaged_resources_path)) { + outputs += [ _packaged_resources_path ] + args += [ + "--apk-path", + rebase_path(_packaged_resources_path, root_build_dir), + ] + } + + # Useful to have android:debuggable in the manifest even for Release + # builds. Just omit it for officai + if (debuggable_apks) { + args += [ "--debuggable" ] + } + if (defined(invoker.zip_path)) { outputs += [ invoker.zip_path ] args += [ @@ -1459,15 +1501,6 @@ ] } - if (defined(invoker.all_resources_zip_path)) { - _all_resources_zip = invoker.all_resources_zip_path - outputs += [ _all_resources_zip ] - args += [ - "--all-resources-zip-out", - rebase_path(_all_resources_zip, root_build_dir), - ] - } - if (defined(invoker.r_text_out_path)) { outputs += [ invoker.r_text_out_path ] args += [ @@ -1517,11 +1550,6 @@ args += [ "--app-as-shared-lib" ] } - if (defined(invoker.include_all_resources) && - invoker.include_all_resources) { - args += [ "--include-all-resources" ] - } - if (defined(invoker.proguard_file)) { outputs += [ invoker.proguard_file ] args += [ @@ -1538,112 +1566,6 @@ ] } - if (defined(invoker.support_zh_hk) && invoker.support_zh_hk) { - args += [ "--support-zh-hk" ] - } - - if (defined(invoker.args)) { - args += invoker.args - } - } - } - - # Runs aapt to create an .ap_ file, which is a zip file containing - # compiled xml and a resources.arsc file. - # - # Required Variables: - # output: Path to .ap_ to create. - # android_manifest: The AndroidManifest.xml for the apk. - # version_code: The verison code to use. - # version_name: The verison name to use. - # Optional Variables: - # aapt_locale_whitelist: If set, all locales not in this list will be - # stripped from resources.arsc. - # alternative_android_sdk_jar: An alternative android sdk jar. - # app_as_shared_lib: Enables --app-as-shared-lib. - # exclude_xxxhdpi: Causes all drawable-xxxhdpi images to be excluded - # (mipmaps are still included). - # png_to_webp: If true, pngs (with the exception of 9-patch) are - # converted to webp. - # post_process_script: Script to call to post-process the .ap_. - # resources_zip: Resource .zip file created by process_resources to package. - # shared_resources: Enables --shared-lib. - # density_splits: A list of densities to create apk splits for. - # language_splits: A list of language codes to create apk splits for. - # xxxhdpi_whitelist: A list of globs used when exclude_xxxhdpi=true. Files - # that match this whitelist will still be included. - template("package_resources") { - _post_process = defined(invoker.post_process_script) - _package_resources_target_name = target_name - _packaged_resources_path = invoker.output - if (_post_process) { - _package_resources_target_name = "${target_name}__intermediate" - _packaged_resources_path = - get_path_info(_packaged_resources_path, "dir") + "/" + - get_path_info(_packaged_resources_path, "name") + ".intermediate.ap_" - } - - action(_package_resources_target_name) { - forward_variables_from(invoker, - [ - "deps", - "testonly", - "visibility", - ]) - - script = "//build/android/gyp/package_resources.py" - depfile = "${target_gen_dir}/${target_name}.d" - outputs = [ - _packaged_resources_path, - ] - - _android_sdk_jar = android_sdk_jar - if (defined(invoker.alternative_android_sdk_jar)) { - _android_sdk_jar = invoker.alternative_android_sdk_jar - } - - inputs = [ - android_default_aapt_path, - _android_sdk_jar, - invoker.android_manifest, - ] - - args = [ - "--depfile", - rebase_path(depfile, root_build_dir), - "--android-sdk-jar", - rebase_path(_android_sdk_jar, root_build_dir), - "--aapt-path", - rebase_path(android_default_aapt_path, root_build_dir), - "--android-manifest", - rebase_path(invoker.android_manifest, root_build_dir), - "--version-code", - invoker.version_code, - "--version-name", - invoker.version_name, - "--apk-path", - rebase_path(_packaged_resources_path, root_build_dir), - ] - - # Useful to have android:debuggable in the manifest even for Release - # builds. Just omit it for officai - if (debuggable_apks) { - args += [ "--debuggable" ] - } - - if (defined(invoker.resources_zip)) { - inputs += [ invoker.resources_zip ] - args += [ - "--resource-zips", - rebase_path(invoker.resources_zip, root_build_dir), - ] - } - if (defined(invoker.shared_resources) && invoker.shared_resources) { - args += [ "--shared-resources" ] - } - if (defined(invoker.app_as_shared_lib) && invoker.app_as_shared_lib) { - args += [ "--app-as-shared-lib" ] - } if (defined(invoker.density_splits) && invoker.density_splits != []) { args += [ "--create-density-splits" ] foreach(_density, invoker.density_splits) { @@ -1675,12 +1597,17 @@ args += [ "--xxxhdpi-whitelist=${invoker.xxxhdpi_whitelist}" ] } } + if (defined(invoker.support_zh_hk) && invoker.support_zh_hk) { args += [ "--support-zh-hk" ] } + + if (defined(invoker.args)) { + args += invoker.args + } } - if (_post_process) { + if (defined(_packaged_resources_path) && _post_process) { action(target_name) { depfile = "${target_gen_dir}/${target_name}.d" script = invoker.post_process_script @@ -1699,7 +1626,7 @@ invoker.output, ] public_deps = [ - ":${_package_resources_target_name}", + ":${_process_resources_target_name}", ] } }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni index 62d500f..753d974 100644 --- a/build/config/android/rules.gni +++ b/build/config/android/rules.gni
@@ -1740,8 +1740,6 @@ # (optional). # apk_under_test: For an instrumentation test apk, this is the target of the # tested apk. - # include_all_resources - If true include all resource IDs in all generated - # R.java files. # testonly: Marks this target as "test-only". # write_asset_list: Adds an extra file to the assets, which contains a list of # all other asset files. @@ -1794,7 +1792,6 @@ # JUnit tests use resource zip files. These must not be put in gen/ # directory or they will not be available to tester bots. - _all_resources_zip_path = "$_base_path.resources.all.zip" _jar_path = "$_base_path.jar" _lib_dex_path = "$_base_path.dex.jar" _rebased_lib_dex_path = rebase_path(_lib_dex_path, root_build_dir) @@ -2113,19 +2110,29 @@ [ "alternative_android_sdk_jar", "app_as_shared_lib", - "include_all_resources", "shared_resources", "support_zh_hk", + "aapt_locale_whitelist", + "exclude_xxxhdpi", + "png_to_webp", + "xxxhdpi_whitelist", ]) + android_manifest = _android_manifest + version_code = _version_code + version_name = _version_name + if (defined(invoker.post_process_package_resources_script)) { + post_process_script = invoker.post_process_package_resources_script + } srcjar_path = "${target_gen_dir}/${target_name}.srcjar" r_text_out_path = "${target_gen_dir}/${target_name}_R.txt" - android_manifest = _android_manifest - all_resources_zip_path = _all_resources_zip_path generate_constant_ids = true proguard_file = _generated_proguard_config if (_enable_multidex) { proguard_file_main_dex = _generated_proguard_main_dex_config } + output = _packaged_resources_path + density_splits = _density_splits + language_splits = _language_splits build_config = _build_config deps = _deps + [ @@ -2134,34 +2141,6 @@ ] } _srcjar_deps += [ ":$_process_resources_target" ] - _package_resources_target = "${target_name}__package_resources" - package_resources(_package_resources_target) { - forward_variables_from(invoker, - [ - "aapt_locale_whitelist", - "alternative_android_sdk_jar", - "app_as_shared_lib", - "exclude_xxxhdpi", - "png_to_webp", - "shared_resources", - "support_zh_hk", - "xxxhdpi_whitelist", - ]) - deps = _deps + [ - ":$_android_manifest_target", - ":$_process_resources_target", - ] - android_manifest = _android_manifest - version_code = _version_code - version_name = _version_name - if (defined(invoker.post_process_package_resources_script)) { - post_process_script = invoker.post_process_package_resources_script - } - resources_zip = _all_resources_zip_path - output = _packaged_resources_path - density_splits = _density_splits - language_splits = _language_splits - } if (_native_libs_deps != []) { _enable_chromium_linker_tests = false @@ -2520,7 +2499,7 @@ incremental_deps = _deps + [ ":$_android_manifest_target", ":$_build_config_target", - ":$_package_resources_target", + ":$_process_resources_target", ] # This target generates the input file _all_resources_zip_path. @@ -2528,7 +2507,7 @@ ":$_android_manifest_target", ":$_build_config_target", ":$_final_dex_target_name", - ":$_package_resources_target", + ":$_process_resources_target", ] if ((_native_libs_deps != [] || @@ -2570,7 +2549,7 @@ ] } - package_resources("${_apk_rule}__process_resources") { + process_resources("${_apk_rule}__process_resources") { deps = [ ":${_apk_rule}__generate_manifest", ]
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc index cc496d38..5edbd0b 100644 --- a/cc/paint/paint_op_buffer.cc +++ b/cc/paint/paint_op_buffer.cc
@@ -347,6 +347,7 @@ serialized_flags = &op->flags; helper.Write(*serialized_flags); helper.Write(op->image, options.image_provider); + helper.AlignMemory(alignof(SkScalar)); helper.Write(op->left); helper.Write(op->top); return helper.size(); @@ -393,6 +394,7 @@ if (!serialized_flags) serialized_flags = &op->flags; helper.Write(*serialized_flags); + helper.AlignMemory(alignof(SkScalar)); helper.Write(op->x0); helper.Write(op->y0); helper.Write(op->x1); @@ -476,6 +478,7 @@ if (!serialized_flags) serialized_flags = &op->flags; helper.Write(*serialized_flags); + helper.AlignMemory(alignof(SkScalar)); helper.Write(op->x); helper.Write(op->y); helper.Write(op->blob); @@ -694,6 +697,7 @@ PaintOpReader helper(input, input_size); helper.Read(&op->flags); helper.Read(&op->image); + helper.AlignMemory(alignof(SkScalar)); helper.Read(&op->left); helper.Read(&op->top); if (!helper.valid() || !op->IsValid()) { @@ -755,6 +759,7 @@ PaintOpReader helper(input, input_size); helper.Read(&op->flags); + helper.AlignMemory(alignof(SkScalar)); helper.Read(&op->x0); helper.Read(&op->y0); helper.Read(&op->x1); @@ -863,6 +868,7 @@ PaintOpReader helper(input, input_size); helper.Read(&op->flags); + helper.AlignMemory(alignof(SkScalar)); helper.Read(&op->x); helper.Read(&op->y); helper.Read(&op->blob); @@ -1669,8 +1675,8 @@ if (written < 4) return 0u; - size_t aligned_written = - MathUtil::UncheckedRoundUp(written, PaintOpBuffer::PaintOpAlign); + size_t aligned_written = ((written + PaintOpBuffer::PaintOpAlign - 1) & + ~(PaintOpBuffer::PaintOpAlign - 1)); if (aligned_written >= kMaxSkip) return 0u; if (aligned_written > size)
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc index b7fa08a..1aa32a7 100644 --- a/cc/paint/paint_op_reader.cc +++ b/cc/paint/paint_op_reader.cc
@@ -94,8 +94,6 @@ void PaintOpReader::ReadSimple(T* val) { static_assert(base::is_trivially_copyable<T>::value, "Not trivially copyable"); - if (!AlignMemory(alignof(T))) - SetInvalid(); if (remaining_bytes_ < sizeof(T)) SetInvalid(); if (!valid_) @@ -117,8 +115,6 @@ ReadSimple(&bytes); if (remaining_bytes_ < bytes) SetInvalid(); - if (!SkIsAlign4(reinterpret_cast<uintptr_t>(memory_))) - SetInvalid(); if (!valid_) return; if (bytes == 0) @@ -127,6 +123,7 @@ // This is assumed safe from TOCTOU violations as the flattenable // deserializing function uses an SkReadBuffer which reads each piece of // memory once much like PaintOpReader does. + DCHECK(SkIsAlign4(reinterpret_cast<uintptr_t>(memory_))); val->reset(static_cast<T*>(SkValidatingDeserializeFlattenable( const_cast<const char*>(memory_), bytes, T::GetFlattenableType()))); if (!val) @@ -232,8 +229,11 @@ // Flattenables must be read at 4-byte boundary, which should be the case // here. ReadFlattenable(&flags->path_effect_); + AlignMemory(4); ReadFlattenable(&flags->mask_filter_); + AlignMemory(4); ReadFlattenable(&flags->color_filter_); + AlignMemory(4); ReadFlattenable(&flags->draw_looper_); Read(&flags->shader_); @@ -490,7 +490,7 @@ *color_type = static_cast<SkColorType>(raw_color_type); } -bool PaintOpReader::AlignMemory(size_t alignment) { +void PaintOpReader::AlignMemory(size_t alignment) { // Due to the math below, alignment must be a power of two. DCHECK_GT(alignment, 0u); DCHECK_EQ(alignment & (alignment - 1), 0u); @@ -502,11 +502,10 @@ // however, since it can be slow. size_t padding = ((memory + alignment - 1) & ~(alignment - 1)) - memory; if (padding > remaining_bytes_) - return false; + valid_ = false; memory_ += padding; remaining_bytes_ -= padding; - return true; } inline void PaintOpReader::SetInvalid() {
diff --git a/cc/paint/paint_op_reader.h b/cc/paint/paint_op_reader.h index 075488b5..2594069 100644 --- a/cc/paint/paint_op_reader.h +++ b/cc/paint/paint_op_reader.h
@@ -82,6 +82,9 @@ // would exceed the available budfer. const volatile void* ExtractReadableMemory(size_t bytes); + // Aligns the memory to the given alignment. + void AlignMemory(size_t alignment); + private: template <typename T> void ReadSimple(T* val); @@ -95,10 +98,6 @@ void SetInvalid(); - // Attempts to align the memory to the given alignment. Returns false if there - // is unsufficient bytes remaining to do this padding. - bool AlignMemory(size_t alignment); - const volatile char* memory_ = nullptr; size_t remaining_bytes_ = 0u; bool valid_ = true;
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc index bd2f159..f5b1c1a 100644 --- a/cc/paint/paint_op_writer.cc +++ b/cc/paint/paint_op_writer.cc
@@ -23,8 +23,6 @@ template <typename T> void PaintOpWriter::WriteSimple(const T& val) { static_assert(base::is_trivially_copyable<T>::value, ""); - if (!AlignMemory(alignof(T))) - valid_ = false; if (remaining_bytes_ < sizeof(T)) valid_ = false; if (!valid_) @@ -37,13 +35,14 @@ } void PaintOpWriter::WriteFlattenable(const SkFlattenable* val) { + DCHECK(SkIsAlign4(reinterpret_cast<uintptr_t>(memory_))) + << "Flattenable must start writing at 4 byte alignment."; + if (!val) { WriteSize(static_cast<size_t>(0u)); return; } - DCHECK(SkIsAlign4(reinterpret_cast<uintptr_t>(memory_))) - << "Flattenable must start writing at 4 byte alignment."; // TODO(enne): change skia API to make this a const parameter. sk_sp<SkData> data( SkValidatingSerializeFlattenable(const_cast<SkFlattenable*>(val))); @@ -114,8 +113,11 @@ // Flattenables must be written starting at a 4 byte boundary, which should be // the case here. WriteFlattenable(flags.path_effect_.get()); + AlignMemory(4); WriteFlattenable(flags.mask_filter_.get()); + AlignMemory(4); WriteFlattenable(flags.color_filter_.get()); + AlignMemory(4); WriteFlattenable(flags.draw_looper_.get()); Write(flags.shader_.get()); @@ -260,7 +262,7 @@ WriteData(bytes, input); } -bool PaintOpWriter::AlignMemory(size_t alignment) { +void PaintOpWriter::AlignMemory(size_t alignment) { // Due to the math below, alignment must be a power of two. DCHECK_GT(alignment, 0u); DCHECK_EQ(alignment & (alignment - 1), 0u); @@ -272,11 +274,10 @@ // however, since it can be slow. size_t padding = ((memory + alignment - 1) & ~(alignment - 1)) - memory; if (padding > remaining_bytes_) - return false; + valid_ = false; memory_ += padding; remaining_bytes_ -= padding; - return true; } } // namespace cc
diff --git a/cc/paint/paint_op_writer.h b/cc/paint/paint_op_writer.h index a2f8a397..ad8eda1 100644 --- a/cc/paint/paint_op_writer.h +++ b/cc/paint/paint_op_writer.h
@@ -61,6 +61,9 @@ } void Write(bool data) { Write(static_cast<uint8_t>(data)); } + // Aligns the memory to the given alignment. + void AlignMemory(size_t alignment); + private: template <typename T> void WriteSimple(const T& val); @@ -71,10 +74,6 @@ static void TypefaceCataloger(SkTypeface* typeface, void* ctx); - // Attempts to align the memory to the given alignment. Returns false if there - // is unsufficient bytes remaining to do this padding. - bool AlignMemory(size_t alignment); - char* memory_ = nullptr; size_t size_ = 0u; size_t remaining_bytes_ = 0u;
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc index c1c69a9..cd901be4 100644 --- a/cc/scheduler/scheduler.cc +++ b/cc/scheduler/scheduler.cc
@@ -122,7 +122,7 @@ } void Scheduler::SetNeedsPrepareTiles() { - DCHECK(!IsInsideAction(SchedulerStateMachine::ACTION_PREPARE_TILES)); + DCHECK(!IsInsideAction(SchedulerStateMachine::Action::PREPARE_TILES)); state_machine_.SetNeedsPrepareTiles(); ProcessScheduledActions(); } @@ -134,7 +134,7 @@ // There is no need to call ProcessScheduledActions here because // submitting a CompositorFrame should not trigger any new actions. if (!inside_process_scheduled_actions_) { - DCHECK_EQ(state_machine_.NextAction(), SchedulerStateMachine::ACTION_NONE); + DCHECK_EQ(state_machine_.NextAction(), SchedulerStateMachine::Action::NONE); } } @@ -228,7 +228,7 @@ void Scheduler::SetupNextBeginFrameIfNeeded() { if (state_machine_.begin_impl_frame_state() != - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE) { + SchedulerStateMachine::BeginImplFrameState::IDLE) { return; } @@ -319,7 +319,7 @@ void Scheduler::OnDrawForLayerTreeFrameSink(bool resourceless_software_draw) { DCHECK(settings_.using_synchronous_renderer_compositor); DCHECK_EQ(state_machine_.begin_impl_frame_state(), - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginImplFrameState::IDLE); DCHECK(begin_impl_frame_deadline_task_.IsCancelled()); state_machine_.SetResourcelessSoftwareDraw(resourceless_software_draw); @@ -355,7 +355,7 @@ // Run the previous deadline if any. if (state_machine_.begin_impl_frame_state() == - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) { + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME) { OnBeginImplFrameDeadline(); // We may not need begin frames any longer. if (!observing_begin_frame_source_) { @@ -365,7 +365,7 @@ } } DCHECK_EQ(state_machine_.begin_impl_frame_state(), - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginImplFrameState::IDLE); bool main_thread_is_in_high_latency_mode = state_machine_.main_thread_missed_last_deadline(); @@ -376,7 +376,7 @@ "MainThreadLatency", main_thread_is_in_high_latency_mode); DCHECK_EQ(state_machine_.begin_impl_frame_state(), - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginImplFrameState::IDLE); adjusted_args.deadline -= compositor_timing_history_->DrawDurationEstimate(); adjusted_args.deadline -= kDeadlineFudgeFactor; @@ -392,10 +392,10 @@ compositor_timing_history_->BeginMainFrameQueueDurationCriticalEstimate(); // TODO(khushalsagar): We need to consider the deadline fudge factor here to - // match the deadline used in BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR mode + // match the deadline used in BeginImplFrameDeadlineMode::REGULAR mode // (used in the case where the impl thread needs to redraw). In the case where // main_frame_to_active is fast, we should consider using - // BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE instead to avoid putting the main + // BeginImplFrameDeadlineMode::LATE instead to avoid putting the main // thread in high latency mode. See crbug.com/753146. base::TimeDelta bmf_to_activate_threshold = adjusted_args.interval - @@ -484,7 +484,7 @@ void Scheduler::BeginImplFrame(const viz::BeginFrameArgs& args, base::TimeTicks now) { DCHECK_EQ(state_machine_.begin_impl_frame_state(), - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginImplFrameState::IDLE); DCHECK(begin_impl_frame_deadline_task_.IsCancelled()); DCHECK(state_machine_.HasInitializedLayerTreeFrameSink()); @@ -508,26 +508,26 @@ begin_impl_frame_deadline_mode_ = state_machine_.CurrentBeginImplFrameDeadlineMode(); switch (begin_impl_frame_deadline_mode_) { - case SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE: + case SchedulerStateMachine::BeginImplFrameDeadlineMode::NONE: // No deadline. return; - case SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE: + case SchedulerStateMachine::BeginImplFrameDeadlineMode::IMMEDIATE: // We are ready to draw a new active tree immediately. // We don't use Now() here because it's somewhat expensive to call. deadline_ = base::TimeTicks(); break; - case SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR: + case SchedulerStateMachine::BeginImplFrameDeadlineMode::REGULAR: // We are animating on the impl thread but we can wait for some time. deadline_ = begin_impl_frame_tracker_.Current().deadline; break; - case SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE: + case SchedulerStateMachine::BeginImplFrameDeadlineMode::LATE: // We are blocked for one reason or another and we should wait. // TODO(brianderson): Handle long deadlines (that are past the next // frame's frame time) properly instead of using this hack. deadline_ = begin_impl_frame_tracker_.Current().frame_time + begin_impl_frame_tracker_.Current().interval; break; - case SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED: + case SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED: // We are blocked because we are waiting for ReadyToDraw signal. We would // post deadline after we received ReadyToDraw singal. TRACE_EVENT1("cc", "Scheduler::ScheduleBeginImplFrameDeadline", @@ -552,7 +552,7 @@ return; if (state_machine_.begin_impl_frame_state() != - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME) return; if (begin_impl_frame_deadline_mode_ == @@ -645,9 +645,9 @@ base::AutoReset<SchedulerStateMachine::Action> mark_inside_action( &inside_action_, action); switch (action) { - case SchedulerStateMachine::ACTION_NONE: + case SchedulerStateMachine::Action::NONE: break; - case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME: + case SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME: compositor_timing_history_->WillBeginMainFrame( begin_main_frame_args_.on_critical_path, begin_main_frame_args_.frame_time); @@ -655,7 +655,7 @@ // TODO(brianderson): Pass begin_main_frame_args_ directly to client. client_->ScheduledActionSendBeginMainFrame(begin_main_frame_args_); break; - case SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: + case SchedulerStateMachine::Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: state_machine_.WillNotifyBeginMainFrameNotSent(); // If SendBeginMainFrameNotExpectedSoon was not previously sent by // BeginImplFrameNotExpectedSoon (because the messages were not required @@ -667,51 +667,51 @@ begin_main_frame_args_.interval); } break; - case SchedulerStateMachine::ACTION_COMMIT: { + case SchedulerStateMachine::Action::COMMIT: { bool commit_has_no_updates = false; state_machine_.WillCommit(commit_has_no_updates); client_->ScheduledActionCommit(); break; } - case SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE: + case SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE: compositor_timing_history_->WillActivate(); state_machine_.WillActivate(); client_->ScheduledActionActivateSyncTree(); compositor_timing_history_->DidActivate(); break; - case SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION: + case SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION: state_machine_.WillPerformImplSideInvalidation(); compositor_timing_history_->WillInvalidateOnImplSide(); client_->ScheduledActionPerformImplSideInvalidation(); break; - case SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE: + case SchedulerStateMachine::Action::DRAW_IF_POSSIBLE: DrawIfPossible(); break; - case SchedulerStateMachine::ACTION_DRAW_FORCED: + case SchedulerStateMachine::Action::DRAW_FORCED: DrawForced(); break; - case SchedulerStateMachine::ACTION_DRAW_ABORT: { + case SchedulerStateMachine::Action::DRAW_ABORT: { // No action is actually performed, but this allows the state machine to // drain the pipeline without actually drawing. state_machine_.AbortDraw(); compositor_timing_history_->DrawAborted(); break; } - case SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION: + case SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION: state_machine_.WillBeginLayerTreeFrameSinkCreation(); client_->ScheduledActionBeginLayerTreeFrameSinkCreation(); break; - case SchedulerStateMachine::ACTION_PREPARE_TILES: + case SchedulerStateMachine::Action::PREPARE_TILES: state_machine_.WillPrepareTiles(); client_->ScheduledActionPrepareTiles(); break; - case SchedulerStateMachine::ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK: { + case SchedulerStateMachine::Action::INVALIDATE_LAYER_TREE_FRAME_SINK: { state_machine_.WillInvalidateLayerTreeFrameSink(); client_->ScheduledActionInvalidateLayerTreeFrameSink(); break; } } - } while (action != SchedulerStateMachine::ACTION_NONE); + } while (action != SchedulerStateMachine::Action::NONE); ScheduleBeginImplFrameDeadlineIfNeeded(); SetupNextBeginFrameIfNeeded(); @@ -876,9 +876,9 @@ bool Scheduler::IsBeginMainFrameSentOrStarted() const { return (state_machine_.begin_main_frame_state() == - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT || + SchedulerStateMachine::BeginMainFrameState::SENT || state_machine_.begin_main_frame_state() == - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_STARTED); + SchedulerStateMachine::BeginMainFrameState::STARTED); } viz::BeginFrameAck Scheduler::CurrentBeginFrameAckForActiveTree() const {
diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h index 94e1c62..5e2dcb9 100644 --- a/cc/scheduler/scheduler.h +++ b/cc/scheduler/scheduler.h
@@ -189,7 +189,7 @@ SchedulerStateMachine::BeginImplFrameDeadlineMode begin_impl_frame_deadline_mode_ = - SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE; + SchedulerStateMachine::BeginImplFrameDeadlineMode::NONE; base::TimeTicks deadline_; base::TimeTicks deadline_scheduled_at_; @@ -203,7 +203,7 @@ SchedulerStateMachine state_machine_; bool inside_process_scheduled_actions_ = false; SchedulerStateMachine::Action inside_action_ = - SchedulerStateMachine::ACTION_NONE; + SchedulerStateMachine::Action::NONE; bool stopped_ = false;
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc index 476f1ae1..f8279d3c 100644 --- a/cc/scheduler/scheduler_state_machine.cc +++ b/cc/scheduler/scheduler_state_machine.cc
@@ -25,16 +25,16 @@ const char* SchedulerStateMachine::LayerTreeFrameSinkStateToString( LayerTreeFrameSinkState state) { switch (state) { - case LAYER_TREE_FRAME_SINK_NONE: - return "LAYER_TREE_FRAME_SINK_NONE"; - case LAYER_TREE_FRAME_SINK_ACTIVE: - return "LAYER_TREE_FRAME_SINK_ACTIVE"; - case LAYER_TREE_FRAME_SINK_CREATING: - return "LAYER_TREE_FRAME_SINK_CREATING"; - case LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT: - return "LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT"; - case LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION: - return "LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION"; + case LayerTreeFrameSinkState::NONE: + return "LayerTreeFrameSinkState::NONE"; + case LayerTreeFrameSinkState::ACTIVE: + return "LayerTreeFrameSinkState::ACTIVE"; + case LayerTreeFrameSinkState::CREATING: + return "LayerTreeFrameSinkState::CREATING"; + case LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT: + return "LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT"; + case LayerTreeFrameSinkState::WAITING_FOR_FIRST_ACTIVATION: + return "LayerTreeFrameSinkState::WAITING_FOR_FIRST_ACTIVATION"; } NOTREACHED(); return "???"; @@ -43,12 +43,12 @@ const char* SchedulerStateMachine::BeginImplFrameStateToString( BeginImplFrameState state) { switch (state) { - case BEGIN_IMPL_FRAME_STATE_IDLE: - return "BEGIN_IMPL_FRAME_STATE_IDLE"; - case BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME: - return "BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME"; - case BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE: - return "BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE"; + case BeginImplFrameState::IDLE: + return "BeginImplFrameState::IDLE"; + case BeginImplFrameState::INSIDE_BEGIN_FRAME: + return "BeginImplFrameState::INSIDE_BEGIN_FRAME"; + case BeginImplFrameState::INSIDE_DEADLINE: + return "BeginImplFrameState::INSIDE_DEADLINE"; } NOTREACHED(); return "???"; @@ -57,16 +57,16 @@ const char* SchedulerStateMachine::BeginImplFrameDeadlineModeToString( BeginImplFrameDeadlineMode mode) { switch (mode) { - case BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE: - return "BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE"; - case BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE: - return "BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE"; - case BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR: - return "BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR"; - case BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE: - return "BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE"; - case BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED: - return "BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED"; + case BeginImplFrameDeadlineMode::NONE: + return "BeginImplFrameDeadlineMode::NONE"; + case BeginImplFrameDeadlineMode::IMMEDIATE: + return "BeginImplFrameDeadlineMode::IMMEDIATE"; + case BeginImplFrameDeadlineMode::REGULAR: + return "BeginImplFrameDeadlineMode::REGULAR"; + case BeginImplFrameDeadlineMode::LATE: + return "BeginImplFrameDeadlineMode::LATE"; + case BeginImplFrameDeadlineMode::BLOCKED: + return "BeginImplFrameDeadlineMode::BLOCKED"; } NOTREACHED(); return "???"; @@ -75,14 +75,14 @@ const char* SchedulerStateMachine::BeginMainFrameStateToString( BeginMainFrameState state) { switch (state) { - case BEGIN_MAIN_FRAME_STATE_IDLE: - return "BEGIN_MAIN_FRAME_STATE_IDLE"; - case BEGIN_MAIN_FRAME_STATE_SENT: - return "BEGIN_MAIN_FRAME_STATE_SENT"; - case BEGIN_MAIN_FRAME_STATE_STARTED: - return "BEGIN_MAIN_FRAME_STATE_STARTED"; - case BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT: - return "BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT"; + case BeginMainFrameState::IDLE: + return "BeginMainFrameState::IDLE"; + case BeginMainFrameState::SENT: + return "BeginMainFrameState::SENT"; + case BeginMainFrameState::STARTED: + return "BeginMainFrameState::STARTED"; + case BeginMainFrameState::READY_TO_COMMIT: + return "BeginMainFrameState::READY_TO_COMMIT"; } NOTREACHED(); return "???"; @@ -91,14 +91,14 @@ const char* SchedulerStateMachine::ForcedRedrawOnTimeoutStateToString( ForcedRedrawOnTimeoutState state) { switch (state) { - case FORCED_REDRAW_STATE_IDLE: - return "FORCED_REDRAW_STATE_IDLE"; - case FORCED_REDRAW_STATE_WAITING_FOR_COMMIT: - return "FORCED_REDRAW_STATE_WAITING_FOR_COMMIT"; - case FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION: - return "FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION"; - case FORCED_REDRAW_STATE_WAITING_FOR_DRAW: - return "FORCED_REDRAW_STATE_WAITING_FOR_DRAW"; + case ForcedRedrawOnTimeoutState::IDLE: + return "ForcedRedrawOnTimeoutState::IDLE"; + case ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT: + return "ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT"; + case ForcedRedrawOnTimeoutState::WAITING_FOR_ACTIVATION: + return "ForcedRedrawOnTimeoutState::WAITING_FOR_ACTIVATION"; + case ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW: + return "ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW"; } NOTREACHED(); return "???"; @@ -117,30 +117,30 @@ const char* SchedulerStateMachine::ActionToString(Action action) { switch (action) { - case ACTION_NONE: - return "ACTION_NONE"; - case ACTION_SEND_BEGIN_MAIN_FRAME: - return "ACTION_SEND_BEGIN_MAIN_FRAME"; - case ACTION_COMMIT: - return "ACTION_COMMIT"; - case ACTION_ACTIVATE_SYNC_TREE: - return "ACTION_ACTIVATE_SYNC_TREE"; - case ACTION_DRAW_IF_POSSIBLE: - return "ACTION_DRAW_IF_POSSIBLE"; - case ACTION_DRAW_FORCED: - return "ACTION_DRAW_FORCED"; - case ACTION_DRAW_ABORT: - return "ACTION_DRAW_ABORT"; - case ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION: - return "ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION"; - case ACTION_PREPARE_TILES: - return "ACTION_PREPARE_TILES"; - case ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK: - return "ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK"; - case ACTION_PERFORM_IMPL_SIDE_INVALIDATION: - return "ACTION_PERFORM_IMPL_SIDE_INVALIDATION"; - case ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: - return "ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT"; + case Action::NONE: + return "Action::NONE"; + case Action::SEND_BEGIN_MAIN_FRAME: + return "Action::SEND_BEGIN_MAIN_FRAME"; + case Action::COMMIT: + return "Action::COMMIT"; + case Action::ACTIVATE_SYNC_TREE: + return "Action::ACTIVATE_SYNC_TREE"; + case Action::DRAW_IF_POSSIBLE: + return "Action::DRAW_IF_POSSIBLE"; + case Action::DRAW_FORCED: + return "Action::DRAW_FORCED"; + case Action::DRAW_ABORT: + return "Action::DRAW_ABORT"; + case Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION: + return "Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION"; + case Action::PREPARE_TILES: + return "Action::PREPARE_TILES"; + case Action::INVALIDATE_LAYER_TREE_FRAME_SINK: + return "Action::INVALIDATE_LAYER_TREE_FRAME_SINK"; + case Action::PERFORM_IMPL_SIDE_INVALIDATION: + return "Action::PERFORM_IMPL_SIDE_INVALIDATION"; + case Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: + return "Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT"; } NOTREACHED(); return "???"; @@ -242,7 +242,7 @@ // when the embedder is Android WebView, software draws could be scheduled by // the Android OS at any time and draws should not be aborted in this case. bool is_layer_tree_frame_sink_lost = - (layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_NONE); + (layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::NONE); if (resourceless_draw_) return is_layer_tree_frame_sink_lost || !can_draw_; @@ -260,7 +260,7 @@ bool SchedulerStateMachine::ShouldAbortCurrentFrame() const { // Abort the frame if there is no output surface to trigger our // activations, avoiding deadlock with the main thread. - if (layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_NONE) + if (layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::NONE) return true; // If we're not visible, we should just abort the frame. Since we @@ -288,13 +288,13 @@ // We only want to start output surface initialization after the // previous commit is complete. - if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) { + if (begin_main_frame_state_ != BeginMainFrameState::IDLE) { return false; } // Make sure the BeginImplFrame from any previous LayerTreeFrameSinks // are complete before creating the new LayerTreeFrameSink. - if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_IDLE) + if (begin_impl_frame_state_ != BeginImplFrameState::IDLE) return false; // We want to clear the pipeline of any pending draws and activations @@ -306,7 +306,7 @@ // We need to create the output surface if we don't have one and we haven't // started creating one yet. - return layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_NONE; + return layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::NONE; } bool SchedulerStateMachine::ShouldDraw() const { @@ -324,7 +324,7 @@ return false; // Don't draw if we are waiting on the first commit after a surface. - if (layer_tree_frame_sink_state_ != LAYER_TREE_FRAME_SINK_ACTIVE) + if (layer_tree_frame_sink_state_ != LayerTreeFrameSinkState::ACTIVE) return false; // Do not queue too many draws. @@ -333,7 +333,7 @@ // Except for the cases above, do not draw outside of the BeginImplFrame // deadline. - if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) + if (begin_impl_frame_state_ != BeginImplFrameState::INSIDE_DEADLINE) return false; // Wait for ready to draw in full-pipeline mode or the browser compositor's @@ -350,7 +350,7 @@ return false; // Only handle forced redraws due to timeouts on the regular deadline. - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) + if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW) return true; return needs_redraw_; @@ -385,7 +385,7 @@ // Don't notify if a BeginMainFrame has already been requested or is in // progress. if (needs_begin_main_frame_ || - begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) + begin_main_frame_state_ != BeginMainFrameState::IDLE) return false; // Only notify when we're visible. @@ -445,7 +445,7 @@ // Other parts of the state machine indirectly defer the BeginMainFrame // by transitioning to WAITING commit states rather than going // immediately to IDLE. - if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) + if (begin_main_frame_state_ != BeginMainFrameState::IDLE) return false; // MFBA is disabled and we are waiting for previous activation. @@ -472,13 +472,13 @@ // TODO(brianderson): Allow sending BeginMainFrame while idle when the main // thread isn't consuming user input for non-synchronous compositor. if (!settings_.using_synchronous_renderer_compositor && - begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE) { + begin_impl_frame_state_ == BeginImplFrameState::IDLE) { return false; } // We need a new commit for the forced redraw. This honors the // single commit per interval because the result will be swapped to screen. - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) + if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT) return true; // We shouldn't normally accept commits if there isn't a LayerTreeFrameSink. @@ -492,7 +492,7 @@ // TODO(brianderson): Remove this restriction to improve throughput or // make it conditional on ImplLatencyTakesPriority. bool just_submitted_in_deadline = - begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE && + begin_impl_frame_state_ == BeginImplFrameState::INSIDE_DEADLINE && did_submit_in_last_frame_; if (IsDrawThrottled() && !just_submitted_in_deadline) return false; @@ -505,7 +505,7 @@ } bool SchedulerStateMachine::ShouldCommit() const { - if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT) + if (begin_main_frame_state_ != BeginMainFrameState::READY_TO_COMMIT) return false; // We must not finish the commit until the pending tree is free. @@ -538,7 +538,7 @@ // Limiting to once per-frame is not enough, since we only want to prepare // tiles _after_ draws. - if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) + if (begin_impl_frame_state_ != BeginImplFrameState::INSIDE_DEADLINE) return false; return needs_prepare_tiles_; @@ -554,7 +554,7 @@ return false; // Invalidations are only performed inside a BeginFrame. - if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) + if (begin_impl_frame_state_ != BeginImplFrameState::INSIDE_BEGIN_FRAME) return false; // TODO(sunnyps): needs_prepare_tiles_ is needed here because PrepareTiles is @@ -565,30 +565,31 @@ SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const { if (ShouldActivateSyncTree()) - return ACTION_ACTIVATE_SYNC_TREE; + return Action::ACTIVATE_SYNC_TREE; if (ShouldCommit()) - return ACTION_COMMIT; + return Action::COMMIT; if (ShouldDraw()) { if (PendingDrawsShouldBeAborted()) - return ACTION_DRAW_ABORT; - else if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) - return ACTION_DRAW_FORCED; + return Action::DRAW_ABORT; + else if (forced_redraw_state_ == + ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW) + return Action::DRAW_FORCED; else - return ACTION_DRAW_IF_POSSIBLE; + return Action::DRAW_IF_POSSIBLE; } if (ShouldPerformImplSideInvalidation()) - return ACTION_PERFORM_IMPL_SIDE_INVALIDATION; + return Action::PERFORM_IMPL_SIDE_INVALIDATION; if (ShouldPrepareTiles()) - return ACTION_PREPARE_TILES; + return Action::PREPARE_TILES; if (ShouldSendBeginMainFrame()) - return ACTION_SEND_BEGIN_MAIN_FRAME; + return Action::SEND_BEGIN_MAIN_FRAME; if (ShouldInvalidateLayerTreeFrameSink()) - return ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK; + return Action::INVALIDATE_LAYER_TREE_FRAME_SINK; if (ShouldBeginLayerTreeFrameSinkCreation()) - return ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION; + return Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION; if (ShouldNotifyBeginMainFrameNotSent()) - return ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT; - return ACTION_NONE; + return Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT; + return Action::NONE; } bool SchedulerStateMachine::ShouldPerformImplSideInvalidation() const { @@ -601,7 +602,7 @@ return false; // No invalidations should be done outside the impl frame. - if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE) + if (begin_impl_frame_state_ == BeginImplFrameState::IDLE) return false; // We need to be able to create a pending tree to perform an invalidation. @@ -624,11 +625,11 @@ } bool SchedulerStateMachine::ShouldDeferInvalidatingForMainFrame() const { - DCHECK_NE(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_IDLE); + DCHECK_NE(begin_impl_frame_state_, BeginImplFrameState::IDLE); // If the main thread is ready to commit, the impl-side invalidations will be // merged with the incoming main frame. - if (begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT) + if (begin_main_frame_state_ == BeginMainFrameState::READY_TO_COMMIT) return true; // If we are inside the deadline, and haven't performed an invalidation yet, @@ -641,7 +642,7 @@ // b) We have to wait on the main thread to respond to a main frame. // In addition, the deadline task can be cancelled if the main thread // responds before it runs. - if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) + if (begin_impl_frame_state_ == BeginImplFrameState::INSIDE_DEADLINE) return false; // If commits are being aborted (which would be the common case for a @@ -658,8 +659,8 @@ return true; // If the main frame was already sent, wait for the main thread to respond. - if (begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_SENT || - begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_STARTED) + if (begin_main_frame_state_ == BeginMainFrameState::SENT || + begin_main_frame_state_ == BeginMainFrameState::STARTED) return true; // If the main thread committed during the last frame, i.e. it was not @@ -720,7 +721,7 @@ DCHECK(visible_); DCHECK(!begin_frame_source_paused_); DCHECK(!did_send_begin_main_frame_for_current_frame_); - begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_SENT; + begin_main_frame_state_ = BeginMainFrameState::SENT; needs_begin_main_frame_ = false; did_send_begin_main_frame_for_current_frame_ = true; last_frame_number_begin_main_frame_sent_ = current_frame_number_; @@ -741,7 +742,7 @@ DCHECK(!has_pending_tree_ || can_have_pending_tree); commit_count_++; last_commit_had_no_updates_ = commit_has_no_updates; - begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; + begin_main_frame_state_ = BeginMainFrameState::IDLE; did_commit_during_frame_ = true; if (!commit_has_no_updates) { @@ -764,28 +765,30 @@ } // Update state related to forced draws. - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) { - forced_redraw_state_ = has_pending_tree_ - ? FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION - : FORCED_REDRAW_STATE_WAITING_FOR_DRAW; + if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT) { + forced_redraw_state_ = + has_pending_tree_ ? ForcedRedrawOnTimeoutState::WAITING_FOR_ACTIVATION + : ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW; } // Update the output surface state. if (layer_tree_frame_sink_state_ == - LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT) { + LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT) { layer_tree_frame_sink_state_ = - has_pending_tree_ ? LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION - : LAYER_TREE_FRAME_SINK_ACTIVE; + has_pending_tree_ + ? LayerTreeFrameSinkState::WAITING_FOR_FIRST_ACTIVATION + : LayerTreeFrameSinkState::ACTIVE; } } void SchedulerStateMachine::WillActivate() { if (layer_tree_frame_sink_state_ == - LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION) - layer_tree_frame_sink_state_ = LAYER_TREE_FRAME_SINK_ACTIVE; + LayerTreeFrameSinkState::WAITING_FOR_FIRST_ACTIVATION) + layer_tree_frame_sink_state_ = LayerTreeFrameSinkState::ACTIVE; - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION) - forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_DRAW; + if (forced_redraw_state_ == + ForcedRedrawOnTimeoutState::WAITING_FOR_ACTIVATION) + forced_redraw_state_ = ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW; has_pending_tree_ = false; pending_tree_is_ready_for_activation_ = false; @@ -814,8 +817,8 @@ did_draw_in_last_frame_ = true; last_frame_number_draw_performed_ = current_frame_number_; - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) - forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; + if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW) + forced_redraw_state_ = ForcedRedrawOnTimeoutState::IDLE; } void SchedulerStateMachine::DidDrawInternal(DrawResult draw_result) { @@ -827,7 +830,7 @@ case DRAW_ABORTED_DRAINING_PIPELINE: case DRAW_SUCCESS: consecutive_checkerboard_animations_ = 0; - forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; + forced_redraw_state_ = ForcedRedrawOnTimeoutState::IDLE; break; case DRAW_ABORTED_CHECKERBOARD_ANIMATIONS: DCHECK(!did_submit_in_last_frame_); @@ -837,11 +840,11 @@ if (consecutive_checkerboard_animations_ >= settings_.maximum_number_of_failed_draws_before_draw_is_forced && - forced_redraw_state_ == FORCED_REDRAW_STATE_IDLE && + forced_redraw_state_ == ForcedRedrawOnTimeoutState::IDLE && settings_.timeout_and_draw_when_animation_checkerboards) { // We need to force a draw, but it doesn't make sense to do this until // we've committed and have new textures. - forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_COMMIT; + forced_redraw_state_ = ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT; } break; case DRAW_ABORTED_MISSING_HIGH_RES_CONTENT: @@ -889,13 +892,13 @@ } void SchedulerStateMachine::WillBeginLayerTreeFrameSinkCreation() { - DCHECK_EQ(layer_tree_frame_sink_state_, LAYER_TREE_FRAME_SINK_NONE); - layer_tree_frame_sink_state_ = LAYER_TREE_FRAME_SINK_CREATING; + DCHECK_EQ(layer_tree_frame_sink_state_, LayerTreeFrameSinkState::NONE); + layer_tree_frame_sink_state_ = LayerTreeFrameSinkState::CREATING; // The following DCHECKs make sure we are in the proper quiescent state. // The pipeline should be flushed entirely before we start output // surface creation to avoid complicated corner cases. - DCHECK(begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_IDLE); + DCHECK(begin_main_frame_state_ == BeginMainFrameState::IDLE); DCHECK(!has_pending_tree_); DCHECK(!active_tree_needs_first_draw_); } @@ -950,7 +953,7 @@ bool SchedulerStateMachine::BeginFrameRequiredForAction() const { // The forced draw respects our normal draw scheduling, so we need to // request a BeginImplFrame for it. - if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW) + if (forced_redraw_state_ == ForcedRedrawOnTimeoutState::WAITING_FOR_DRAW) return true; return needs_redraw_ || needs_one_begin_impl_frame_ || @@ -974,8 +977,7 @@ // request frames when commits are disabled, because the frame requests will // not provide the needed commit (and will wake up the process when it could // stay idle). - if ((begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) && - !defer_commits_) + if ((begin_main_frame_state_ != BeginMainFrameState::IDLE) && !defer_commits_) return true; // If the pending tree activates quickly, we'll want a BeginImplFrame soon @@ -1005,7 +1007,7 @@ void SchedulerStateMachine::OnBeginImplFrame(uint64_t source_id, uint64_t sequence_number) { - begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; + begin_impl_frame_state_ = BeginImplFrameState::INSIDE_BEGIN_FRAME; current_frame_number_++; // Cache the values from the previous impl frame before reseting them for this @@ -1026,14 +1028,14 @@ } void SchedulerStateMachine::OnBeginImplFrameDeadline() { - begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE; + begin_impl_frame_state_ = BeginImplFrameState::INSIDE_DEADLINE; // Clear funnels for any actions we perform during the deadline. did_draw_ = false; } void SchedulerStateMachine::OnBeginImplFrameIdle() { - begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_IDLE; + begin_impl_frame_state_ = BeginImplFrameState::IDLE; // Count any prepare tiles that happens in commits in between frames. We want // to prevent a prepare tiles during the next frame's deadline in that case. @@ -1058,19 +1060,19 @@ SchedulerStateMachine::CurrentBeginImplFrameDeadlineMode() const { if (settings_.using_synchronous_renderer_compositor) { // No deadline for synchronous compositor. - return BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE; + return BeginImplFrameDeadlineMode::NONE; } else if (ShouldBlockDeadlineIndefinitely()) { - return BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED; + return BeginImplFrameDeadlineMode::BLOCKED; } else if (ShouldTriggerBeginImplFrameDeadlineImmediately()) { - return BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE; + return BeginImplFrameDeadlineMode::IMMEDIATE; } else if (needs_redraw_) { // We have an animation or fast input path on the impl thread that wants // to draw, so don't wait too long for a new active tree. - return BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR; + return BeginImplFrameDeadlineMode::REGULAR; } else { // The impl thread doesn't have anything it wants to draw and we are just // waiting for a new active tree. In short we are blocked. - return BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE; + return BeginImplFrameDeadlineMode::LATE; } } @@ -1119,7 +1121,7 @@ // Avoid blocking for any reason if we don't have a layer tree frame sink or // are invisible. - if (layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_NONE) + if (layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::NONE) return false; if (!visible_) @@ -1136,7 +1138,7 @@ if (ShouldSendBeginMainFrame()) return true; - if (begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE) + if (begin_main_frame_state_ != BeginMainFrameState::IDLE) return true; // Wait for tiles and activation. @@ -1193,8 +1195,7 @@ bool has_impl_updates = needs_redraw_ || needs_one_begin_impl_frame_; bool main_updates_expected = needs_begin_main_frame_ || - begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE || - has_pending_tree_; + begin_main_frame_state_ != BeginMainFrameState::IDLE || has_pending_tree_; return has_impl_updates && !main_updates_expected; } @@ -1258,9 +1259,9 @@ } void SchedulerStateMachine::NotifyReadyToCommit() { - DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_STARTED) + DCHECK_EQ(begin_main_frame_state_, BeginMainFrameState::STARTED) << AsValue()->ToString(); - begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT; + begin_main_frame_state_ = BeginMainFrameState::READY_TO_COMMIT; // In commit_to_active_tree mode, commit should happen right after BeginFrame, // meaning when this function is called, next action should be commit. if (settings_.commit_to_active_tree) @@ -1268,7 +1269,7 @@ } void SchedulerStateMachine::BeginMainFrameAborted(CommitEarlyOutReason reason) { - DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_STARTED); + DCHECK_EQ(begin_main_frame_state_, BeginMainFrameState::STARTED); // If the main thread aborted, it doesn't matter if the main thread missed // the last deadline since it didn't have an update anyway. @@ -1278,7 +1279,7 @@ case CommitEarlyOutReason::ABORTED_LAYER_TREE_FRAME_SINK_LOST: case CommitEarlyOutReason::ABORTED_NOT_VISIBLE: case CommitEarlyOutReason::ABORTED_DEFERRED_COMMIT: - begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; + begin_main_frame_state_ = BeginMainFrameState::IDLE; SetNeedsBeginMainFrame(); return; case CommitEarlyOutReason::FINISHED_NO_UPDATES: @@ -1294,10 +1295,10 @@ } void SchedulerStateMachine::DidLoseLayerTreeFrameSink() { - if (layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_NONE || - layer_tree_frame_sink_state_ == LAYER_TREE_FRAME_SINK_CREATING) + if (layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::NONE || + layer_tree_frame_sink_state_ == LayerTreeFrameSinkState::CREATING) return; - layer_tree_frame_sink_state_ = LAYER_TREE_FRAME_SINK_NONE; + layer_tree_frame_sink_state_ = LayerTreeFrameSinkState::NONE; needs_redraw_ = false; } @@ -1314,8 +1315,9 @@ } void SchedulerStateMachine::DidCreateAndInitializeLayerTreeFrameSink() { - DCHECK_EQ(layer_tree_frame_sink_state_, LAYER_TREE_FRAME_SINK_CREATING); - layer_tree_frame_sink_state_ = LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT; + DCHECK_EQ(layer_tree_frame_sink_state_, LayerTreeFrameSinkState::CREATING); + layer_tree_frame_sink_state_ = + LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT; if (did_create_and_initialize_first_layer_tree_frame_sink_) { // TODO(boliu): See if we can remove this when impl-side painting is always @@ -1329,19 +1331,19 @@ } void SchedulerStateMachine::NotifyBeginMainFrameStarted() { - DCHECK_EQ(begin_main_frame_state_, BEGIN_MAIN_FRAME_STATE_SENT); - begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_STARTED; + DCHECK_EQ(begin_main_frame_state_, BeginMainFrameState::SENT); + begin_main_frame_state_ = BeginMainFrameState::STARTED; } bool SchedulerStateMachine::HasInitializedLayerTreeFrameSink() const { switch (layer_tree_frame_sink_state_) { - case LAYER_TREE_FRAME_SINK_NONE: - case LAYER_TREE_FRAME_SINK_CREATING: + case LayerTreeFrameSinkState::NONE: + case LayerTreeFrameSinkState::CREATING: return false; - case LAYER_TREE_FRAME_SINK_ACTIVE: - case LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT: - case LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION: + case LayerTreeFrameSinkState::ACTIVE: + case LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT: + case LayerTreeFrameSinkState::WAITING_FOR_FIRST_ACTIVATION: return true; } NOTREACHED();
diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h index c0f1f0a..4e2f664 100644 --- a/cc/scheduler/scheduler_state_machine.h +++ b/cc/scheduler/scheduler_state_machine.h
@@ -50,48 +50,48 @@ explicit SchedulerStateMachine(const SchedulerSettings& settings); ~SchedulerStateMachine(); - enum LayerTreeFrameSinkState { - LAYER_TREE_FRAME_SINK_NONE, - LAYER_TREE_FRAME_SINK_ACTIVE, - LAYER_TREE_FRAME_SINK_CREATING, - LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT, - LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION, + enum class LayerTreeFrameSinkState { + NONE, + ACTIVE, + CREATING, + WAITING_FOR_FIRST_COMMIT, + WAITING_FOR_FIRST_ACTIVATION, }; static const char* LayerTreeFrameSinkStateToString( LayerTreeFrameSinkState state); // Note: BeginImplFrameState does not cycle through these states in a fixed // order on all platforms. It's up to the scheduler to set these correctly. - enum BeginImplFrameState { - BEGIN_IMPL_FRAME_STATE_IDLE, - BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME, - BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE, + enum class BeginImplFrameState { + IDLE, + INSIDE_BEGIN_FRAME, + INSIDE_DEADLINE, }; static const char* BeginImplFrameStateToString(BeginImplFrameState state); - enum BeginImplFrameDeadlineMode { - BEGIN_IMPL_FRAME_DEADLINE_MODE_NONE, - BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE, - BEGIN_IMPL_FRAME_DEADLINE_MODE_REGULAR, - BEGIN_IMPL_FRAME_DEADLINE_MODE_LATE, - BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + enum class BeginImplFrameDeadlineMode { + NONE, + IMMEDIATE, + REGULAR, + LATE, + BLOCKED, }; static const char* BeginImplFrameDeadlineModeToString( BeginImplFrameDeadlineMode mode); - enum BeginMainFrameState { - BEGIN_MAIN_FRAME_STATE_IDLE, - BEGIN_MAIN_FRAME_STATE_SENT, - BEGIN_MAIN_FRAME_STATE_STARTED, - BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT, + enum class BeginMainFrameState { + IDLE, + SENT, + STARTED, + READY_TO_COMMIT, }; static const char* BeginMainFrameStateToString(BeginMainFrameState state); - enum ForcedRedrawOnTimeoutState { - FORCED_REDRAW_STATE_IDLE, - FORCED_REDRAW_STATE_WAITING_FOR_COMMIT, - FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION, - FORCED_REDRAW_STATE_WAITING_FOR_DRAW, + enum class ForcedRedrawOnTimeoutState { + IDLE, + WAITING_FOR_COMMIT, + WAITING_FOR_ACTIVATION, + WAITING_FOR_DRAW, }; static const char* ForcedRedrawOnTimeoutStateToString( ForcedRedrawOnTimeoutState state); @@ -101,9 +101,9 @@ } bool CommitPending() const { - return begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_SENT || - begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_STARTED || - begin_main_frame_state_ == BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT; + return begin_main_frame_state_ == BeginMainFrameState::SENT || + begin_main_frame_state_ == BeginMainFrameState::STARTED || + begin_main_frame_state_ == BeginMainFrameState::READY_TO_COMMIT; } bool NewActiveTreeLikely() const { @@ -113,19 +113,19 @@ bool RedrawPending() const { return needs_redraw_; } bool PrepareTilesPending() const { return needs_prepare_tiles_; } - enum Action { - ACTION_NONE, - ACTION_SEND_BEGIN_MAIN_FRAME, - ACTION_COMMIT, - ACTION_ACTIVATE_SYNC_TREE, - ACTION_PERFORM_IMPL_SIDE_INVALIDATION, - ACTION_DRAW_IF_POSSIBLE, - ACTION_DRAW_FORCED, - ACTION_DRAW_ABORT, - ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION, - ACTION_PREPARE_TILES, - ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK, - ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT, + enum class Action { + NONE, + SEND_BEGIN_MAIN_FRAME, + COMMIT, + ACTIVATE_SYNC_TREE, + PERFORM_IMPL_SIDE_INVALIDATION, + DRAW_IF_POSSIBLE, + DRAW_FORCED, + DRAW_ABORT, + BEGIN_LAYER_TREE_FRAME_SINK_CREATION, + PREPARE_TILES, + INVALIDATE_LAYER_TREE_FRAME_SINK, + NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT, }; static const char* ActionToString(Action action); @@ -232,12 +232,12 @@ // active). void SetNeedsOneBeginImplFrame(); - // Call this only in response to receiving an ACTION_SEND_BEGIN_MAIN_FRAME + // Call this only in response to receiving an Action::SEND_BEGIN_MAIN_FRAME // from NextAction. // Indicates that all painting is complete. void NotifyReadyToCommit(); - // Call this only in response to receiving an ACTION_SEND_BEGIN_MAIN_FRAME + // Call this only in response to receiving an Action::SEND_BEGIN_MAIN_FRAME // from NextAction if the client rejects the BeginMainFrame message. void BeginMainFrameAborted(CommitEarlyOutReason reason); @@ -324,10 +324,11 @@ const SchedulerSettings settings_; LayerTreeFrameSinkState layer_tree_frame_sink_state_ = - LAYER_TREE_FRAME_SINK_NONE; - BeginImplFrameState begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_IDLE; - BeginMainFrameState begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE; - ForcedRedrawOnTimeoutState forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE; + LayerTreeFrameSinkState::NONE; + BeginImplFrameState begin_impl_frame_state_ = BeginImplFrameState::IDLE; + BeginMainFrameState begin_main_frame_state_ = BeginMainFrameState::IDLE; + ForcedRedrawOnTimeoutState forced_redraw_state_ = + ForcedRedrawOnTimeoutState::IDLE; // These are used for tracing only. int commit_count_ = 0;
diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc index c670aa7..ed089d15 100644 --- a/cc/scheduler/scheduler_state_machine_unittest.cc +++ b/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -36,26 +36,26 @@ EXPECT_ENUM_EQ(ActionToString, expected, state.NextAction()) \ << state.AsValue()->ToString() -#define EXPECT_ACTION_UPDATE_STATE(action) \ - EXPECT_ACTION(action); \ - if (action == SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE || \ - action == SchedulerStateMachine::ACTION_DRAW_FORCED) { \ - EXPECT_IMPL_FRAME_STATE( \ - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); \ - } \ - PerformAction(&state, action); \ - if (action == SchedulerStateMachine::ACTION_NONE) { \ - if (state.begin_impl_frame_state() == \ - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) \ - state.OnBeginImplFrameIdle(); \ +#define EXPECT_ACTION_UPDATE_STATE(action) \ + EXPECT_ACTION(action); \ + if (action == SchedulerStateMachine::Action::DRAW_IF_POSSIBLE || \ + action == SchedulerStateMachine::Action::DRAW_FORCED) { \ + EXPECT_IMPL_FRAME_STATE( \ + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); \ + } \ + PerformAction(&state, action); \ + if (action == SchedulerStateMachine::Action::NONE) { \ + if (state.begin_impl_frame_state() == \ + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE) \ + state.OnBeginImplFrameIdle(); \ } -#define SET_UP_STATE(state) \ - state.SetVisible(true); \ - EXPECT_ACTION_UPDATE_STATE( \ - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); \ - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); \ - state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); \ +#define SET_UP_STATE(state) \ + state.SetVisible(true); \ + EXPECT_ACTION_UPDATE_STATE( \ + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); \ + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); \ + state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); \ state.SetCanDraw(true); namespace cc { @@ -64,16 +64,16 @@ const SchedulerStateMachine::BeginImplFrameState all_begin_impl_frame_states[] = { - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE, - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME, - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE, + SchedulerStateMachine::BeginImplFrameState::IDLE, + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME, + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE, }; const SchedulerStateMachine::BeginMainFrameState begin_main_frame_states[] = { - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE, - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT, - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_STARTED, - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT}; + SchedulerStateMachine::BeginMainFrameState::IDLE, + SchedulerStateMachine::BeginMainFrameState::SENT, + SchedulerStateMachine::BeginMainFrameState::STARTED, + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT}; // Exposes the protected state fields of the SchedulerStateMachine for testing class StateMachine : public SchedulerStateMachine { @@ -84,7 +84,7 @@ void CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit() { DidCreateAndInitializeLayerTreeFrameSink(); - layer_tree_frame_sink_state_ = LAYER_TREE_FRAME_SINK_ACTIVE; + layer_tree_frame_sink_state_ = LayerTreeFrameSinkState::ACTIVE; } void IssueNextBeginImplFrame() { @@ -132,11 +132,11 @@ DrawResult draw_result_for_test() { return draw_result_for_test_; } void SetNeedsForcedRedrawForTimeout(bool b) { - forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_COMMIT; + forced_redraw_state_ = ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT; active_tree_needs_first_draw_ = true; } bool NeedsForcedRedrawForTimeout() const { - return forced_redraw_state_ != FORCED_REDRAW_STATE_IDLE; + return forced_redraw_state_ != ForcedRedrawOnTimeoutState::IDLE; } void SetActiveTreeNeedsFirstDraw(bool needs_first_draw) { @@ -171,52 +171,52 @@ void PerformAction(StateMachine* sm, SchedulerStateMachine::Action action) { switch (action) { - case SchedulerStateMachine::ACTION_NONE: + case SchedulerStateMachine::Action::NONE: return; - case SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE: + case SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE: sm->WillActivate(); return; - case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME: + case SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME: sm->WillSendBeginMainFrame(); return; - case SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: + case SchedulerStateMachine::Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT: sm->WillNotifyBeginMainFrameNotSent(); return; - case SchedulerStateMachine::ACTION_COMMIT: { + case SchedulerStateMachine::Action::COMMIT: { bool commit_has_no_updates = false; sm->WillCommit(commit_has_no_updates); return; } - case SchedulerStateMachine::ACTION_DRAW_FORCED: - case SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE: { + case SchedulerStateMachine::Action::DRAW_FORCED: + case SchedulerStateMachine::Action::DRAW_IF_POSSIBLE: { sm->WillDraw(); sm->DidDraw(sm->draw_result_for_test()); return; } - case SchedulerStateMachine::ACTION_DRAW_ABORT: { + case SchedulerStateMachine::Action::DRAW_ABORT: { sm->AbortDraw(); return; } - case SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION: + case SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION: sm->WillBeginLayerTreeFrameSinkCreation(); return; - case SchedulerStateMachine::ACTION_PREPARE_TILES: + case SchedulerStateMachine::Action::PREPARE_TILES: sm->WillPrepareTiles(); return; - case SchedulerStateMachine::ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK: + case SchedulerStateMachine::Action::INVALIDATE_LAYER_TREE_FRAME_SINK: sm->WillInvalidateLayerTreeFrameSink(); return; - case SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION: + case SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION: sm->WillPerformImplSideInvalidation(); return; } @@ -227,11 +227,11 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); // Don't request BeginFrames if we are idle. state.SetNeedsRedraw(false); @@ -278,18 +278,18 @@ state.SetMainThreadWantsBeginMainFrameNotExpectedMessages(true); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); state.IssueNextBeginImplFrame(); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.SetNeedsRedraw(true); state.SetNeedsBeginMainFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestNextActionBeginsMainFrameIfNeeded) { @@ -300,21 +300,21 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); state.SetNeedsRedraw(false); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.NeedsCommit()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.NeedsCommit()); } @@ -322,18 +322,18 @@ { StateMachine state(default_scheduler_settings); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); state.SetNeedsRedraw(false); state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.NeedsCommit()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.NeedsCommit()); } @@ -341,26 +341,26 @@ { StateMachine state(default_scheduler_settings); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetNeedsRedraw(false); state.SetNeedsBeginMainFrame(); // Expect nothing to happen until after OnBeginImplFrame. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); - EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); + EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BeginImplFrameState::IDLE); EXPECT_TRUE(state.NeedsCommit()); EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); } @@ -368,26 +368,26 @@ { StateMachine state(default_scheduler_settings); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetNeedsRedraw(false); state.SetNeedsBeginMainFrame(); state.SetCanDraw(false); // Expect nothing to happen until after OnBeginImplFrame. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); - EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); + EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BeginImplFrameState::IDLE); EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); } } @@ -398,7 +398,7 @@ scheduler_settings.main_frame_before_activation_enabled = true; StateMachine state(scheduler_settings); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + SchedulerStateMachine::BeginMainFrameState::IDLE); SET_UP_STATE(state) state.SetNeedsRedraw(false); state.SetNeedsBeginMainFrame(); @@ -408,45 +408,45 @@ // Commit to the pending tree. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify that the next commit starts while there is still a pending tree. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify the pending commit doesn't overwrite the pending // tree until the pending tree has been activated. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify NotifyReadyToActivate unblocks activation, commit, and // draw in that order. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); } TEST(SchedulerStateMachineTest, @@ -460,31 +460,31 @@ // Start a frame. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.CommitPending()); // Failing a draw triggers request for a new BeginMainFrame. state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // It's okay to attempt more draws just in case additional raster // finishes and the requested commit wasn't actually necessary. EXPECT_TRUE(state.CommitPending()); EXPECT_TRUE(state.RedrawPending()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, FailedDrawForMissingHighResNeedsCommit) { @@ -497,50 +497,50 @@ // Start a frame. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.CommitPending()); // Failing a draw triggers because of high res tiles missing // request for a new BeginMainFrame. state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // It doesn't request a draw until we get a new commit though. EXPECT_TRUE(state.CommitPending()); EXPECT_FALSE(state.RedrawPending()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Finish the commit and activation. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.RedrawPending()); // Verify we draw with the new frame. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_SUCCESS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -552,19 +552,19 @@ // Start a commit. state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.CommitPending()); // Then initiate a draw that fails. state.SetNeedsRedraw(true); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.BeginFrameNeeded()); EXPECT_TRUE(state.RedrawPending()); EXPECT_TRUE(state.CommitPending()); @@ -573,28 +573,28 @@ // continue the commit as usual. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.RedrawPending()); // Activate so we're ready for a new main frame. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.RedrawPending()); // The redraw should be forced at the end of the next BeginImplFrame. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_SUCCESS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_FORCED); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_FORCED); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestFailedDrawsDoNotRestartForcedDraw) { @@ -607,54 +607,56 @@ // Start a commit. state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.CommitPending()); // Then initiate a draw. state.SetNeedsRedraw(true); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); // Fail the draw enough times to force a redraw. for (int i = 0; i < draw_limit; ++i) { state.SetNeedsRedraw(true); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } EXPECT_TRUE(state.BeginFrameNeeded()); EXPECT_TRUE(state.RedrawPending()); // But the commit is ongoing. EXPECT_TRUE(state.CommitPending()); - EXPECT_TRUE(state.ForcedRedrawState() == - SchedulerStateMachine::FORCED_REDRAW_STATE_WAITING_FOR_COMMIT); + EXPECT_TRUE( + state.ForcedRedrawState() == + SchedulerStateMachine::ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT); // After failing additional draws, we should still be in a forced // redraw, but not back in IDLE. for (int i = 0; i < draw_limit; ++i) { state.SetNeedsRedraw(true); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } EXPECT_TRUE(state.RedrawPending()); - EXPECT_TRUE(state.ForcedRedrawState() == - SchedulerStateMachine::FORCED_REDRAW_STATE_WAITING_FOR_COMMIT); + EXPECT_TRUE( + state.ForcedRedrawState() == + SchedulerStateMachine::ForcedRedrawOnTimeoutState::WAITING_FOR_COMMIT); } TEST(SchedulerStateMachineTest, TestFailedDrawIsRetriedInNextBeginImplFrame) { @@ -666,31 +668,31 @@ state.SetNeedsRedraw(true); EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); EXPECT_TRUE(state.RedrawPending()); state.SetDrawResultForTest(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); // Failing the draw for animation checkerboards makes us require a commit. EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.RedrawPending()); // We should not be trying to draw again now, but we have a commit pending. EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // We should try to draw again at the end of the next BeginImplFrame on // the impl thread. state.OnBeginImplFrameDeadline(); state.SetDrawResultForTest(DRAW_SUCCESS); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestDoestDrawTwiceInSameFrame) { @@ -702,28 +704,28 @@ // Draw the first frame. EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Before the next BeginImplFrame, set needs redraw again. // This should not redraw until the next BeginImplFrame. state.SetNeedsRedraw(true); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Move to another frame. This should now draw. EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // We just submitted, so we should proactively request another BeginImplFrame. EXPECT_TRUE(state.BeginFrameNeeded()); @@ -745,23 +747,23 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginMainFrameState(begin_main_frame_states[i]); state.SetBeginImplFrameState(all_begin_impl_frame_states[j]); bool visible = (all_begin_impl_frame_states[j] != - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); state.SetVisible(visible); // Case 1: needs_begin_main_frame=false - EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, + EXPECT_NE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE, state.NextAction()); // Case 2: needs_begin_main_frame=true state.SetNeedsBeginMainFrame(); - EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, + EXPECT_NE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE, state.NextAction()) << state.AsValue()->ToString(); } @@ -773,22 +775,22 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetCanDraw(true); state.SetBeginMainFrameState(begin_main_frame_states[i]); state.SetBeginImplFrameState( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); state.SetNeedsRedraw(true); SchedulerStateMachine::Action expected_action; if (begin_main_frame_states[i] == - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT) { - expected_action = SchedulerStateMachine::ACTION_COMMIT; + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT) { + expected_action = SchedulerStateMachine::Action::COMMIT; } else { - expected_action = SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE; + expected_action = SchedulerStateMachine::Action::DRAW_IF_POSSIBLE; } // Case 1: needs_begin_main_frame=false. @@ -812,24 +814,24 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginMainFrameState(begin_main_frame_states[i]); state.SetVisible(false); state.SetNeedsRedraw(true); if (j == 1) { state.SetBeginImplFrameState( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); } // Case 1: needs_begin_main_frame=false. - EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, + EXPECT_NE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE, state.NextAction()); // Case 2: needs_begin_main_frame=true. state.SetNeedsBeginMainFrame(); - EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, + EXPECT_NE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE, state.NextAction()) << state.AsValue()->ToString(); } @@ -848,8 +850,8 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginMainFrameState(begin_main_frame_states[i]); state.SetVisible(false); @@ -858,7 +860,7 @@ state.IssueNextBeginImplFrame(); state.SetCanDraw(false); - EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, + EXPECT_NE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE, state.NextAction()); } } @@ -870,8 +872,8 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetActiveTreeNeedsFirstDraw(true); @@ -879,18 +881,18 @@ state.SetNeedsRedraw(true); state.SetCanDraw(false); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestSetNeedsBeginMainFrameIsNotLost) { @@ -904,8 +906,8 @@ // Begin the frame. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); // Now, while the frame is in progress, set another commit. state.SetNeedsBeginMainFrame(); @@ -915,43 +917,43 @@ state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_MAIN_FRAME_STATE( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT); + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT); // Expect to commit regardless of BeginImplFrame state. EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME); - EXPECT_ACTION(SchedulerStateMachine::ACTION_COMMIT); + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::COMMIT); state.OnBeginImplFrameDeadline(); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); - EXPECT_ACTION(SchedulerStateMachine::ACTION_COMMIT); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); + EXPECT_ACTION(SchedulerStateMachine::Action::COMMIT); state.OnBeginImplFrameIdle(); - EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); - EXPECT_ACTION(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BeginImplFrameState::IDLE); + EXPECT_ACTION(SchedulerStateMachine::Action::COMMIT); state.IssueNextBeginImplFrame(); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME); - EXPECT_ACTION(SchedulerStateMachine::ACTION_COMMIT); + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::COMMIT); // Finish the commit and activate, then make sure we start the next commit // immediately and draw on the next BeginImplFrame. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); EXPECT_TRUE(state.active_tree_needs_first_draw()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestFullCycle) { @@ -965,38 +967,38 @@ // Begin the frame. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Tell the scheduler the frame finished. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_MAIN_FRAME_STATE( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT); + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT); // Commit. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); // Activate. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.active_tree_needs_first_draw()); EXPECT_TRUE(state.needs_redraw()); // Expect to do nothing until BeginImplFrame deadline - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // At BeginImplFrame deadline, draw. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); // Should be synchronized, no draw needed, no action needed. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); EXPECT_FALSE(state.needs_redraw()); } @@ -1011,12 +1013,12 @@ // Make a main frame, commit and activate it. But don't draw it. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); // Try to make a new main frame before drawing. Since we will commit it to a // pending tree and not clobber the active tree, we're able to start a new @@ -1024,10 +1026,10 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); } TEST(SchedulerStateMachineTest, DontCommitWithoutDrawWithoutPendingTree) { @@ -1043,18 +1045,18 @@ // Make a main frame, commit and activate it. But don't draw it. state.OnBeginImplFrame(0, 10); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); // Try to make a new main frame before drawing, but since we would clobber the // active tree, we will not do so. state.SetNeedsBeginMainFrame(); state.OnBeginImplFrame(0, 11); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, AbortedMainFrameDoesNotResetPendingTree) { @@ -1067,48 +1069,48 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.has_pending_tree()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Ask for another commit but abort it. Verify that we didn't reset pending // tree state. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.has_pending_tree()); state.NotifyBeginMainFrameStarted(); state.BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.has_pending_tree()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Ask for another commit that doesn't abort. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.has_pending_tree()); // Verify that commit is delayed until the pending tree is activated. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_FALSE(state.has_pending_tree()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.has_pending_tree()); } @@ -1125,68 +1127,68 @@ // Begin the frame. state.OnBeginImplFrame(0, 10); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Tell the scheduler the frame finished. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_MAIN_FRAME_STATE( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT); + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT); // Commit. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); // Commit always calls NotifyReadyToActivate in this mode. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // No draw because we haven't received NotifyReadyToDraw yet. state.OnBeginImplFrameDeadline(); EXPECT_TRUE(state.active_tree_needs_first_draw()); EXPECT_TRUE(state.needs_redraw()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Can't BeginMainFrame yet since last commit hasn't been drawn yet. state.SetNeedsBeginMainFrame(); state.OnBeginImplFrame(0, 11); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Now call ready to draw which will allow the draw to happen and // BeginMainFrame to be sent. state.NotifyReadyToDraw(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); // Submit throttled from this point. state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Can't BeginMainFrame yet since we're submit-frame throttled. state.OnBeginImplFrame(0, 12); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // CompositorFrameAck unblocks BeginMainFrame. state.DidReceiveCompositorFrameAck(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Draw the newly activated tree. state.NotifyReadyToDraw(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // When commits are deferred, we don't block the deadline. state.SetDeferCommits(true); state.OnBeginImplFrame(0, 13); - EXPECT_NE(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_NE(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); } @@ -1201,46 +1203,46 @@ // Begin the frame. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request another commit while the commit is in flight. state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Tell the scheduler the frame finished. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_MAIN_FRAME_STATE( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT); + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT); // First commit and activate. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.active_tree_needs_first_draw()); EXPECT_TRUE(state.needs_redraw()); // Expect to do nothing until BeginImplFrame deadline. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // At BeginImplFrame deadline, draw. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); // Should be synchronized, no draw needed, no action needed. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); EXPECT_FALSE(state.needs_redraw()); // Next BeginImplFrame should initiate second commit. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, TestNoRequestCommitWhenInvisible) { @@ -1248,13 +1250,13 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetVisible(false); state.SetNeedsBeginMainFrame(); EXPECT_FALSE(state.CouldSendBeginMainFrame()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestNoRequestCommitWhenBeginFrameSourcePaused) { @@ -1262,32 +1264,32 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginFrameSourcePaused(true); state.SetNeedsBeginMainFrame(); EXPECT_FALSE(state.CouldSendBeginMainFrame()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestNoRequestLayerTreeFrameSinkWhenInvisible) { SchedulerSettings default_scheduler_settings; StateMachine state(default_scheduler_settings); // We should not request a LayerTreeFrameSink when we are still invisible. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetVisible(false); state.DidLoseLayerTreeFrameSink(); state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); } // See ProxyMain::BeginMainFrame "EarlyOut_NotVisible" / @@ -1303,10 +1305,10 @@ // Begin the frame while visible. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Become invisible and abort BeginMainFrame. state.SetVisible(false); @@ -1318,30 +1320,30 @@ EXPECT_TRUE(state.NeedsCommit()); // We should now be back in the idle state as if we never started the frame. - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // We shouldn't do anything on the BeginImplFrame deadline. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Become visible again. state.SetVisible(true); // Although we have aborted on this frame and haven't cancelled the commit // (i.e. need another), don't send another BeginMainFrame yet. - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.NeedsCommit()); // Start a new frame. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); // We should be starting the commit now. - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } // See ProxyMain::BeginMainFrame "EarlyOut_NoUpdates" case. @@ -1350,8 +1352,8 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidCreateAndInitializeLayerTreeFrameSink(); state.SetCanDraw(true); @@ -1359,10 +1361,10 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); // Abort the commit, true means that the BeginMainFrame was sent but there // was no work to do on the main thread. @@ -1373,24 +1375,24 @@ EXPECT_FALSE(state.NeedsCommit()); // Since the commit was aborted, we don't need to try and draw. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify another commit doesn't start on another frame either. EXPECT_FALSE(state.NeedsCommit()); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify another commit can start if requested, though. state.SetNeedsBeginMainFrame(); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); state.IssueNextBeginImplFrame(); - EXPECT_ACTION(SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, TestFirstContextCreation) { @@ -1400,21 +1402,21 @@ state.SetCanDraw(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Check that the first init does not SetNeedsBeginMainFrame. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Check that a needs commit initiates a BeginMainFrame. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, TestContextLostWhenCompletelyIdle) { @@ -1422,16 +1424,16 @@ StateMachine state(default_scheduler_settings); SET_UP_STATE(state) - EXPECT_NE(SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION, + EXPECT_NE(SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION, state.NextAction()); state.DidLoseLayerTreeFrameSink(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Once context recreation begins, nothing should happen. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Recreate the context. state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); @@ -1439,7 +1441,7 @@ // When the context is recreated, we should begin a commit. state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, @@ -1448,92 +1450,92 @@ StateMachine state(default_scheduler_settings); SET_UP_STATE(state) - EXPECT_NE(SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION, + EXPECT_NE(SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION, state.NextAction()); state.DidLoseLayerTreeFrameSink(); EXPECT_EQ(state.layer_tree_frame_sink_state(), - SchedulerStateMachine::LAYER_TREE_FRAME_SINK_NONE); + SchedulerStateMachine::LayerTreeFrameSinkState::NONE); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Once context recreation begins, nothing should happen. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // While context is recreating, commits shouldn't begin. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Recreate the context state.DidCreateAndInitializeLayerTreeFrameSink(); EXPECT_EQ( state.layer_tree_frame_sink_state(), - SchedulerStateMachine::LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT); + SchedulerStateMachine::LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT); EXPECT_FALSE(state.RedrawPending()); // When the context is recreated, we wait until the next BeginImplFrame // before starting. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // When the BeginFrame comes in we should begin a commit state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); // Until that commit finishes, we shouldn't be drawing. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Finish the commit, which should make the surface active. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); EXPECT_EQ(state.layer_tree_frame_sink_state(), - SchedulerStateMachine:: - LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_ACTIVATION); + SchedulerStateMachine::LayerTreeFrameSinkState:: + WAITING_FOR_FIRST_ACTIVATION); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_EQ(state.layer_tree_frame_sink_state(), - SchedulerStateMachine::LAYER_TREE_FRAME_SINK_ACTIVE); + SchedulerStateMachine::LayerTreeFrameSinkState::ACTIVE); // Finishing the first commit after initializing a LayerTreeFrameSink should // automatically cause a redraw. EXPECT_TRUE(state.RedrawPending()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.RedrawPending()); // Next frame as no work to do. state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Once the context is recreated, whether we draw should be based on // SetCanDraw if waiting on first draw after activate. state.SetNeedsRedraw(true); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.SetCanDraw(false); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); state.SetCanDraw(true); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Once the context is recreated, whether we draw should be based on // SetCanDraw if waiting on first draw after activate. @@ -1541,24 +1543,24 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Activate so we need the first draw state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.active_tree_needs_first_draw()); EXPECT_TRUE(state.needs_redraw()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.SetCanDraw(false); - EXPECT_ACTION(SchedulerStateMachine::ACTION_DRAW_ABORT); + EXPECT_ACTION(SchedulerStateMachine::Action::DRAW_ABORT); state.SetCanDraw(true); - EXPECT_ACTION(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); } TEST(SchedulerStateMachineTest, TestContextLostWhileCommitInProgress) { @@ -1573,13 +1575,13 @@ state.SetNeedsRedraw(true); state.OnBeginImplFrame(0, 10); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Cause a lost context while the BeginMainFrame is in flight. state.DidLoseLayerTreeFrameSink(); @@ -1587,34 +1589,34 @@ // Ask for another draw. Expect nothing happens. state.SetNeedsRedraw(true); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); // Finish the frame, commit and activate. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); // We will abort the draw when the LayerTreeFrameSink is lost if we are // waiting for the first draw to unblock the main thread. EXPECT_TRUE(state.active_tree_needs_first_draw()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); - // Expect to begin context recreation only in BEGIN_IMPL_FRAME_STATE_IDLE - EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + // Expect to begin context recreation only in BeginImplFrameState::IDLE + EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BeginImplFrameState::IDLE); EXPECT_ACTION( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); state.OnBeginImplFrame(0, 11); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -1625,19 +1627,19 @@ // Get a commit in flight. state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Set damage and expect a draw. state.SetNeedsRedraw(true); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Cause a lost context while the BeginMainFrame is in flight. state.DidLoseLayerTreeFrameSink(); @@ -1645,56 +1647,56 @@ // Ask for another draw and also set needs commit. Expect nothing happens. state.SetNeedsRedraw(true); state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Finish the frame, and commit and activate. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.active_tree_needs_first_draw()); // Because the LayerTreeFrameSink is missing, we expect the draw to abort. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); - // Expect to begin context recreation only in BEGIN_IMPL_FRAME_STATE_IDLE - EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); + // Expect to begin context recreation only in BeginImplFrameState::IDLE + EXPECT_IMPL_FRAME_STATE(SchedulerStateMachine::BeginImplFrameState::IDLE); EXPECT_ACTION( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); state.IssueNextBeginImplFrame(); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_BEGIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); EXPECT_IMPL_FRAME_STATE( - SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::BeginImplFrameState::INSIDE_DEADLINE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameIdle(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); // After we get a new LayerTreeFrameSink, the commit flow should start. state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -1708,13 +1710,13 @@ // Cause a lost LayerTreeFrameSink, and restore it. state.DidLoseLayerTreeFrameSink(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidCreateAndInitializeLayerTreeFrameSink(); EXPECT_FALSE(state.RedrawPending()); state.IssueNextBeginImplFrame(); - EXPECT_ACTION(SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION(SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, @@ -1724,20 +1726,20 @@ SET_UP_STATE(state) state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::BeginMainFrameState::SENT); // Cause a lost context. state.DidLoseLayerTreeFrameSink(); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); EXPECT_TRUE(state.ShouldAbortCurrentFrame()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.PendingDrawsShouldBeAborted()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); } TEST(SchedulerStateMachineTest, TestNoBeginFrameNeededWhenInvisible) { @@ -1745,8 +1747,8 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); EXPECT_FALSE(state.BeginFrameNeeded()); @@ -1765,12 +1767,12 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetVisible(false); state.SetNeedsBeginMainFrame(); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.BeginFrameNeeded()); // When become visible again, the needs commit should still be pending. @@ -1778,7 +1780,7 @@ EXPECT_TRUE(state.BeginFrameNeeded()); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, TestFinishCommitWhenCommitInProgress) { @@ -1786,23 +1788,23 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetVisible(false); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::BeginMainFrameState::SENT); state.SetNeedsBeginMainFrame(); // After the commit completes, activation and draw happen immediately // because we are not visible. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.active_tree_needs_first_draw()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -1811,12 +1813,12 @@ StateMachine state(default_scheduler_settings); state.SetVisible(true); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.CreateAndInitializeLayerTreeFrameSinkWithActivatedCommit(); state.SetBeginFrameSourcePaused(true); state.SetBeginMainFrameState( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::BeginMainFrameState::SENT); state.SetNeedsBeginMainFrame(); // After the commit completes, activation and draw happen immediately @@ -1824,11 +1826,11 @@ state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_TRUE(state.ShouldAbortCurrentFrame()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); EXPECT_TRUE(state.active_tree_needs_first_draw()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_ABORT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_ABORT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestInitialActionsWhenContextLost) { @@ -1841,19 +1843,19 @@ // When we are visible, we normally want to begin LayerTreeFrameSink creation // as soon as possible. EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); state.DidCreateAndInitializeLayerTreeFrameSink(); EXPECT_EQ( state.layer_tree_frame_sink_state(), - SchedulerStateMachine::LAYER_TREE_FRAME_SINK_WAITING_FOR_FIRST_COMMIT); + SchedulerStateMachine::LayerTreeFrameSinkState::WAITING_FOR_FIRST_COMMIT); // We should not send a BeginMainFrame when we are invisible, even if we've // lost the LayerTreeFrameSink and are trying to get the first commit, since // the // main thread will just abort anyway. state.SetVisible(false); - EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, ReportIfNotDrawing) { @@ -1949,8 +1951,8 @@ // We should start the commit normally. EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Since only the scroll offset changed, the main thread will abort the // commit. @@ -1969,18 +1971,18 @@ state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); } @@ -1995,8 +1997,8 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify the deadline is not triggered early until we enter // prefer impl latency mode. @@ -2008,33 +2010,33 @@ // Trigger the deadline. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidReceiveCompositorFrameAck(); // Request a new commit and finish the previous one. state.SetNeedsBeginMainFrame(); FinishPreviousCommitAndDrawWithoutExitingDeadline(&state); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Finish the previous commit and draw it. FinishPreviousCommitAndDrawWithoutExitingDeadline(&state); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Verify we do not send another BeginMainFrame if was are submit-frame // throttled and did not just submit one. state.SetNeedsBeginMainFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -2047,12 +2049,12 @@ state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.DidLoseLayerTreeFrameSink(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // The deadline should be triggered immediately when LayerTreeFrameSink is // lost. EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); @@ -2067,12 +2069,12 @@ state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.SetVisible(false); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.ShouldAbortCurrentFrame()); EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); } @@ -2087,12 +2089,12 @@ state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_FALSE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.SetBeginFrameSourcePaused(true); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); EXPECT_TRUE(state.ShouldAbortCurrentFrame()); EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); } @@ -2106,18 +2108,18 @@ state.SetNeedsBeginMainFrame(); EXPECT_FALSE(state.BeginFrameNeeded()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.SetDeferCommits(false); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); } TEST(SchedulerStateMachineTest, EarlyOutCommitWantsProactiveBeginFrame) { @@ -2142,20 +2144,20 @@ // Set up the request for a commit and start a frame. state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); - PerformAction(&state, SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + PerformAction(&state, SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); // Lose the LayerTreeFrameSink. state.DidLoseLayerTreeFrameSink(); // The scheduler shouldn't trigger the LayerTreeFrameSink creation till the // previous commit has been cleared. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Trigger the deadline and ensure that the scheduler does not trigger any // actions until we receive a response for the pending commit. state.OnBeginImplFrameDeadline(); state.OnBeginImplFrameIdle(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Abort the commit, since that is what we expect the main thread to do if the // LayerTreeFrameSink was lost due to a synchronous call from the main thread @@ -2166,7 +2168,7 @@ // The scheduler should begin the LayerTreeFrameSink creation now. EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); } TEST(SchedulerStateMachineTest, NoImplSideInvalidationsWhileInvisible) { @@ -2180,7 +2182,7 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -2196,7 +2198,7 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -2211,7 +2213,7 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, @@ -2227,10 +2229,10 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, @@ -2244,23 +2246,23 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); bool needs_first_draw_on_activation = true; state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, @@ -2274,19 +2276,19 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); bool needs_first_draw_on_activation = true; state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, @@ -2299,27 +2301,27 @@ // initialized. state.DidLoseLayerTreeFrameSink(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // No impl-side invalidations should be performed during frame sink creation. bool needs_first_draw_on_activation = true; state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Initializing the LayerTreeFrameSink puts us in a state waiting for the // first commit. state.DidCreateAndInitializeLayerTreeFrameSink(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, ImplSideInvalidationWhenPendingTreeExists) { @@ -2331,32 +2333,32 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); // Request an impl-side invalidation after the commit. The request should wait // till the current pending tree is activated. bool needs_first_draw_on_activation = true; state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Activate the pending tree. Since the commit fills the impl-side // invalidation funnel as well, the request should wait until the next // BeginFrame. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Since there is no main frame request, this should perform impl-side // invalidations. state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, ImplSideInvalidationWhileReadyToCommit) { @@ -2369,26 +2371,26 @@ state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request an impl-side invalidation. The request should wait till a response // is received from the main thread. bool needs_first_draw_on_activation = true; state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); EXPECT_TRUE(state.needs_impl_side_invalidation()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Perform a commit, the impl-side invalidation request should be reset since // they will be merged with the commit. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); EXPECT_FALSE(state.needs_impl_side_invalidation()); // Deadline. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, @@ -2403,24 +2405,24 @@ state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request another invalidation, which should wait until the pending tree is // activated *and* we start the next BeginFrame. state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Now start the next frame, which will first draw the active tree and then // perform the pending impl-side invalidation request. state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, ImplSideInvalidationsThrottledOnDraw) { @@ -2435,17 +2437,17 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); state.NotifyReadyToDraw(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request impl-side invalidation and start a new frame, which should be // blocked on the ack for the previous frame. @@ -2453,7 +2455,7 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Ack the previous frame and begin impl frame, which should perform the // invalidation now. @@ -2461,7 +2463,7 @@ state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, @@ -2474,8 +2476,8 @@ state.SetNeedsBeginMainFrame(); state.IssueNextBeginImplFrame(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request an impl-side invalidation and trigger the deadline, the // invalidation should run if the request is still pending when we enter the @@ -2484,7 +2486,7 @@ state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); } TEST(SchedulerStateMachineTest, PrepareTilesWaitForImplSideInvalidation) { @@ -2501,9 +2503,9 @@ state.IssueNextBeginImplFrame(); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); state.DidPrepareTiles(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); } TEST(SchedulerStateMachineTest, TestFullPipelineMode) { @@ -2522,73 +2524,73 @@ // Begin the frame. state.OnBeginImplFrame(0, 10); // We are blocking because we need a main frame. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); // Even if main thread defers commits, we still need to wait for it. state.SetDeferCommits(true); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); state.SetDeferCommits(false); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_SENT); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::SENT); EXPECT_FALSE(state.NeedsCommit()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // We are blocking on the main frame. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); // Tell the scheduler the frame finished. state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); EXPECT_MAIN_FRAME_STATE( - SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_READY_TO_COMMIT); + SchedulerStateMachine::BeginMainFrameState::READY_TO_COMMIT); // We are blocking on commit. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); // Commit. - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); // We are blocking on activation. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // We should prepare tiles even though we are not in the deadline, otherwise // we would get stuck here. EXPECT_FALSE(state.ShouldPrepareTiles()); state.SetNeedsPrepareTiles(); EXPECT_TRUE(state.ShouldPrepareTiles()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_PREPARE_TILES); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::PREPARE_TILES); // Ready to activate, but not draw. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); // We should no longer block, because can_draw is still false, and we are no // longer waiting for activation. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::IMMEDIATE, state.CurrentBeginImplFrameDeadlineMode()); // However, we should continue to block on ready to draw if we can draw. state.SetCanDraw(true); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Ready to draw triggers immediate deadline. state.NotifyReadyToDraw(); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::IMMEDIATE, state.CurrentBeginImplFrameDeadlineMode()); state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // In full-pipe mode, CompositorFrameAck should always arrive before any // subsequent BeginFrame. state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request a redraw without main frame. state.SetNeedsRedraw(true); @@ -2596,20 +2598,20 @@ // Redraw should happen immediately since there is no pending tree and active // tree is ready to draw. state.OnBeginImplFrame(0, 11); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE, + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::IMMEDIATE, state.CurrentBeginImplFrameDeadlineMode()); // Redraw on impl-side only. state.OnBeginImplFrameDeadline(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); state.DidSubmitCompositorFrame(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // In full-pipe mode, CompositorFrameAck should always arrive before any // subsequent BeginFrame. state.DidReceiveCompositorFrameAck(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Request a redraw on active frame and a main frame. state.SetNeedsRedraw(true); @@ -2617,10 +2619,10 @@ state.OnBeginImplFrame(0, 12); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); // Blocked on main frame. - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); // Even with SMOOTHNESS_TAKES_PRIORITY, we don't prioritize impl thread and we @@ -2628,15 +2630,15 @@ state.SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_BLOCKED, + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); // Abort commit and ensure that we don't block anymore. state.NotifyBeginMainFrameStarted(); state.BeginMainFrameAborted(CommitEarlyOutReason::FINISHED_NO_UPDATES); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE); - EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE); - EXPECT_EQ(SchedulerStateMachine::BEGIN_IMPL_FRAME_DEADLINE_MODE_IMMEDIATE, + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE); + EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BeginMainFrameState::IDLE); + EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::IMMEDIATE, state.CurrentBeginImplFrameDeadlineMode()); } @@ -2652,23 +2654,23 @@ state.OnBeginImplFrame(0, 1); state.OnBeginImplFrameDeadline(); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION); + SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION); state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); state.OnBeginImplFrameIdle(); // Now we have a main frame. state.SetNeedsBeginMainFrame(); state.OnBeginImplFrame(0, 2); EXPECT_ACTION_UPDATE_STATE( - SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME); + SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME); state.NotifyBeginMainFrameStarted(); state.NotifyReadyToCommit(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_COMMIT); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT); // We should be able to activate this tree without drawing the active tree. state.NotifyReadyToActivate(); - EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_ACTIVATE_SYNC_TREE); + EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE); } } // namespace
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadItemView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadItemView.java index ca2f24a..fcf7671 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadItemView.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadItemView.java
@@ -56,7 +56,7 @@ // Controls for completed downloads. private View mLayoutCompleted; private TextView mFilenameCompletedView; - private TextView mDescriptionView; + private TextView mDescriptionCompletedView; // Controls for in-progress downloads. private View mLayoutInProgress; @@ -103,7 +103,7 @@ mLayoutInProgress = findViewById(R.id.progress_layout); mFilenameCompletedView = (TextView) findViewById(R.id.filename_completed_view); - mDescriptionView = (TextView) findViewById(R.id.description_view); + mDescriptionCompletedView = (TextView) findViewById(R.id.description_view); mFilenameInProgressView = (TextView) findViewById(R.id.filename_progress_view); mDownloadStatusView = (TextView) findViewById(R.id.status_view); @@ -189,13 +189,13 @@ if (mThumbnailBitmap == null) updateIconView(); - Context context = mDescriptionView.getContext(); + Context context = mDescriptionCompletedView.getContext(); mFilenameCompletedView.setText(item.getDisplayFileName()); mFilenameInProgressView.setText(item.getDisplayFileName()); String description = String.format(Locale.getDefault(), "%s - %s", Formatter.formatFileSize(context, item.getFileSize()), item.getDisplayHostname()); - mDescriptionView.setText(description); + mDescriptionCompletedView.setText(description); if (item.isComplete()) { showLayout(mLayoutCompleted);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OfflineGroupHeaderView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OfflineGroupHeaderView.java index df74132..7721d0d59 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OfflineGroupHeaderView.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/OfflineGroupHeaderView.java
@@ -41,9 +41,9 @@ private DownloadHistoryAdapter mAdapter; private DownloadItemSelectionDelegate mSelectionDelegate; - private TextView mDescriptionView; + private TextView mDescriptionTextView; private ImageView mExpandImage; - private TintedImageView mIconView; + private TintedImageView mIconImageView; public OfflineGroupHeaderView(Context context, AttributeSet attrs) { super(context, attrs); @@ -65,8 +65,8 @@ protected void onFinishInflate() { super.onFinishInflate(); - mIconView = (TintedImageView) findViewById(R.id.icon_view); - mDescriptionView = (TextView) findViewById(R.id.description); + mIconImageView = (TintedImageView) findViewById(R.id.icon_view); + mDescriptionTextView = (TextView) findViewById(R.id.description); mExpandImage = (ImageView) findViewById(R.id.expand_icon); } @@ -102,7 +102,7 @@ String description = String.format(Locale.getDefault(), "%s - %s", Formatter.formatFileSize(getContext(), header.getTotalFileSize()), getContext().getString(R.string.download_manager_offline_header_description)); - mDescriptionView.setText(description); + mDescriptionTextView.setText(description); updateExpandIcon(header.isExpanded()); setChecked(mSelectionDelegate.isHeaderSelected(header)); } @@ -117,26 +117,26 @@ private void updateCheckIcon(boolean checked) { if (checked) { if (FeatureUtilities.isChromeHomeEnabled()) { - mIconView.setBackgroundResource(mIconBackgroundResId); - mIconView.getBackground().setLevel( + mIconImageView.setBackgroundResource(mIconBackgroundResId); + mIconImageView.getBackground().setLevel( getResources().getInteger(R.integer.list_item_level_selected)); } else { - mIconView.setBackgroundColor(mIconBackgroundColorSelected); + mIconImageView.setBackgroundColor(mIconBackgroundColorSelected); } - mIconView.setImageResource(R.drawable.ic_check_googblue_24dp); - mIconView.setTint(mCheckedIconForegroundColorList); + mIconImageView.setImageResource(R.drawable.ic_check_googblue_24dp); + mIconImageView.setTint(mCheckedIconForegroundColorList); } else { if (FeatureUtilities.isChromeHomeEnabled()) { - mIconView.setBackgroundResource(mIconBackgroundResId); - mIconView.getBackground().setLevel( + mIconImageView.setBackgroundResource(mIconBackgroundResId); + mIconImageView.getBackground().setLevel( getResources().getInteger(R.integer.list_item_level_default)); } else { - mIconView.setBackgroundColor(mIconBackgroundColor); + mIconImageView.setBackgroundColor(mIconBackgroundColor); } - mIconView.setImageResource(R.drawable.ic_chrome); - mIconView.setTint(mIconForegroundColorList); + mIconImageView.setImageResource(R.drawable.ic_chrome); + mIconImageView.setTint(mIconForegroundColorList); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/OverviewListLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/OverviewListLayout.java index f1cc4b1..a23e574 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/widget/OverviewListLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/OverviewListLayout.java
@@ -34,7 +34,7 @@ */ public class OverviewListLayout extends Layout implements AccessibilityTabModelAdapterListener { private AccessibilityTabModelWrapper mTabModelWrapper; - private final float mDpToPx; + private final float mDensity; private final BlackHoleEventFilter mBlackHoleEventFilter; private final SceneLayer mSceneLayer; @@ -42,7 +42,7 @@ Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) { super(context, updateHost, renderHost); mBlackHoleEventFilter = new BlackHoleEventFilter(context); - mDpToPx = context.getResources().getDisplayMetrics().density; + mDensity = context.getResources().getDisplayMetrics().density; mSceneLayer = new SceneLayer(); } @@ -81,7 +81,7 @@ (FrameLayout.LayoutParams) mTabModelWrapper.getLayoutParams(); if (params == null) return; - int margin = (int) ((getHeight() - getHeightMinusBrowserControls()) * mDpToPx); + int margin = (int) ((getHeight() - getHeightMinusBrowserControls()) * mDensity); if (FeatureUtilities.isChromeHomeEnabled()) { params.bottomMargin = margin; } else {
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index 1fee6fc..685ffde 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp
@@ -3711,6 +3711,11 @@ Don't remind me again </message> + <!-- Obsolete versions Notification strings--> + <message name="IDS_UPDATE_REQUIRED_LOGIN_SCREEN_MESSAGE" desc="The message on login screen to inform the user that policy prevents user sign in before OS version is is updated."> + Your device is no longer compliant with the minimum client version specified by your admin. Please update to login. + </message> + <!-- Genius App --> <message name="IDS_GENIUS_APP_NAME" desc="Name of the genius app in the app shelf"> Get Help
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc index 8ef7b24e..db61e06b 100644 --- a/chrome/browser/android/chrome_feature_list.cc +++ b/chrome/browser/android/chrome_feature_list.cc
@@ -170,7 +170,7 @@ base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kCCTBackgroundTab{"CCTBackgroundTab", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kCCTExternalLinkHandling{"CCTExternalLinkHandling", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc index d7fc13e..3ad1b302 100644 --- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc +++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -10,7 +10,6 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/feature_list.h" -#include "base/memory/ptr_util.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string16.h" #include "base/strings/string_util.h" @@ -91,7 +90,7 @@ ZeroSuggestPrefetcher::ZeroSuggestPrefetcher(Profile* profile) : controller_(new AutocompleteController( - base::MakeUnique<ChromeAutocompleteProviderClient>(profile), + std::make_unique<ChromeAutocompleteProviderClient>(profile), this, AutocompleteProvider::TYPE_ZERO_SUGGEST)) { // Creating an arbitrary fake_request_source to avoid passing in an invalid @@ -128,7 +127,7 @@ AutocompleteControllerAndroid::AutocompleteControllerAndroid(Profile* profile) : autocomplete_controller_(new AutocompleteController( - base::WrapUnique(new ChromeAutocompleteProviderClient(profile)), + std::make_unique<ChromeAutocompleteProviderClient>(profile), this, AutocompleteClassifier::DefaultOmniboxProviders())), inside_synchronous_start_(false),
diff --git a/chrome/browser/chrome_browser_main_linux.cc b/chrome/browser/chrome_browser_main_linux.cc index f41c5a6..0b6ca73 100644 --- a/chrome/browser/chrome_browser_main_linux.cc +++ b/chrome/browser/chrome_browser_main_linux.cc
@@ -26,6 +26,7 @@ #if !defined(OS_CHROMEOS) #include "base/command_line.h" #include "base/linux_util.h" +#include "chrome/browser/dbus/dbus_thread_linux.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/common/chrome_switches.h" #include "components/os_crypt/key_storage_config_linux.h" @@ -70,9 +71,10 @@ parsed_command_line().GetSwitchValueASCII(switches::kPasswordStore); // Forward the product name config->product_name = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME); - // OSCrypt may target keyring, which requires calls from the main thread. + // OSCrypt may target backends, which require calls from specific threads. config->main_thread_runner = content::BrowserThread::GetTaskRunnerForThread( content::BrowserThread::UI); + config->dbus_task_runner = chrome::GetDBusTaskRunner(); // OSCrypt can be disabled in a special settings file. config->should_use_preference = parsed_command_line().HasSwitch(switches::kEnableEncryptionSelection);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index 2bfb1aa..d4d4e67 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -992,6 +992,9 @@ "login/screens/terms_of_service_screen.cc", "login/screens/terms_of_service_screen.h", "login/screens/terms_of_service_screen_view.h", + "login/screens/update_required_screen.cc", + "login/screens/update_required_screen.h", + "login/screens/update_required_view.h", "login/screens/update_screen.cc", "login/screens/update_screen.h", "login/screens/update_view.h",
diff --git a/chrome/browser/chromeos/login/app_launch_signin_screen.cc b/chrome/browser/chromeos/login/app_launch_signin_screen.cc index 8cbbd37..ee1e059 100644 --- a/chrome/browser/chromeos/login/app_launch_signin_screen.cc +++ b/chrome/browser/chromeos/login/app_launch_signin_screen.cc
@@ -126,6 +126,10 @@ NOTREACHED(); } +void AppLaunchSigninScreen::ShowUpdateRequiredScreen() { + NOTREACHED(); +} + void AppLaunchSigninScreen::ShowWrongHWIDScreen() { NOTREACHED(); }
diff --git a/chrome/browser/chromeos/login/app_launch_signin_screen.h b/chrome/browser/chromeos/login/app_launch_signin_screen.h index 5750123..116acea 100644 --- a/chrome/browser/chromeos/login/app_launch_signin_screen.h +++ b/chrome/browser/chromeos/login/app_launch_signin_screen.h
@@ -70,6 +70,7 @@ void ShowEnableDebuggingScreen() override; void ShowKioskEnableScreen() override; void ShowKioskAutolaunchScreen() override; + void ShowUpdateRequiredScreen() override; void ShowWrongHWIDScreen() override; void SetWebUIHandler(LoginDisplayWebUIHandler* webui_handler) override; bool IsShowGuest() const override;
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc index 73aea39..de5dd50 100644 --- a/chrome/browser/chromeos/login/existing_user_controller.cc +++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -347,6 +347,9 @@ kAccountsPrefDeviceLocalAccountAutoLoginDelay, base::Bind(&ExistingUserController::ConfigureAutoLogin, base::Unretained(this))); + minimum_version_policy_handler_ = + std::make_unique<policy::MinimumVersionPolicyHandler>(cros_settings_); + minimum_version_policy_handler_->AddObserver(this); } void ExistingUserController::Init(const user_manager::UserList& users) { @@ -466,11 +469,28 @@ void ExistingUserController::OnArcKioskAppsChanged() { ConfigureAutoLogin(); } + +//////////////////////////////////////////////////////////////////////////////// +// ExistingUserController, policy::MinimumVersionPolicyHandler::Observer +// implementation: +// + +void ExistingUserController::OnMinimumVersionStateChanged() { + if (is_login_in_progress_) { + // Too late, but there is another check in user session. + return; + } + if (!minimum_version_policy_handler_->RequirementsAreSatisfied()) { + ShowUpdateRequiredScreen(); + } +} + //////////////////////////////////////////////////////////////////////////////// // ExistingUserController, private: ExistingUserController::~ExistingUserController() { UserSessionManager::GetInstance()->DelegateDeleted(this); + minimum_version_policy_handler_->RemoveObserver(this); if (current_controller_ == this) { current_controller_ = nullptr; @@ -690,6 +710,10 @@ host_->StartWizard(OobeScreen::SCREEN_WRONG_HWID); } +void ExistingUserController::ShowUpdateRequiredScreen() { + host_->StartWizard(OobeScreen::SCREEN_UPDATE_REQUIRED); +} + void ExistingUserController::Signout() { NOTREACHED(); } @@ -1563,6 +1587,7 @@ login_display_->SetUIEnabled(true); return; } + // if () chromeos::DBusThreadManager::Get() ->GetCryptohomeClient()
diff --git a/chrome/browser/chromeos/login/existing_user_controller.h b/chrome/browser/chromeos/login/existing_user_controller.h index c50920b..e598523 100644 --- a/chrome/browser/chromeos/login/existing_user_controller.h +++ b/chrome/browser/chromeos/login/existing_user_controller.h
@@ -24,6 +24,7 @@ #include "chrome/browser/chromeos/login/session/user_session_manager.h" #include "chrome/browser/chromeos/login/signin/token_handle_util.h" #include "chrome/browser/chromeos/login/ui/login_display.h" +#include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h" #include "chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" @@ -67,7 +68,8 @@ public content::NotificationObserver, public LoginPerformer::Delegate, public UserSessionManagerDelegate, - public ArcKioskAppManager::ArcKioskAppManagerObserver { + public ArcKioskAppManager::ArcKioskAppManagerObserver, + public policy::MinimumVersionPolicyHandler::Observer { public: // All UI initialization is deferred till Init() call. explicit ExistingUserController(LoginDisplayHost* host); @@ -107,6 +109,7 @@ void SetDisplayAndGivenName(const std::string& display_name, const std::string& given_name) override; void ShowWrongHWIDScreen() override; + void ShowUpdateRequiredScreen() override; void Signout() override; bool IsUserWhitelisted(const AccountId& account_id) override; @@ -118,6 +121,9 @@ // ArcKioskAppManager::ArcKioskAppManagerObserver overrides. void OnArcKioskAppsChanged() override; + // policy::MinimumVersionPolicyHandler::Observer overrides. + void OnMinimumVersionStateChanged() override; + // Set a delegate that we will pass AuthStatusConsumer events to. // Used for testing. void set_login_status_consumer(AuthStatusConsumer* consumer) { @@ -404,6 +410,8 @@ local_account_auto_login_id_subscription_; std::unique_ptr<CrosSettings::ObserverSubscription> local_account_auto_login_delay_subscription_; + std::unique_ptr<policy::MinimumVersionPolicyHandler> + minimum_version_policy_handler_; std::unique_ptr<OAuth2TokenInitializer> oauth2_token_initializer_;
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc index bf6819d..124f663 100644 --- a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc +++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
@@ -355,6 +355,10 @@ NOTREACHED(); } +void WebUIScreenLocker::ShowUpdateRequiredScreen() { + NOTREACHED(); +} + void WebUIScreenLocker::ResetAutoLoginTimer() {} void WebUIScreenLocker::ResyncUserData() {
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.h b/chrome/browser/chromeos/login/lock/webui_screen_locker.h index 048a31a..363af938 100644 --- a/chrome/browser/chromeos/login/lock/webui_screen_locker.h +++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.h
@@ -99,6 +99,7 @@ void OnStartKioskEnableScreen() override; void OnStartKioskAutolaunchScreen() override; void ShowWrongHWIDScreen() override; + void ShowUpdateRequiredScreen() override; void ResetAutoLoginTimer() override; void ResyncUserData() override; void SetDisplayEmail(const std::string& email) override;
diff --git a/chrome/browser/chromeos/login/oobe_screen.cc b/chrome/browser/chromeos/login/oobe_screen.cc index 60a8b6f..0230cfdb 100644 --- a/chrome/browser/chromeos/login/oobe_screen.cc +++ b/chrome/browser/chromeos/login/oobe_screen.cc
@@ -50,6 +50,7 @@ "encryption-migration", // SCREEN_ENCRYPTION_MIGRATION "voice-interaction-value-prop", // SCREEN_VOICE_INTERACTION_VALUE_PROP "wait-for-container-ready", // SCREEN_WAIT_FOR_CONTAINTER_READY + "update-required", // SCREEN_UPDATE_REQUIRED "login", // SCREEN_SPECIAL_LOGIN "oobe", // SCREEN_SPECIAL_OOBE "test:nowindow", // SCREEN_TEST_NO_WINDOW
diff --git a/chrome/browser/chromeos/login/oobe_screen.h b/chrome/browser/chromeos/login/oobe_screen.h index b305d3e..07cde09 100644 --- a/chrome/browser/chromeos/login/oobe_screen.h +++ b/chrome/browser/chromeos/login/oobe_screen.h
@@ -48,6 +48,7 @@ SCREEN_ENCRYPTION_MIGRATION, SCREEN_VOICE_INTERACTION_VALUE_PROP, SCREEN_WAIT_FOR_CONTAINER_READY, + SCREEN_UPDATE_REQUIRED, // Special "first screen" that initiates login flow. SCREEN_SPECIAL_LOGIN,
diff --git a/chrome/browser/chromeos/login/screens/update_required_screen.cc b/chrome/browser/chromeos/login/screens/update_required_screen.cc new file mode 100644 index 0000000..f028409 --- /dev/null +++ b/chrome/browser/chromeos/login/screens/update_required_screen.cc
@@ -0,0 +1,48 @@ +// Copyright (c) 2017 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 "chrome/browser/chromeos/login/screens/update_required_screen.h" + +#include <algorithm> + +#include "base/bind.h" +#include "chrome/browser/chromeos/login/screens/base_screen_delegate.h" +#include "chrome/browser/chromeos/login/screens/update_required_view.h" + +namespace chromeos { + +UpdateRequiredScreen::UpdateRequiredScreen( + BaseScreenDelegate* base_screen_delegate, + UpdateRequiredView* view) + : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_UPDATE_REQUIRED), + view_(view), + weak_factory_(this) { + if (view_) + view_->Bind(this); +} + +UpdateRequiredScreen::~UpdateRequiredScreen() { + if (view_) + view_->Unbind(); +} + +void UpdateRequiredScreen::OnViewDestroyed(UpdateRequiredView* view) { + if (view_ == view) + view_ = nullptr; +} + +void UpdateRequiredScreen::Show() { + is_shown_ = true; + + if (view_) + view_->Show(); +} + +void UpdateRequiredScreen::Hide() { + if (view_) + view_->Hide(); + is_shown_ = false; +} + +} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/update_required_screen.h b/chrome/browser/chromeos/login/screens/update_required_screen.h new file mode 100644 index 0000000..87714e9 --- /dev/null +++ b/chrome/browser/chromeos/login/screens/update_required_screen.h
@@ -0,0 +1,50 @@ +// Copyright (c) 2017 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_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_SCREEN_H_ +#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_SCREEN_H_ + +#include <set> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/chromeos/login/screens/base_screen.h" +#include "chrome/browser/chromeos/login/screens/error_screen.h" + +namespace chromeos { + +class BaseScreenDelegate; +class UpdateRequiredView; + +// Controller for the update required screen. +class UpdateRequiredScreen : public BaseScreen { + public: + constexpr static OobeScreen kScreenId = OobeScreen::SCREEN_UPDATE_REQUIRED; + + UpdateRequiredScreen(BaseScreenDelegate* base_screen_delegate, + UpdateRequiredView* view); + ~UpdateRequiredScreen() override; + + // Called when the being destroyed. This should call Unbind() on the + // associated View if this class is destroyed before it. + void OnViewDestroyed(UpdateRequiredView* view); + + private: + // BaseScreen: + void Show() override; + void Hide() override; + + UpdateRequiredView* view_ = nullptr; + bool is_shown_; + + base::WeakPtrFactory<UpdateRequiredScreen> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(UpdateRequiredScreen); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_SCREEN_H_
diff --git a/chrome/browser/chromeos/login/screens/update_required_view.h b/chrome/browser/chromeos/login/screens/update_required_view.h new file mode 100644 index 0000000..afa3a432 --- /dev/null +++ b/chrome/browser/chromeos/login/screens/update_required_view.h
@@ -0,0 +1,38 @@ +// Copyright (c) 2017 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_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_VIEW_H_ +#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_VIEW_H_ + +#include "chrome/browser/chromeos/login/oobe_screen.h" + +namespace chromeos { + +class UpdateRequiredScreen; + +// Interface for dependency injection between UpdateRequiredScreen and its +// WebUI representation. + +class UpdateRequiredView { + public: + constexpr static OobeScreen kScreenId = OobeScreen::SCREEN_UPDATE_REQUIRED; + + virtual ~UpdateRequiredView() {} + + // Shows the contents of the screen. + virtual void Show() = 0; + + // Hides the contents of the screen. + virtual void Hide() = 0; + + // Binds |screen| to the view. + virtual void Bind(UpdateRequiredScreen* screen) = 0; + + // Unbinds the screen from the view. + virtual void Unbind() = 0; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_UPDATE_REQUIRED_VIEW_H_
diff --git a/chrome/browser/chromeos/login/ui/login_display.h b/chrome/browser/chromeos/login/ui/login_display.h index ca7f8d1..052381f 100644 --- a/chrome/browser/chromeos/login/ui/login_display.h +++ b/chrome/browser/chromeos/login/ui/login_display.h
@@ -81,6 +81,9 @@ // Called when the owner permission for kiosk app auto launch is requested. virtual void OnStartKioskAutolaunchScreen() = 0; + // Shows update required screen. + virtual void ShowUpdateRequiredScreen() = 0; + // Shows wrong HWID screen. virtual void ShowWrongHWIDScreen() = 0;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.h b/chrome/browser/chromeos/login/ui/login_display_host_webui.h index 9156155..26c75bc 100644 --- a/chrome/browser/chromeos/login/ui/login_display_host_webui.h +++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
@@ -129,10 +129,10 @@ // Overridden from ui::InputDeviceEventObserver void OnTouchscreenDeviceConfigurationChanged() override; - // Overriden from views::WidgetRemovalsObserver: + // Overridden from views::WidgetRemovalsObserver: void OnWillRemoveView(views::Widget* widget, views::View* view) override; - // Overriden from MultiUserWindowManager::Observer: + // Overridden from chrome::MultiUserWindowManager::Observer: void OnUserSwitchAnimationFinished() override; private:
diff --git a/chrome/browser/chromeos/login/ui/webui_login_display.cc b/chrome/browser/chromeos/login/ui/webui_login_display.cc index 952ee421..d5bc901d 100644 --- a/chrome/browser/chromeos/login/ui/webui_login_display.cc +++ b/chrome/browser/chromeos/login/ui/webui_login_display.cc
@@ -272,6 +272,11 @@ delegate_->OnStartKioskAutolaunchScreen(); } +void WebUILoginDisplay::ShowUpdateRequiredScreen() { + if (delegate_) + delegate_->ShowUpdateRequiredScreen(); +} + void WebUILoginDisplay::ShowWrongHWIDScreen() { if (delegate_) delegate_->ShowWrongHWIDScreen();
diff --git a/chrome/browser/chromeos/login/ui/webui_login_display.h b/chrome/browser/chromeos/login/ui/webui_login_display.h index 0ae1818f..37039c1 100644 --- a/chrome/browser/chromeos/login/ui/webui_login_display.h +++ b/chrome/browser/chromeos/login/ui/webui_login_display.h
@@ -73,6 +73,7 @@ void ShowEnableDebuggingScreen() override; void ShowKioskEnableScreen() override; void ShowKioskAutolaunchScreen() override; + void ShowUpdateRequiredScreen() override; void ShowWrongHWIDScreen() override; void SetWebUIHandler(LoginDisplayWebUIHandler* webui_handler) override; virtual void ShowSigninScreenForCreds(const std::string& username,
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc index 3bb6b07..dc33eb2 100644 --- a/chrome/browser/chromeos/login/wizard_controller.cc +++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -51,6 +51,7 @@ #include "chrome/browser/chromeos/login/screens/network_view.h" #include "chrome/browser/chromeos/login/screens/reset_screen.h" #include "chrome/browser/chromeos/login/screens/terms_of_service_screen.h" +#include "chrome/browser/chromeos/login/screens/update_required_screen.h" #include "chrome/browser/chromeos/login/screens/update_screen.h" #include "chrome/browser/chromeos/login/screens/user_image_screen.h" #include "chrome/browser/chromeos/login/screens/voice_interaction_value_prop_screen.h" @@ -451,8 +452,10 @@ } else if (screen == OobeScreen::SCREEN_WAIT_FOR_CONTAINER_READY) { return new WaitForContainerReadyScreen( this, oobe_ui_->GetWaitForContainerReadyScreenView()); + } else if (screen == OobeScreen::SCREEN_UPDATE_REQUIRED) { + return new UpdateRequiredScreen(this, + oobe_ui_->GetUpdateRequiredScreenView()); } - return nullptr; } @@ -672,6 +675,10 @@ SetCurrentScreen(GetScreen(OobeScreen::SCREEN_WAIT_FOR_CONTAINER_READY)); } +void WizardController::ShowUpdateRequiredScreen() { + SetCurrentScreen(GetScreen(OobeScreen::SCREEN_UPDATE_REQUIRED)); +} + void WizardController::SkipToLoginForTesting( const LoginScreenContext& context) { VLOG(1) << "SkipToLoginForTesting."; @@ -1166,6 +1173,8 @@ ShowVoiceInteractionValuePropScreen(); } else if (screen == OobeScreen::SCREEN_WAIT_FOR_CONTAINER_READY) { ShowWaitForContainerReadyScreen(); + } else if (screen == OobeScreen::SCREEN_UPDATE_REQUIRED) { + ShowUpdateRequiredScreen(); } else if (screen != OobeScreen::SCREEN_TEST_NO_WINDOW) { if (is_out_of_box_) { time_oobe_started_ = base::Time::Now();
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h index d968f3b..68276f8 100644 --- a/chrome/browser/chromeos/login/wizard_controller.h +++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -157,6 +157,7 @@ void ShowEncryptionMigrationScreen(); void ShowVoiceInteractionValuePropScreen(); void ShowWaitForContainerReadyScreen(); + void ShowUpdateRequiredScreen(); // Shows images login screen. void ShowLoginScreen(const LoginScreenContext& context);
diff --git a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc index 4b78f19..1e8fc59 100644 --- a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc +++ b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
@@ -926,12 +926,9 @@ if (data_reduction_proxy_io_data && previews_io_data) { previews::PreviewsUserData::Create(url_request, previews_io_data->GeneratePageId()); - if (data_reduction_proxy_io_data->ShouldEnableLoFi(*url_request, - previews_io_data)) { + if (data_reduction_proxy_io_data->ShouldEnableServerPreviews( + *url_request, previews_io_data)) { previews_state |= content::SERVER_LOFI_ON; - } - if (data_reduction_proxy_io_data->ShouldEnableLitePages(*url_request, - previews_io_data)) { previews_state |= content::SERVER_LITE_PAGE_ON; }
diff --git a/chrome/browser/media_router_resources.grdp b/chrome/browser/media_router_resources.grdp index 01200e3..4d61f45 100644 --- a/chrome/browser/media_router_resources.grdp +++ b/chrome/browser/media_router_resources.grdp
@@ -30,9 +30,6 @@ <include name="IDR_ROUTE_CONTROLS_HTML" file="resources\media_router\elements\route_controls\route_controls.html" type="BINDATA" /> <include name="IDR_ROUTE_CONTROLS_CSS" file="resources\media_router\elements\route_controls\route_controls.css" type="BINDATA" /> <include name="IDR_ROUTE_CONTROLS_JS" file="resources\media_router\elements\route_controls\route_controls.js" type="BINDATA" /> - <include name="IDR_EXTENSION_VIEW_WRAPPER_HTML" file="resources\media_router\elements\route_details\extension_view_wrapper\extension_view_wrapper.html" type="BINDATA" /> - <include name="IDR_EXTENSION_VIEW_WRAPPER_CSS" file="resources\media_router\elements\route_details\extension_view_wrapper\extension_view_wrapper.css" type="BINDATA" /> - <include name="IDR_EXTENSION_VIEW_WRAPPER_JS" file="resources\media_router\elements\route_details\extension_view_wrapper\extension_view_wrapper.js" type="BINDATA" /> <include name="IDR_ROUTE_DETAILS_HTML" file="resources\media_router\elements\route_details\route_details.html" type="BINDATA" /> <include name="IDR_ROUTE_DETAILS_CSS" file="resources\media_router\elements\route_details\route_details.css" type="BINDATA" /> <include name="IDR_ROUTE_DETAILS_JS" file="resources\media_router\elements\route_details\route_details.js" type="BINDATA" />
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html index 6a1fdf5..d7ef61c 100644 --- a/chrome/browser/resources/chromeos/login/custom_elements_login.html +++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -16,6 +16,7 @@ <include src="throbber_notice.html"> <include src="navigation_bar.html"> <include src="unrecoverable_cryptohome_error_card.html"> +<include src="update_required_card.html"> <include src="offline_ad_login.html"> <include src="active_directory_password_change.html"> <include src="arc_terms_of_service.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chrome/browser/resources/chromeos/login/custom_elements_login.js index dc682da..d2937fd 100644 --- a/chrome/browser/resources/chromeos/login/custom_elements_login.js +++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -16,6 +16,7 @@ // <include src="throbber_notice.js"> // <include src="navigation_bar.js"> // <include src="unrecoverable_cryptohome_error_card.js"> +// <include src="update_required_card.js"> // <include src="offline_ad_login.js"> // <include src="active_directory_password_change.js"> // <include src="oobe_buttons.js">
diff --git a/chrome/browser/resources/chromeos/login/login.js b/chrome/browser/resources/chromeos/login/login.js index 18af9ca..5e1858b 100644 --- a/chrome/browser/resources/chromeos/login/login.js +++ b/chrome/browser/resources/chromeos/login/login.js
@@ -54,6 +54,7 @@ login.EncryptionMigrationScreen.register(); login.VoiceInteractionValuePropScreen.register(); login.WaitForContainerReadyScreen.register(); + login.UpdateRequiredScreen.register(); cr.ui.Bubble.decorate($('bubble')); login.HeaderBar.decorate($('login-header-bar'));
diff --git a/chrome/browser/resources/chromeos/login/login_non_lock_shared.html b/chrome/browser/resources/chromeos/login/login_non_lock_shared.html index 2cc0e12..c77d3f7 100644 --- a/chrome/browser/resources/chromeos/login/login_non_lock_shared.html +++ b/chrome/browser/resources/chromeos/login/login_non_lock_shared.html
@@ -36,5 +36,6 @@ <link rel="stylesheet" href="screen_device_disabled.css"> <link rel="stylesheet" href="screen_unrecoverable_cryptohome_error.css"> <link rel="stylesheet" href="screen_active_directory_password_change.css"> +<link rel="stylesheet" href="screen_update_required.css"> <script src="chrome://oobe/keyboard_utils.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/login_non_lock_shared.js b/chrome/browser/resources/chromeos/login/login_non_lock_shared.js index 9da4f9a2..765a6bec 100644 --- a/chrome/browser/resources/chromeos/login/login_non_lock_shared.js +++ b/chrome/browser/resources/chromeos/login/login_non_lock_shared.js
@@ -34,6 +34,7 @@ // <include src="screen_unrecoverable_cryptohome_error.js"> // <include src="screen_active_directory_password_change.js"> // <include src="screen_encryption_migration.js"> +// <include src="screen_update_required.js"> // <include src="../../gaia_auth_host/authenticator.js">
diff --git a/chrome/browser/resources/chromeos/login/login_screens.html b/chrome/browser/resources/chromeos/login/login_screens.html index 85a72af..f32accf 100644 --- a/chrome/browser/resources/chromeos/login/login_screens.html +++ b/chrome/browser/resources/chromeos/login/login_screens.html
@@ -21,3 +21,4 @@ <include src="screen_unrecoverable_cryptohome_error.html"> <include src="screen_active_directory_password_change.html"> <include src="screen_encryption_migration.html"> +<include src="screen_update_required.html">
diff --git a/chrome/browser/resources/chromeos/login/md_login.js b/chrome/browser/resources/chromeos/login/md_login.js index 112bf30..c5b1e54 100644 --- a/chrome/browser/resources/chromeos/login/md_login.js +++ b/chrome/browser/resources/chromeos/login/md_login.js
@@ -54,6 +54,7 @@ login.EncryptionMigrationScreen.register(); login.VoiceInteractionValuePropScreen.register(); login.WaitForContainerReadyScreen.register(); + login.UpdateRequiredScreen.register(); cr.ui.Bubble.decorate($('bubble')); login.HeaderBar.decorate($('login-header-bar'));
diff --git a/chrome/browser/resources/chromeos/login/md_login_screens.html b/chrome/browser/resources/chromeos/login/md_login_screens.html index 06db7cf..7ef0aff 100644 --- a/chrome/browser/resources/chromeos/login/md_login_screens.html +++ b/chrome/browser/resources/chromeos/login/md_login_screens.html
@@ -21,3 +21,4 @@ <include src="screen_unrecoverable_cryptohome_error.html"> <include src="screen_active_directory_password_change.html"> <include src="screen_encryption_migration.html"> +<include src="screen_update_required.html">
diff --git a/chrome/browser/resources/chromeos/login/oobe_screens.html b/chrome/browser/resources/chromeos/login/oobe_screens.html index 7b3f79ed..47071ba 100644 --- a/chrome/browser/resources/chromeos/login/oobe_screens.html +++ b/chrome/browser/resources/chromeos/login/oobe_screens.html
@@ -27,3 +27,4 @@ <include src="screen_fatal_error.html"> <include src="screen_device_disabled.html"> <include src="screen_active_directory_password_change.html"> +<include src="screen_update_required.html">
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css b/chrome/browser/resources/chromeos/login/screen_update_required.css similarity index 68% rename from chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css rename to chrome/browser/resources/chromeos/login/screen_update_required.css index bd41df1..086ea95 100644 --- a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.css +++ b/chrome/browser/resources/chromeos/login/screen_update_required.css
@@ -2,8 +2,6 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ -#custom-controller { - display: inline-block; - height: 142px; - width: 100%; +#update-required { + width: 448px; /* Should be the same as #gaia-signin. */ }
diff --git a/chrome/browser/resources/chromeos/login/screen_update_required.html b/chrome/browser/resources/chromeos/login/screen_update_required.html new file mode 100644 index 0000000..f5a72042 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/screen_update_required.html
@@ -0,0 +1,9 @@ +<!-- Copyright 2017 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. --> + +<div class="step faded no-logo" id="update-required" hidden> + <update-required-card id="update-required-card"> + </update-required-card> +</div> +
diff --git a/chrome/browser/resources/chromeos/login/screen_update_required.js b/chrome/browser/resources/chromeos/login/screen_update_required.js new file mode 100644 index 0000000..e454e32 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/screen_update_required.js
@@ -0,0 +1,16 @@ +// Copyright 2017 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. + +/** + * @fileoverview "Update is required to sign in" screen. + */ + +login.createScreen('UpdateRequiredScreen', 'update-required', function() { + return { + /** @Override */ + onBeforeShow: function(data) { + Oobe.getInstance().headerHidden = true; + } + }; +});
diff --git a/chrome/browser/resources/chromeos/login/update_required_card.css b/chrome/browser/resources/chromeos/login/update_required_card.css new file mode 100644 index 0000000..dccd287 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/update_required_card.css
@@ -0,0 +1,12 @@ +/* Copyright 2016 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. */ + +:host .content { + padding: 24px 24px 16px; +} + +:host .message { + margin-bottom: 25px; + margin-top: 20px; +}
diff --git a/chrome/browser/resources/chromeos/login/update_required_card.html b/chrome/browser/resources/chromeos/login/update_required_card.html new file mode 100644 index 0000000..d581a88e0 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/update_required_card.html
@@ -0,0 +1,23 @@ +<!-- Copyright 2016 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/v1_0/polymer/polymer.html"> + +<!-- + Update required card that informs user that current chromeos version does not + satisfy policy requirements. + + Events: none +--> +<dom-module id="update-required-card"> + <link rel="stylesheet" href="oobe_flex_layout.css"> + <link rel="stylesheet" href="update_required_card.css"> + + <template> + <div class="content"> + <div class="message" i18n-content="updateRequiredMessage"> + </div> + </div> + </template> +</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/update_required_card.js b/chrome/browser/resources/chromeos/login/update_required_card.js new file mode 100644 index 0000000..ad7e012 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/update_required_card.js
@@ -0,0 +1,7 @@ +// Copyright 2016 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({ + is: 'update-required-card', +});
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.css b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css index 0fcf48a..c2e56bd6 100644 --- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.css +++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.css
@@ -60,7 +60,6 @@ #route-title { color: rgb(125, 125, 125); margin: 3px 8px; - overflow: hidden; } #route-volume-slider {
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.html b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html index f6184a8..b9ecc9eb 100644 --- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.html +++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.html
@@ -8,11 +8,6 @@ <link rel="import" type="css" href="route_controls.css"> <template> <div id="media-controls"> - <!-- - TODO(crbug.com/786208): Remove the div below and always render the - description in the details element. And, possibly combine details and - controls elements. - --> <div class="ellipsis" id="route-description" title="[[routeDescription_]]"> [[routeDescription_]]
diff --git a/chrome/browser/resources/media_router/elements/route_controls/route_controls.js b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js index ce15e1e..d84188e 100644 --- a/chrome/browser/resources/media_router/elements/route_controls/route_controls.js +++ b/chrome/browser/resources/media_router/elements/route_controls/route_controls.js
@@ -21,16 +21,6 @@ }, /** - * The route description to display. Uses the media route description if - * none is provided by the media route status object. - * @private {string} - */ - routeDescription_: { - type: String, - value: '', - }, - - /** * The volume shown in the volume control, between 0 and 1. * @private {number} */ @@ -50,15 +40,6 @@ }, /** - * Keep in sync with media remoting individual user setting. - * @private {boolean} - */ - mediaRemotingEnabled_: { - type: Boolean, - value: true, - }, - - /** * The timestamp for when the initial media status was loaded. * @private {number} */ @@ -116,6 +97,15 @@ }, /** + * Keep in sync with media remoting individual user setting. + * @private {boolean} + */ + mediaRemotingEnabled_: { + type: Boolean, + value: true, + }, + + /** * The route currently associated with this controller. * @type {?media_router.Route|undefined} */ @@ -125,6 +115,16 @@ }, /** + * The route description to display. Uses the media route description if + * none is provided by the media route status object. + * @private {string} + */ + routeDescription_: { + type: String, + value: '', + }, + + /** * The timestamp for when the route details view was opened. * @type {number} */
diff --git a/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp b/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp index ea2470c..d8eb228 100644 --- a/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp +++ b/chrome/browser/resources/media_router/elements/route_details/compiled_resources2.gyp
@@ -6,7 +6,6 @@ { 'target_name': 'route_details', 'dependencies': [ - 'extension_view_wrapper/compiled_resources2.gyp:extension_view_wrapper', '../../compiled_resources2.gyp:media_router_data', '../../compiled_resources2.gyp:media_router_ui_interface', '../route_controls/compiled_resources2.gyp:route_controls',
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp deleted file mode 100644 index b7d8fe1d..0000000 --- a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/compiled_resources2.gyp +++ /dev/null
@@ -1,14 +0,0 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -{ - 'targets': [ - { - 'target_name': 'extension_view_wrapper', - 'dependencies': [ - '../../../compiled_resources2.gyp:media_router_data', - ], - 'includes': ['../../../../../../../third_party/closure_compiler/compile_js2.gypi'], - }, - ], -}
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html deleted file mode 100644 index 9b33d90..0000000 --- a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.html +++ /dev/null
@@ -1,9 +0,0 @@ -<link rel="import" href="chrome://resources/html/polymer.html"> -<dom-module id="extension-view-wrapper"> - <link rel="import" type="css" href="extension_view_wrapper.css"> - <template> - <extensionview id="custom-controller"> - </extensionview> - </template> - <script src="extension_view_wrapper.js"></script> -</dom-module>
diff --git a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js b/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js deleted file mode 100644 index 8740e75..0000000 --- a/chrome/browser/resources/media_router/elements/route_details/extension_view_wrapper/extension_view_wrapper.js +++ /dev/null
@@ -1,77 +0,0 @@ -// Copyright 2017 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 Polymer element shows the custom controller for a route using -// extensionview. -Polymer({ - is: 'extension-view-wrapper', - - properties: { - /** - * Whether the extension view is ready to be shown. - * @type {boolean} - */ - isExtensionViewReady: { - type: Boolean, - value: false, - notify: true, - }, - - /** - * The route to show the custom controller for. - * @type {?media_router.Route|undefined} - */ - route: { - type: Object, - observer: 'maybeLoadExtensionView_', - }, - - /** - * The timestamp for when the route details view was opened. - * @type {number} - */ - routeDetailsOpenTime: { - type: Number, - value: 0, - }, - }, - - /** - * @return {?string} - */ - getCustomControllerPath_: function() { - if (!this.route || !this.route.customControllerPath) { - return null; - } - return this.route.customControllerPath + - '&requestTimestamp=' + this.routeDetailsOpenTime; - }, - - /** - * Loads the custom controller if the controller path for the current route is - * valid. - */ - maybeLoadExtensionView_: function() { - /** @const */ var extensionview = this.$['custom-controller']; - /** @const */ var controllerPath = this.getCustomControllerPath_(); - - // Do nothing if the controller path doesn't exist or is already shown in - // the extension view. - if (!controllerPath || controllerPath == extensionview.src) { - return; - } - - /** @const */ var that = this; - extensionview.load(controllerPath) - .then( - function() { - // Load was successful; show the custom controller. - that.isExtensionViewReady = true; - }, - function() { - // Load was unsuccessful; fall back to default view. - that.isExtensionViewReady = false; - }); - }, -});
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.html b/chrome/browser/resources/media_router/elements/route_details/route_details.html index 8d91b1be..a745fd2a 100644 --- a/chrome/browser/resources/media_router/elements/route_details/route_details.html +++ b/chrome/browser/resources/media_router/elements/route_details/route_details.html
@@ -1,24 +1,16 @@ <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> -<link rel="import" href="extension_view_wrapper/extension_view_wrapper.html"> <link rel="import" href="../route_controls/route_controls.html"> <dom-module id="route-details"> <link rel="import" type="css" href="../../media_router_common.css"> <link rel="import" type="css" href="route_details.css"> <template> <div class="ellipsis" id="route-description" title="[[routeDescription_]]" - hidden$="[[!shouldShowRouteDescription_(controllerType_)]]"> + hidden$="[[shouldShowWebUiControls_(route)]]"> [[routeDescription_]] </div> - <template is="dom-if" if="[[shouldAttemptLoadingExtensionView_(route)]]"> - <extension-view-wrapper id="extension-view-wrapper" route="[[route]]" - route-details-open-time="[[openTime_]]" - is-extension-view-ready="{{isExtensionViewReady}}" - hidden$="[[!shouldShowExtensionView_(controllerType_)]]"> - </extension-view-wrapper> - </template> - <template is="dom-if" if="[[shouldShowWebUiControls_(controllerType_)]]"> + <template is="dom-if" if="[[shouldShowWebUiControls_(route)]]"> <route-controls id="route-controls" route-details-open-time="[[openTime_]]" route="[[route]]"></route-controls>
diff --git a/chrome/browser/resources/media_router/elements/route_details/route_details.js b/chrome/browser/resources/media_router/elements/route_details/route_details.js index 8724cd2..a91b589 100644 --- a/chrome/browser/resources/media_router/elements/route_details/route_details.js +++ b/chrome/browser/resources/media_router/elements/route_details/route_details.js
@@ -28,15 +28,6 @@ }, /** - * An enum value to represent the controller to show. - * @private {number} - */ - controllerType_: { - type: Number, - computed: 'computeControllerType_(route, isExtensionViewReady)', - }, - - /** * Whether a sink is currently launching in the container. * @type {boolean} */ @@ -46,15 +37,6 @@ }, /** - * Whether the custom controller extensionview is ready to be shown. - * @type {boolean} - */ - isExtensionViewReady: { - type: Boolean, - value: false, - }, - - /** * The timestamp for when the route details view was opened. We initialize * the value in a function so that the value is set when the element is * loaded, rather than at page load. @@ -150,22 +132,6 @@ }, /** - * @param {?media_router.Route} route - * @param {boolean} isExtensionViewReady - * @return {number} An enum value to represent the controller to show. - * @private - */ - computeControllerType_: function(route, isExtensionViewReady) { - if (route && route.supportsWebUiController) { - return media_router.ControllerType.WEBUI; - } - if (isExtensionViewReady) { - return media_router.ControllerType.EXTENSION; - } - return media_router.ControllerType.NONE; - }, - - /** * @param {number} castMode User selected cast mode or AUTO. * @param {?media_router.Sink} sink Sink to which we will cast. * @return {number} The selected cast mode when |castMode| is selected in the @@ -191,20 +157,10 @@ }, /** - * Updates |routeDescription_| for the default view. - * - * @private - */ - updateRouteDescription_: function() { - this.routeDescription_ = this.route ? this.route.description : ''; - }, - - /** * Called when the route details view is closed. Resets route-controls. */ onClosed: function() { - if (this.controllerType_ === media_router.ControllerType.WEBUI && - this.$$('route-controls')) { + if (this.$$('route-controls')) { this.$$('route-controls').reset(); } }, @@ -213,62 +169,29 @@ * Called when the route details view is opened. */ onOpened: function() { - if (this.controllerType_ === media_router.ControllerType.WEBUI && - this.$$('route-controls')) { + if (this.$$('route-controls')) { media_router.ui.setRouteControls( /** @type {RouteControlsInterface} */ (this.$$('route-controls'))); } }, /** - * Updates either the extensionview or the WebUI route controller, depending - * on which should be shown. - * @param {?media_router.Route} newRoute + * Updates |routeDescription_| for the default view. + * @param {?media_router.Route} route * @private */ - onRouteChange_: function(newRoute) { - if (this.controllerType_ !== media_router.ControllerType.WEBUI) { - this.updateRouteDescription_(); - } + onRouteChange_: function(route) { + this.routeDescription_ = route ? route.description : ''; }, /** * @param {?media_router.Route} route - * @return {boolean} - * @private - */ - shouldAttemptLoadingExtensionView_: function(route) { - return !!route && !route.supportsWebUiController; - }, - - /** - * @param {number} controllerType - * @return {boolean} Whether the extensionview should be shown instead of the - * default route info element or the WebUI route controller. - * @private - */ - shouldShowExtensionView_: function(controllerType) { - return controllerType === media_router.ControllerType.EXTENSION; - }, - - /** - * @param {number} controllerType - * @return {boolean} Whether the route info element should be shown instead of - * the extensionview or the WebUI route controller. - * @private - */ - shouldShowRouteDescription_: function(controllerType) { - return controllerType === media_router.ControllerType.NONE; - }, - - /** - * @param {number} controllerType * @return {boolean} Whether the WebUI route controller should be shown - * instead of the default route info element or the extensionview. + * instead of the default route description element. * @private */ - shouldShowWebUiControls_: function(controllerType) { - return controllerType === media_router.ControllerType.WEBUI; + shouldShowWebUiControls_: function(route) { + return route && route.supportsWebUiController; }, /**
diff --git a/chrome/browser/resources/media_router/media_router_common.css b/chrome/browser/resources/media_router/media_router_common.css index d944684..ee07e9d 100644 --- a/chrome/browser/resources/media_router/media_router_common.css +++ b/chrome/browser/resources/media_router/media_router_common.css
@@ -21,12 +21,8 @@ } .ellipsis { + overflow: hidden; padding: 0 1%; text-overflow: ellipsis; white-space: nowrap; } - -#route-description { - background-color: white; - overflow: hidden; -}
diff --git a/chrome/browser/resources/media_router/media_router_data.js b/chrome/browser/resources/media_router/media_router_data.js index bcb1b16..a3d4861f 100644 --- a/chrome/browser/resources/media_router/media_router_data.js +++ b/chrome/browser/resources/media_router/media_router_data.js
@@ -25,16 +25,6 @@ }; /** - * Route controller types that can be shown in the route details view. - * @enum {number} - */ -media_router.ControllerType = { - NONE: 0, - WEBUI: 1, - EXTENSION: 2, -}; - -/** * The ESC key maps to KeyboardEvent.key value 'Escape'. * @const {string} */
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc index e33dc8a..de2be261 100644 --- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.cc
@@ -123,6 +123,11 @@ chrome_cleaner::kEngineSwitch, reporter_engine.empty() ? "1" : reporter_engine); + if (reporter_invocation.cleaner_logs_upload_enabled()) { + cleaner_command_line_.AppendSwitch( + chrome_cleaner::kWithScanningModeLogsSwitch); + } + // If metrics is enabled, we can enable crash reporting in the Chrome Cleaner // process. if (metrics_status == ChromeMetricsStatus::kEnabled) {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc index c4ebea6..1b1e3ed 100644 --- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win_unittest.cc
@@ -56,17 +56,21 @@ // enabled // - reporter_engine (ReporterEngine): the type of Cleaner engine specified in // the SwReporterInvocation. +// - cleaner_logs_enabled (bool): if logs can be collected in the cleaner +// process running in scanning mode. class ChromeCleanerRunnerSimpleTest : public testing::TestWithParam< std::tuple<ChromeCleanerRunner::ChromeMetricsStatus, - ReporterEngine>>, + ReporterEngine, + bool>>, public ChromeCleanerRunnerTestDelegate { public: ChromeCleanerRunnerSimpleTest() : command_line_(base::CommandLine::NO_PROGRAM) {} void SetUp() override { - std::tie(metrics_status_, reporter_engine_) = GetParam(); + std::tie(metrics_status_, reporter_engine_, cleaner_logs_enabled_) = + GetParam(); SetChromeCleanerRunnerTestDelegateForTesting(this); } @@ -88,6 +92,8 @@ break; } + reporter_invocation.set_cleaner_logs_upload_enabled(cleaner_logs_enabled_); + ChromeCleanerRunner::RunChromeCleanerAndReplyWithExitCode( base::FilePath(FILE_PATH_LITERAL("cleaner.exe")), reporter_invocation, metrics_status_, @@ -132,6 +138,7 @@ // Test fixture parameters. ChromeCleanerRunner::ChromeMetricsStatus metrics_status_; ReporterEngine reporter_engine_; + bool cleaner_logs_enabled_ = false; // Set by LaunchTestProcess. base::CommandLine command_line_; @@ -172,6 +179,9 @@ EXPECT_EQ( metrics_status_ == ChromeMetricsStatus::kEnabled, command_line_.HasSwitch(chrome_cleaner::kEnableCrashReportingSwitch)); + EXPECT_EQ( + cleaner_logs_enabled_, + command_line_.HasSwitch(chrome_cleaner::kWithScanningModeLogsSwitch)); } INSTANTIATE_TEST_CASE_P( @@ -181,7 +191,8 @@ ChromeCleanerRunner::ChromeMetricsStatus::kDisabled), Values(ReporterEngine::kUnspecified, ReporterEngine::kOldEngine, - ReporterEngine::kNewEngine))); + ReporterEngine::kNewEngine), + Bool())); // Enum to be used as parameter for the ChromeCleanerRunnerTest fixture below. enum class UwsFoundState {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc index c29b2b0..a365947 100644 --- a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
@@ -522,7 +522,7 @@ // Scans and shows the Chrome Cleaner UI if the user has not already been // prompted in the current prompt wave. void MaybeScanAndPrompt(SwReporterInvocationType invocation_type, - const SwReporterInvocation& reporter_invocation) { + SwReporterInvocation reporter_invocation) { ChromeCleanerController* cleaner_controller = ChromeCleanerController::GetInstance(); @@ -562,6 +562,10 @@ return; } + reporter_invocation.set_cleaner_logs_upload_enabled( + invocation_type == + SwReporterInvocationType::kUserInitiatedWithLogsAllowed); + cleaner_controller->Scan(reporter_invocation); DCHECK_EQ(ChromeCleanerController::State::kScanning, cleaner_controller->state()); @@ -1009,6 +1013,15 @@ reporter_logs_upload_enabled_ = reporter_logs_upload_enabled; } +bool SwReporterInvocation::cleaner_logs_upload_enabled() const { + return cleaner_logs_upload_enabled_; +} + +void SwReporterInvocation::set_cleaner_logs_upload_enabled( + bool cleaner_logs_upload_enabled) { + cleaner_logs_upload_enabled_ = cleaner_logs_upload_enabled; +} + SwReporterInvocationSequence::SwReporterInvocationSequence( const base::Version& version, const Queue& container,
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h index 101c30d..a4dec16d 100644 --- a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h
@@ -111,6 +111,11 @@ bool reporter_logs_upload_enabled() const; void set_reporter_logs_upload_enabled(bool reporter_logs_upload_enabled); + // Indicates if the invocation type allows logs to be uploaded by the + // cleaner process in scanning mode. + bool cleaner_logs_upload_enabled() const; + void set_cleaner_logs_upload_enabled(bool cleaner_logs_upload_enabled); + private: base::CommandLine command_line_; @@ -119,6 +124,7 @@ std::string suffix_; bool reporter_logs_upload_enabled_ = false; + bool cleaner_logs_upload_enabled_ = false; }; enum class SwReporterInvocationResult {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 63385d3..26a7a14 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1436,6 +1436,8 @@ "webui/chromeos/login/supervised_user_creation_screen_handler.h", "webui/chromeos/login/terms_of_service_screen_handler.cc", "webui/chromeos/login/terms_of_service_screen_handler.h", + "webui/chromeos/login/update_required_screen_handler.cc", + "webui/chromeos/login/update_required_screen_handler.h", "webui/chromeos/login/update_screen_handler.cc", "webui/chromeos/login/update_screen_handler.h", "webui/chromeos/login/user_board_screen_handler.cc",
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc index 353c154..e95c6d5d 100644 --- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc +++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -57,6 +57,7 @@ #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.h" +#include "chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/update_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/user_board_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/user_image_screen_handler.h" @@ -341,6 +342,8 @@ AddScreenHandler(base::MakeUnique<WaitForContainerReadyScreenHandler>()); + AddScreenHandler(base::MakeUnique<UpdateRequiredScreenHandler>()); + // Initialize KioskAppMenuHandler. Note that it is NOT a screen handler. auto kiosk_app_menu_handler = base::MakeUnique<KioskAppMenuHandler>(network_state_informer_); @@ -464,6 +467,10 @@ return GetView<WaitForContainerReadyScreenHandler>(); } +UpdateRequiredView* OobeUI::GetUpdateRequiredScreenView() { + return GetView<UpdateRequiredScreenHandler>(); +} + UserImageView* OobeUI::GetUserImageView() { return GetView<UserImageScreenHandler>(); }
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h index fb7ad5c..c0ad51d 100644 --- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h +++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
@@ -58,6 +58,7 @@ class UserBoardView; class UserImageView; class UpdateView; +class UpdateRequiredView; class VoiceInteractionValuePropScreenView; class WaitForContainerReadyScreenView; class WrongHWIDScreenView; @@ -119,6 +120,7 @@ EncryptionMigrationScreenView* GetEncryptionMigrationScreenView(); VoiceInteractionValuePropScreenView* GetVoiceInteractionValuePropScreenView(); WaitForContainerReadyScreenView* GetWaitForContainerReadyScreenView(); + UpdateRequiredView* GetUpdateRequiredScreenView(); GaiaView* GetGaiaScreenView(); UserBoardView* GetUserBoardView();
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc index 848a9da..1a6188f 100644 --- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc +++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -60,6 +60,7 @@ #include "chrome/browser/chromeos/login/wizard_controller.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/device_local_account.h" +#include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/system/system_clock.h" @@ -159,6 +160,12 @@ DISALLOW_COPY_AND_ASSIGN(CallOnReturn); }; +policy::MinimumVersionPolicyHandler* GetMinimumVersionPolicyHandler() { + return g_browser_process->platform_part() + ->browser_policy_connector_chromeos() + ->GetMinimumVersionPolicyHandler(); +} + } // namespace namespace chromeos { @@ -1292,6 +1299,12 @@ return; } + if (delegate_ && !oobe_ui_ && GetMinimumVersionPolicyHandler() && + !GetMinimumVersionPolicyHandler()->RequirementsAreSatisfied()) { + delegate_->ShowUpdateRequiredScreen(); + return; + } + PrefService* prefs = g_browser_process->local_state(); if (prefs->GetBoolean(prefs::kFactoryResetRequested)) { if (core_oobe_view_)
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h index e4dd6c5..680ab51 100644 --- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h +++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -170,6 +170,9 @@ // Show wrong hwid screen. virtual void ShowWrongHWIDScreen() = 0; + // Show update required screen. + virtual void ShowUpdateRequiredScreen() = 0; + // Sets the displayed email for the next login attempt. If it succeeds, // user's displayed email value will be updated to |email|. virtual void SetDisplayEmail(const std::string& email) = 0;
diff --git a/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.cc new file mode 100644 index 0000000..acdac707 --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.cc
@@ -0,0 +1,67 @@ +// Copyright (c) 2012 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 "chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h" + +#include <memory> + +#include "base/values.h" +#include "chrome/browser/chromeos/login/oobe_screen.h" +#include "chrome/browser/chromeos/login/screens/update_required_screen.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "components/login/localized_values_builder.h" + +namespace { + +const char kJsScreenPath[] = "login.UpdateRequiredScreen"; + +} // namespace + +namespace chromeos { + +UpdateRequiredScreenHandler::UpdateRequiredScreenHandler() + : BaseScreenHandler(kScreenId) { + set_call_js_prefix(kJsScreenPath); +} + +UpdateRequiredScreenHandler::~UpdateRequiredScreenHandler() { + if (screen_) + screen_->OnViewDestroyed(this); +} + +void UpdateRequiredScreenHandler::DeclareLocalizedValues( + ::login::LocalizedValuesBuilder* builder) { + builder->Add("updateRequiredMessage", + IDS_UPDATE_REQUIRED_LOGIN_SCREEN_MESSAGE); +} + +void UpdateRequiredScreenHandler::Initialize() { + if (show_on_init_) { + Show(); + show_on_init_ = false; + } +} + +void UpdateRequiredScreenHandler::Show() { + if (!page_is_ready()) { + show_on_init_ = true; + return; + } + ShowScreen(kScreenId); +} + +void UpdateRequiredScreenHandler::Hide() {} + +void UpdateRequiredScreenHandler::Bind(UpdateRequiredScreen* screen) { + screen_ = screen; + BaseScreenHandler::SetBaseScreen(screen_); +} + +void UpdateRequiredScreenHandler::Unbind() { + screen_ = nullptr; + BaseScreenHandler::SetBaseScreen(nullptr); +} + +} // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h new file mode 100644 index 0000000..a4d8083 --- /dev/null +++ b/chrome/browser/ui/webui/chromeos/login/update_required_screen_handler.h
@@ -0,0 +1,45 @@ +// Copyright (c) 2012 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_CHROMEOS_LOGIN_UPDATE_REQUIRED_SCREEN_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_UPDATE_REQUIRED_SCREEN_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "chrome/browser/chromeos/login/screens/update_required_screen.h" +#include "chrome/browser/chromeos/login/screens/update_required_view.h" +#include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h" + +namespace chromeos { + +class UpdateRequiredScreenHandler : public UpdateRequiredView, + public BaseScreenHandler { + public: + UpdateRequiredScreenHandler(); + ~UpdateRequiredScreenHandler() override; + + private: + void Show() override; + void Hide() override; + void Bind(UpdateRequiredScreen* screen) override; + void Unbind() override; + + // BaseScreenHandler: + void DeclareLocalizedValues( + ::login::LocalizedValuesBuilder* builder) override; + void Initialize() override; + + UpdateRequiredScreen* screen_ = nullptr; + + // If true, Initialize() will call Show(). + bool show_on_init_ = false; + + DISALLOW_COPY_AND_ASSIGN(UpdateRequiredScreenHandler); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_UPDATE_REQUIRED_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc b/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc index 79861a0..bd29d23 100644 --- a/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc +++ b/chrome/browser/ui/webui/media_router/media_router_resources_provider.cc
@@ -81,17 +81,6 @@ html_source->AddResourcePath( "elements/media_router_container/pseudo_sink_search_state.js", IDR_PSEUDO_SINK_SEARCH_STATE_JS); - html_source->AddResourcePath( - "elements/route_details/extension_view_wrapper/" - "extension_view_wrapper.html", - IDR_EXTENSION_VIEW_WRAPPER_HTML); - html_source->AddResourcePath( - "elements/route_details/extension_view_wrapper/extension_view_wrapper.js", - IDR_EXTENSION_VIEW_WRAPPER_JS); - html_source->AddResourcePath( - "elements/route_details/extension_view_wrapper/" - "extension_view_wrapper.css", - IDR_EXTENSION_VIEW_WRAPPER_CSS); } } // namespace
diff --git a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc index 2f6ac26..25b9963 100644 --- a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc +++ b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
@@ -158,17 +158,14 @@ bool can_join, const std::string& extension_id, bool incognito, - int current_cast_mode, - bool is_web_ui_route_controller_available) { + int current_cast_mode) { auto dictionary = base::MakeUnique<base::DictionaryValue>(); dictionary->SetString("id", route.media_route_id()); dictionary->SetString("sinkId", route.media_sink_id()); dictionary->SetString("description", route.description()); dictionary->SetBoolean("isLocal", route.is_local()); - dictionary->SetBoolean( - "supportsWebUiController", - is_web_ui_route_controller_available && - route.controller_type() != RouteControllerType::kNone); + dictionary->SetBoolean("supportsWebUiController", + route.controller_type() != RouteControllerType::kNone); dictionary->SetBoolean("canJoin", can_join); if (current_cast_mode > 0) { dictionary->SetInteger("currentCastMode", current_cast_mode); @@ -261,8 +258,6 @@ : incognito_( Profile::FromWebUI(media_router_ui->web_ui())->IsOffTheRecord()), dialog_closing_(false), - is_web_ui_route_controller_available_(base::FeatureList::IsEnabled( - features::kMediaRouterUIRouteController)), media_router_ui_(media_router_ui) {} MediaRouterWebUIMessageHandler::~MediaRouterWebUIMessageHandler() {} @@ -305,7 +300,7 @@ route->media_route_id(), media_router_ui_->routes_and_cast_modes()); std::unique_ptr<base::DictionaryValue> route_value(RouteToValue( *route, false, media_router_ui_->GetRouteProviderExtensionId(), - incognito_, current_cast_mode, is_web_ui_route_controller_available_)); + incognito_, current_cast_mode)); web_ui()->CallJavascriptFunctionUnsafe(kOnCreateRouteResponseReceived, base::Value(sink_id), *route_value, base::Value(route->for_display())); @@ -1132,9 +1127,8 @@ base::ContainsValue(joinable_route_ids, route.media_route_id()); int current_cast_mode = CurrentCastModeForRouteId(route.media_route_id(), current_cast_modes); - std::unique_ptr<base::DictionaryValue> route_val( - RouteToValue(route, can_join, extension_id, incognito_, - current_cast_mode, is_web_ui_route_controller_available_)); + std::unique_ptr<base::DictionaryValue> route_val(RouteToValue( + route, can_join, extension_id, incognito_, current_cast_mode)); value->Append(std::move(route_val)); }
diff --git a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h index dd82fa2a..43e0f44 100644 --- a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h +++ b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h
@@ -166,9 +166,6 @@ // Keeps track of whether a command to close the dialog has been issued. bool dialog_closing_; - // Whether the WebUI version of route controller is available for use. - const bool is_web_ui_route_controller_available_; - // The media status currently shown in the UI. base::Optional<MediaStatus> current_media_status_;
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index c1e994b..984b4d49 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -329,12 +329,6 @@ // during Cast Tab Mirroring. const base::Feature kMediaRemoting{"MediaRemoting", base::FEATURE_DISABLED_BY_DEFAULT}; - -// If enabled, replaces the <extensionview> controller in the route details view -// of the Media Router dialog with the controller bundled with the WebUI -// resources. -const base::Feature kMediaRouterUIRouteController{ - "MediaRouterUIRouteController", base::FEATURE_ENABLED_BY_DEFAULT}; #endif // !defined(OS_ANDROID) // Enables or disables modal permission prompts.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h index 2904b22..8bab90e 100644 --- a/chrome/common/chrome_features.h +++ b/chrome/common/chrome_features.h
@@ -169,7 +169,6 @@ #if !defined(OS_ANDROID) extern const base::Feature kMediaRemoting; -extern const base::Feature kMediaRouterUIRouteController; #endif extern const base::Feature kModalPermissionPrompts;
diff --git a/chrome/test/data/webui/media_router/route_details_tests.js b/chrome/test/data/webui/media_router/route_details_tests.js index 9ccd085..e376721 100644 --- a/chrome/test/data/webui/media_router/route_details_tests.js +++ b/chrome/test/data/webui/media_router/route_details_tests.js
@@ -73,19 +73,6 @@ .hasAttribute('hidden')); }; - // Checks the custom controller is shown. - var checkCustomControllerIsShown = function() { - assertTrue(details.$$('#route-description').hasAttribute('hidden')); - assertFalse( - details.$$('extension-view-wrapper').hasAttribute('hidden')); - }; - - // Checks whether |expected| and the text in the |elementId| element - // are equal given an id. - var checkElementTextWithId = function(expected, elementId) { - assertEquals(expected, details.$$('#' + elementId).innerText); - }; - // Import route_details.html before running suite. suiteSetup(function() { return PolymerTest.importHtml( @@ -213,49 +200,6 @@ checkDefaultViewIsShown(); checkStartCastButtonIsShown(); }); - - // Tests when |route| exists, has a custom controller, and it loads. - test('route has custom controller and loading succeeds', function(done) { - // Get the extension-view-wrapper stamped first, so that we can mock out - // the load method. - details.route = fakeRouteTwo; - - setTimeout(function() { - details.$$('extension-view-wrapper').$$('#custom-controller').load = - function(url) { - setTimeout(function() { - assertEquals( - fakeRouteOneControllerPath, - url.substring(0, fakeRouteOneControllerPath.length)); - checkCustomControllerIsShown(); - done(); - }); - return Promise.resolve(); - }; - - details.route = fakeRouteOne; - }); - }); - - // Tests when |route| exists, has a custom controller, but fails to load. - test('route has custom controller but loading fails', function(done) { - // Get the extension-view-wrapper stamped first, so that we can mock out - // the load method. - details.route = fakeRouteTwo; - - setTimeout(function() { - details.$$('extension-view-wrapper').$$('#custom-controller').load = - function(url) { - setTimeout(function() { - checkDefaultViewIsShown(); - done(); - }); - return Promise.reject(); - }; - - details.route = fakeRouteOne; - }); - }); }); }
diff --git a/chromecast/media/cma/backend/fuchsia/cast_media_shlib_fuchsia.cc b/chromecast/media/cma/backend/fuchsia/cast_media_shlib_fuchsia.cc index eb11b6e0b..cbf1e049 100644 --- a/chromecast/media/cma/backend/fuchsia/cast_media_shlib_fuchsia.cc +++ b/chromecast/media/cma/backend/fuchsia/cast_media_shlib_fuchsia.cc
@@ -41,9 +41,8 @@ } // namespace void CastMediaShlib::Initialize(const std::vector<std::string>& argv) { - // Sets logging to display process and thread ID. - logging::SetLogItems(true, true, false, false); - chromecast::InitCommandLineShlib(argv); + // On Fuchsia CastMediaShlib is compiled statically with cast_shell, so |argv| + // can be ignored. g_video_plane = new DefaultVideoPlane();
diff --git a/components/chrome_cleaner/public/constants/constants.cc b/components/chrome_cleaner/public/constants/constants.cc index 11ccb72..f20082ba 100644 --- a/components/chrome_cleaner/public/constants/constants.cc +++ b/components/chrome_cleaner/public/constants/constants.cc
@@ -13,6 +13,7 @@ const char kChromePromptSwitch[] = "chrome-prompt"; const char kChromeSystemInstallSwitch[] = "chrome-system-install"; const char kChromeVersionSwitch[] = "chrome-version"; +const char kWithScanningModeLogsSwitch[] = "with-scanning-mode-logs"; const char kEnableCrashReportingSwitch[] = "enable-crash-reporting"; const char kEngineSwitch[] = "engine"; const char kExecutionModeSwitch[] = "execution-mode";
diff --git a/components/chrome_cleaner/public/constants/constants.h b/components/chrome_cleaner/public/constants/constants.h index 4b96dd5..62c6280 100644 --- a/components/chrome_cleaner/public/constants/constants.h +++ b/components/chrome_cleaner/public/constants/constants.h
@@ -33,6 +33,11 @@ // The Chrome version string. extern const char kChromeVersionSwitch[]; +// Identify that the cleaner process in scanning mode is allowed to collect +// logs. This should only be set if |kExecutionModeSwitch| is +// ExecutionMode::kScanning. +extern const char kWithScanningModeLogsSwitch[]; + // Indicates that crash reporting is enabled for the current user. extern const char kEnableCrashReportingSwitch[];
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc index 54bb095..312d5bf 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
@@ -542,17 +542,7 @@ warmup_url_fetcher_->FetchWarmupURL(); } -bool DataReductionProxyConfig::ShouldEnableLoFi( - const net::URLRequest& request, - const previews::PreviewsDecider& previews_decider) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK((request.load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) != 0); - DCHECK(!request.url().SchemeIsCryptographic()); - - return ShouldAcceptServerPreview(request, previews_decider); -} - -bool DataReductionProxyConfig::ShouldEnableLitePages( +bool DataReductionProxyConfig::ShouldEnableServerPreviews( const net::URLRequest& request, const previews::PreviewsDecider& previews_decider) { DCHECK(thread_checker_.CalledOnValidThread());
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h index abd2f3a..2a6d182 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
@@ -37,7 +37,7 @@ class URLRequest; class URLRequestContextGetter; class URLRequestStatus; -} +} // namespace net namespace previews { class PreviewsDecider; @@ -173,18 +173,13 @@ virtual bool ContainsDataReductionProxy( const net::ProxyConfig::ProxyRules& proxy_rules) const; - // Returns true when Lo-Fi Previews should be activated. Records metrics for - // Lo-Fi state changes. |request| is used to get the network quality estimator - // from the URLRequestContext. |previews_decider| is used to check if - // |request| is locally blacklisted. - bool ShouldEnableLoFi(const net::URLRequest& request, - const previews::PreviewsDecider& previews_decider); - - // Returns true when Lite Page Previews should be activated. |request| is used - // to get the network quality estimator from the URLRequestContext. - // |previews_decider| is used to check if |request| is locally blacklisted. - bool ShouldEnableLitePages(const net::URLRequest& request, - const previews::PreviewsDecider& previews_decider); + // Returns true when server previews should be activated. Records metrics for + // previews state changes. |request| is used to get the network quality + // estimator from the URLRequestContext. |previews_decider| is used to check + // if |request| is locally blacklisted. + bool ShouldEnableServerPreviews( + const net::URLRequest& request, + const previews::PreviewsDecider& previews_decider); // Returns true if the data saver has been enabled by the user, and the data // saver proxy is reachable. @@ -226,8 +221,7 @@ friend class TestDataReductionProxyConfig; FRIEND_TEST_ALL_PREFIXES(DataReductionProxyConfigTest, TestSetProxyConfigsHoldback); - FRIEND_TEST_ALL_PREFIXES(DataReductionProxyConfigTest, - AreProxiesBypassed); + FRIEND_TEST_ALL_PREFIXES(DataReductionProxyConfigTest, AreProxiesBypassed); FRIEND_TEST_ALL_PREFIXES(DataReductionProxyConfigTest, AreProxiesBypassedRetryDelay); FRIEND_TEST_ALL_PREFIXES(DataReductionProxyConfigTest, WarmupURL); @@ -276,11 +270,10 @@ // reduction proxies in min_retry_delay (if not NULL). If there are no // bypassed data reduction proxies for the request scheme, returns false and // does not assign min_retry_delay. - bool AreProxiesBypassed( - const net::ProxyRetryInfoMap& retry_map, - const net::ProxyConfig::ProxyRules& proxy_rules, - bool is_https, - base::TimeDelta* min_retry_delay) const; + bool AreProxiesBypassed(const net::ProxyRetryInfoMap& retry_map, + const net::ProxyConfig::ProxyRules& proxy_rules, + bool is_https, + base::TimeDelta* min_retry_delay) const; // Returns whether the request is blacklisted (or if Lo-Fi is disabled). bool IsBlackListedOrDisabled(
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc index 205b456..1b5f58a3 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
@@ -936,7 +936,7 @@ } } -TEST_F(DataReductionProxyConfigTest, ShouldEnableLoFi) { +TEST_F(DataReductionProxyConfigTest, ShouldEnableServerPreviews) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature( features::kDataReductionProxyDecidesTransform); @@ -949,33 +949,12 @@ net::LOAD_MAIN_FRAME_DEPRECATED); std::unique_ptr<TestPreviewsDecider> previews_decider = base::MakeUnique<TestPreviewsDecider>(true); - EXPECT_TRUE( - test_config()->ShouldEnableLoFi(*request.get(), *previews_decider.get())); + EXPECT_TRUE(test_config()->ShouldEnableServerPreviews( + *request.get(), *previews_decider.get())); previews_decider = base::MakeUnique<TestPreviewsDecider>(false); - EXPECT_FALSE(test_config()->ShouldEnableLitePages(*request.get(), - *previews_decider.get())); -} - -TEST_F(DataReductionProxyConfigTest, ShouldEnableLitePages) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature( - features::kDataReductionProxyDecidesTransform); - - net::TestURLRequestContext context_; - net::TestDelegate delegate_; - std::unique_ptr<net::URLRequest> request = context_.CreateRequest( - GURL(), net::IDLE, &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); - request->SetLoadFlags(request->load_flags() | - net::LOAD_MAIN_FRAME_DEPRECATED); - std::unique_ptr<TestPreviewsDecider> previews_decider = - base::MakeUnique<TestPreviewsDecider>(true); - EXPECT_TRUE(test_config()->ShouldEnableLitePages(*request.get(), - *previews_decider.get())); - - previews_decider = base::MakeUnique<TestPreviewsDecider>(false); - EXPECT_FALSE(test_config()->ShouldEnableLitePages(*request.get(), - *previews_decider.get())); + EXPECT_FALSE(test_config()->ShouldEnableServerPreviews( + *request.get(), *previews_decider.get())); } TEST_F(DataReductionProxyConfigTest, ShouldAcceptServerPreview) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc index 51fcbf3..b55c538 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc
@@ -286,7 +286,7 @@ config_client_->ApplySerializedConfig(serialized_config); } -bool DataReductionProxyIOData::ShouldEnableLoFi( +bool DataReductionProxyIOData::ShouldEnableServerPreviews( const net::URLRequest& request, previews::PreviewsDecider* previews_decider) { DCHECK(previews_decider); @@ -295,19 +295,7 @@ request, configurator_->GetProxyConfig()))) { return false; } - return config_->ShouldEnableLoFi(request, *previews_decider); -} - -bool DataReductionProxyIOData::ShouldEnableLitePages( - const net::URLRequest& request, - previews::PreviewsDecider* previews_decider) { - DCHECK(previews_decider); - DCHECK((request.load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) != 0); - if (!config_ || (config_->IsBypassedByDataReductionProxyLocalRules( - request, configurator_->GetProxyConfig()))) { - return false; - } - return config_->ShouldEnableLitePages(request, *previews_decider); + return config_->ShouldEnableServerPreviews(request, *previews_decider); } void DataReductionProxyIOData::UpdateDataUseForHost(int64_t network_bytes,
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h index e5e1aaae..8a1e32a 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h
@@ -101,21 +101,12 @@ // Applies a serialized Data Reduction Proxy configuration. void SetDataReductionProxyConfiguration(const std::string& serialized_config); - // Returns true when Lo-Fi Previews should be activated. When Lo-Fi is - // active, URL requests are modified to request low fidelity versions of the - // resources, except when the user is in the Lo-Fi control group. - // |previews_decider| is a non-null object that determines eligibility of - // showing the preview based on past opt outs. - bool ShouldEnableLoFi(const net::URLRequest& request, - previews::PreviewsDecider* previews_decider); - - // Returns true when Lite Page Previews should be activated. When Lite Pages - // are active, a low fidelity transcoded page is requested on the main frame - // resource, except when the user is in the control group. |previews_decider| - // is a non-null object that determines eligibility of showing the preview - // based on past opt outs. - bool ShouldEnableLitePages(const net::URLRequest& request, - previews::PreviewsDecider* previews_decider); + // Returns true when server previews should be activated. When server previews + // are active, URL requests are modified to request low fidelity versions of + // the resources.|previews_decider| is a non-null object that determines + // eligibility of showing the preview based on past opt outs. + bool ShouldEnableServerPreviews(const net::URLRequest& request, + previews::PreviewsDecider* previews_decider); // Bridge methods to safely call to the UI thread objects. void UpdateDataUseForHost(int64_t network_bytes,
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc index 528cdff..8d83f28 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
@@ -981,7 +981,7 @@ std::unique_ptr<net::URLRequest> fake_request = context()->CreateRequest( GURL(kTestURL), net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); fake_request->SetLoadFlags(net::LOAD_MAIN_FRAME_DEPRECATED); - lofi_decider()->SetIsUsingLoFi(config()->ShouldEnableLoFi( + lofi_decider()->SetIsUsingLoFi(config()->ShouldEnableServerPreviews( *fake_request.get(), test_previews_decider)); NotifyNetworkDelegate(fake_request.get(), data_reduction_proxy_info, proxy_retry_info, &headers); @@ -989,8 +989,8 @@ VerifyHeaders(tests[i].is_data_reduction_proxy, true, headers); VerifyDataReductionProxyData( *fake_request, tests[i].is_data_reduction_proxy, - config()->ShouldEnableLoFi(*fake_request.get(), - test_previews_decider)); + config()->ShouldEnableServerPreviews(*fake_request.get(), + test_previews_decider)); } { @@ -1064,14 +1064,14 @@ std::unique_ptr<net::URLRequest> fake_request = context()->CreateRequest( GURL(kTestURL), net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); fake_request->SetLoadFlags(net::LOAD_MAIN_FRAME_DEPRECATED); - lofi_decider()->SetIsUsingLoFi(config()->ShouldEnableLoFi( + lofi_decider()->SetIsUsingLoFi(config()->ShouldEnableServerPreviews( *fake_request.get(), test_previews_decider)); NotifyNetworkDelegate(fake_request.get(), data_reduction_proxy_info, proxy_retry_info, &headers); VerifyDataReductionProxyData( *fake_request, tests[i].is_data_reduction_proxy, - config()->ShouldEnableLoFi(*fake_request.get(), - test_previews_decider)); + config()->ShouldEnableServerPreviews(*fake_request.get(), + test_previews_decider)); } } } @@ -1361,8 +1361,8 @@ // Needed as a parameter, but functionality is not tested. TestPreviewsDecider test_previews_decider; - lofi_decider()->SetIsUsingLoFi( - config()->ShouldEnableLoFi(*fake_request.get(), test_previews_decider)); + lofi_decider()->SetIsUsingLoFi(config()->ShouldEnableServerPreviews( + *fake_request.get(), test_previews_decider)); fake_request = (FetchURLRequest(GURL(kTestURL), nullptr, response_headers, kResponseContentLength, 0));
diff --git a/components/os_crypt/key_storage_config_linux.h b/components/os_crypt/key_storage_config_linux.h index 6199cbd..78cbcf8 100644 --- a/components/os_crypt/key_storage_config_linux.h +++ b/components/os_crypt/key_storage_config_linux.h
@@ -33,6 +33,9 @@ bool should_use_preference; // Preferences are stored in a separate file in the user data directory. base::FilePath user_data_path; + // Communication with the backend via dbus needs to happen on a specific + // thread. Currently, only KWallet needs to use dbus. + scoped_refptr<base::SequencedTaskRunner> dbus_task_runner; private: DISALLOW_COPY_AND_ASSIGN(Config);
diff --git a/components/os_crypt/key_storage_kwallet.cc b/components/os_crypt/key_storage_kwallet.cc index 53f00c1..ae029a2 100644 --- a/components/os_crypt/key_storage_kwallet.cc +++ b/components/os_crypt/key_storage_kwallet.cc
@@ -8,12 +8,18 @@ #include "base/base64.h" #include "base/rand_util.h" +#include "base/sequenced_task_runner.h" #include "components/os_crypt/kwallet_dbus.h" #include "dbus/bus.h" -KeyStorageKWallet::KeyStorageKWallet(base::nix::DesktopEnvironment desktop_env, - std::string app_name) - : desktop_env_(desktop_env), handle_(-1), app_name_(std::move(app_name)) {} +KeyStorageKWallet::KeyStorageKWallet( + base::nix::DesktopEnvironment desktop_env, + std::string app_name, + scoped_refptr<base::SequencedTaskRunner> dbus_task_runner) + : desktop_env_(desktop_env), + handle_(-1), + app_name_(std::move(app_name)), + dbus_task_runner_(dbus_task_runner) {} KeyStorageKWallet::~KeyStorageKWallet() { // The handle is shared between programs that are using the same wallet. @@ -23,7 +29,12 @@ kwallet_dbus_->GetSessionBus()->ShutdownAndBlock(); } +base::SequencedTaskRunner* KeyStorageKWallet::GetTaskRunner() { + return dbus_task_runner_.get(); +} + bool KeyStorageKWallet::Init() { + DCHECK(dbus_task_runner_->RunsTasksInCurrentSequence()); // Initialize using the production KWalletDBus. return InitWithKWalletDBus(nullptr); } @@ -80,6 +91,8 @@ } std::string KeyStorageKWallet::GetKeyImpl() { + DCHECK(dbus_task_runner_->RunsTasksInCurrentSequence()); + // Get handle KWalletDBus::Error error = kwallet_dbus_->Open(wallet_name_, app_name_, &handle_);
diff --git a/components/os_crypt/key_storage_kwallet.h b/components/os_crypt/key_storage_kwallet.h index 209b2fa..d747f3f 100644 --- a/components/os_crypt/key_storage_kwallet.h +++ b/components/os_crypt/key_storage_kwallet.h
@@ -12,10 +12,15 @@ #include "components/os_crypt/key_storage_linux.h" #include "components/os_crypt/kwallet_dbus.h" +namespace base { +class SequencedTaskRunner; +} + class KeyStorageKWallet : public KeyStorageLinux { public: KeyStorageKWallet(base::nix::DesktopEnvironment desktop_env, - std::string app_name); + std::string app_name, + scoped_refptr<base::SequencedTaskRunner> dbus_task_runner); ~KeyStorageKWallet() override; // Initialize using an optional KWalletDBus mock. @@ -25,6 +30,7 @@ protected: // KeyStorageLinux + base::SequencedTaskRunner* GetTaskRunner() override; bool Init() override; std::string GetKeyImpl() override; @@ -46,6 +52,7 @@ std::string wallet_name_; const std::string app_name_; std::unique_ptr<KWalletDBus> kwallet_dbus_; + scoped_refptr<base::SequencedTaskRunner> dbus_task_runner_; DISALLOW_COPY_AND_ASSIGN(KeyStorageKWallet); };
diff --git a/components/os_crypt/key_storage_kwallet_unittest.cc b/components/os_crypt/key_storage_kwallet_unittest.cc index 9f9b7da..68ff9698 100644 --- a/components/os_crypt/key_storage_kwallet_unittest.cc +++ b/components/os_crypt/key_storage_kwallet_unittest.cc
@@ -6,6 +6,7 @@ #include "base/logging.h" #include "base/nix/xdg_util.h" +#include "base/test/test_simple_task_runner.h" #include "dbus/message.h" #include "dbus/mock_bus.h" #include "dbus/mock_object_proxy.h" @@ -95,7 +96,9 @@ class KeyStorageKWalletTest : public testing::Test { public: - KeyStorageKWalletTest() : key_storage_kwallet_(kDesktopEnv, "test-app") {} + KeyStorageKWalletTest() + : task_runner_(base::MakeRefCounted<base::TestSimpleTaskRunner>()), + key_storage_kwallet_(kDesktopEnv, "test-app", task_runner_) {} void SetUp() override { kwallet_dbus_mock_ = new StrictMock<MockKWalletDBus>(); @@ -120,6 +123,7 @@ protected: StrictMock<MockKWalletDBus>* kwallet_dbus_mock_; + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; KeyStorageKWallet key_storage_kwallet_; const std::string wallet_name_ = "mollet"; @@ -230,7 +234,8 @@ : public testing::TestWithParam<KWalletDBus::Error> { public: KeyStorageKWalletFailuresTest() - : key_storage_kwallet_(kDesktopEnv, "test-app") {} + : task_runner_(new base::TestSimpleTaskRunner()), + key_storage_kwallet_(kDesktopEnv, "test-app", task_runner_) {} void SetUp() override { // |key_storage_kwallet_| will take ownership of |kwallet_dbus_mock_|. @@ -255,6 +260,7 @@ protected: StrictMock<MockKWalletDBus>* kwallet_dbus_mock_; + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; KeyStorageKWallet key_storage_kwallet_; const std::string wallet_name_ = "mollet";
diff --git a/components/os_crypt/key_storage_linux.cc b/components/os_crypt/key_storage_linux.cc index 3be8806..3a04ce5 100644 --- a/components/os_crypt/key_storage_linux.cc +++ b/components/os_crypt/key_storage_linux.cc
@@ -80,7 +80,7 @@ #if defined(USE_LIBSECRET) if (selected_backend == os_crypt::SelectedLinuxBackend::GNOME_ANY || selected_backend == os_crypt::SelectedLinuxBackend::GNOME_LIBSECRET) { - key_storage.reset(new KeyStorageLibsecret()); + key_storage = std::make_unique<KeyStorageLibsecret>(); if (key_storage->WaitForInitOnTaskRunner()) { VLOG(1) << "OSCrypt using Libsecret as backend."; return key_storage; @@ -91,7 +91,8 @@ #if defined(USE_KEYRING) if (selected_backend == os_crypt::SelectedLinuxBackend::GNOME_ANY || selected_backend == os_crypt::SelectedLinuxBackend::GNOME_KEYRING) { - key_storage.reset(new KeyStorageKeyring(config.main_thread_runner)); + key_storage = + std::make_unique<KeyStorageKeyring>(config.main_thread_runner); if (key_storage->WaitForInitOnTaskRunner()) { VLOG(1) << "OSCrypt using Keyring as backend."; return key_storage; @@ -107,8 +108,8 @@ selected_backend == os_crypt::SelectedLinuxBackend::KWALLET ? base::nix::DESKTOP_ENVIRONMENT_KDE4 : base::nix::DESKTOP_ENVIRONMENT_KDE5; - key_storage.reset( - new KeyStorageKWallet(used_desktop_env, config.product_name)); + key_storage = std::make_unique<KeyStorageKWallet>( + used_desktop_env, config.product_name, config.dbus_task_runner); if (key_storage->WaitForInitOnTaskRunner()) { VLOG(1) << "OSCrypt using KWallet as backend."; return key_storage;
diff --git a/content/common/content_switches_internal.cc b/content/common/content_switches_internal.cc index 1033dd0..5e8ebae 100644 --- a/content/common/content_switches_internal.cc +++ b/content/common/content_switches_internal.cc
@@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/feature_list.h" #include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_params.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" @@ -56,6 +57,9 @@ base::FEATURE_DISABLED_BY_DEFAULT}; #endif +const base::Feature kSavePreviousDocumentResources{ + "SavePreviousDocumentResources", base::FEATURE_DISABLED_BY_DEFAULT}; + #if defined(OS_WIN) base::string16 ToNativeString(base::StringPiece string) { @@ -157,6 +161,14 @@ return SavePreviousDocumentResources::UNTIL_ON_DOM_CONTENT_LOADED; if (save_previous_document_resources == "onload") return SavePreviousDocumentResources::UNTIL_ON_LOAD; + // The command line, which is set by the user, takes priority. Otherwise, + // fall back to the field trial. + std::string until = base::GetFieldTrialParamValueByFeature( + kSavePreviousDocumentResources, "until"); + if (until == "onDOMContentLoaded") + return SavePreviousDocumentResources::UNTIL_ON_DOM_CONTENT_LOADED; + if (until == "onload") + return SavePreviousDocumentResources::UNTIL_ON_LOAD; return SavePreviousDocumentResources::NEVER; }
diff --git a/content/public/android/java/src/org/chromium/content/common/ContentSwitches.java b/content/public/android/java/src/org/chromium/content/common/ContentSwitches.java index 3d92d19f..81b6863 100644 --- a/content/public/android/java/src/org/chromium/content/common/ContentSwitches.java +++ b/content/public/android/java/src/org/chromium/content/common/ContentSwitches.java
@@ -36,9 +36,6 @@ // How much of the browser controls need to be hidden before they will auto hide. public static final String TOP_CONTROLS_HIDE_THRESHOLD = "top-controls-hide-threshold"; - // Native switch - shell_switches::kRunLayoutTest - public static final String RUN_LAYOUT_TEST = "run-layout-test"; - // Native switch - chrome_switches::kDisablePopupBlocking public static final String DISABLE_POPUP_BLOCKING = "disable-popup-blocking";
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json index 6e5582fe..8311a052 100644 --- a/content/public/app/mojo/content_browser_manifest.json +++ b/content/public/app/mojo/content_browser_manifest.json
@@ -24,7 +24,6 @@ "blink::mojom::BroadcastChannelProvider", "blink::mojom::Hyphenation", "blink::mojom::MimeRegistry", - "blink::mojom::NotificationService", "blink::mojom::OffscreenCanvasProvider", "blink::mojom::ReportingServiceProxy", "blink::mojom::WebDatabaseHost",
diff --git a/content/renderer/media/aec_dump_message_filter.cc b/content/renderer/media/aec_dump_message_filter.cc index 56f40ee..f423391 100644 --- a/content/renderer/media/aec_dump_message_filter.cc +++ b/content/renderer/media/aec_dump_message_filter.cc
@@ -48,10 +48,6 @@ int id = delegate_id_counter_++; delegates_[id] = delegate; - if (override_aec3_) { - delegate->OnAec3Enable(*override_aec3_); - } - io_task_runner_->PostTask( FROM_HERE, base::BindOnce(&AecDumpMessageFilter::RegisterAecDumpConsumer, this, id)); @@ -72,6 +68,11 @@ id)); } +base::Optional<bool> AecDumpMessageFilter::GetOverrideAec3() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return override_aec3_; +} + void AecDumpMessageFilter::Send(IPC::Message* message) { DCHECK(io_task_runner_->BelongsToCurrentThread()); if (sender_)
diff --git a/content/renderer/media/aec_dump_message_filter.h b/content/renderer/media/aec_dump_message_filter.h index 7df6635..3e64108 100644 --- a/content/renderer/media/aec_dump_message_filter.h +++ b/content/renderer/media/aec_dump_message_filter.h
@@ -8,6 +8,7 @@ #include <memory> #include "base/macros.h" +#include "base/optional.h" #include "content/common/content_export.h" #include "content/renderer/render_thread_impl.h" #include "ipc/ipc_platform_file.h" @@ -40,10 +41,13 @@ // Getter for the one AecDumpMessageFilter object. static scoped_refptr<AecDumpMessageFilter> Get(); - // Adds a delegate that receives the enable and disable notifications. + // Adds a delegate that receives the enable and disable notifications. Must be + // called on the main task runner (|main_task_runner| in constructor). All + // calls on |delegate| are done on the main task runner. void AddDelegate(AecDumpMessageFilter::AecDumpDelegate* delegate); - // Removes a delegate. + // Removes a delegate. Must be called on the main task runner + // (|main_task_runner| in constructor). void RemoveDelegate(AecDumpMessageFilter::AecDumpDelegate* delegate); // IO task runner associated with this message filter. @@ -51,6 +55,10 @@ return io_task_runner_; } + // Returns the AEC3 setting. Must be called on the main task runner + // (|main_task_runner| in constructor). + base::Optional<bool> GetOverrideAec3() const; + protected: ~AecDumpMessageFilter() override;
diff --git a/content/renderer/media/media_stream_audio_processor.cc b/content/renderer/media/media_stream_audio_processor.cc index d314246..b874972 100644 --- a/content/renderer/media/media_stream_audio_processor.cc +++ b/content/renderer/media/media_stream_audio_processor.cc
@@ -320,12 +320,17 @@ DCHECK(main_thread_runner_); capture_thread_checker_.DetachFromThread(); render_thread_checker_.DetachFromThread(); + + // In unit tests not creating a message filter, |aec_dump_message_filter_| + // will be null. We can just ignore that below. Other unit tests and browser + // tests ensure that we do get the filter when we should. + aec_dump_message_filter_ = AecDumpMessageFilter::Get(); + + if (aec_dump_message_filter_) + override_aec3_ = aec_dump_message_filter_->GetOverrideAec3(); + InitializeAudioProcessingModule(properties); - aec_dump_message_filter_ = AecDumpMessageFilter::Get(); - // In unit tests not creating a message filter, |aec_dump_message_filter_| - // will be NULL. We can just ignore that. Other unit tests and browser tests - // ensure that we do get the filter when we should. if (aec_dump_message_filter_.get()) aec_dump_message_filter_->AddDelegate(this);
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java index 60a4195..85e8964 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
@@ -21,7 +21,6 @@ import org.chromium.content.browser.BrowserStartupController; import org.chromium.content.browser.ContentViewCore; import org.chromium.content.browser.DeviceUtils; -import org.chromium.content.common.ContentSwitches; import org.chromium.content_public.browser.WebContents; import org.chromium.content_shell.Shell; import org.chromium.content_shell.ShellManager; @@ -37,6 +36,9 @@ private static final String ACTIVE_SHELL_URL_KEY = "activeUrl"; public static final String COMMAND_LINE_ARGS_KEY = "commandLineArgs"; + // Native switch - shell_switches::kRunLayoutTest + private static final String RUN_LAYOUT_TEST_SWITCH = "run-layout-test"; + private ShellManager mShellManager; private ActivityWindowAndroid mWindowAndroid; private Intent mLastSentIntent; @@ -83,7 +85,7 @@ mShellManager.setStartupUrl(Shell.sanitizeUrl(mStartupUrl)); } - if (CommandLine.getInstance().hasSwitch(ContentSwitches.RUN_LAYOUT_TEST)) { + if (CommandLine.getInstance().hasSwitch(RUN_LAYOUT_TEST_SWITCH)) { try { BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER) .startBrowserProcessesSync(false);
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py index 2dcffbb..a72825a 100644 --- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py +++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -295,6 +295,10 @@ self.Fail('deqp/functional/gles3/integerstatequery.html', ['passthrough', 'opengl'], bug=602688) + # Win / Intel + self.Fail('conformance/rendering/rendering-stencil-large-viewport.html', + ['win', 'intel', 'd3d11'], bug=782317) + # Passthrough command decoder / OpenGL / Intel self.Fail('conformance2/textures/video/tex-2d-rgb32f-rgb-float.html', ['passthrough', 'opengl', 'intel'], bug=602688) @@ -696,6 +700,9 @@ 'multisampled-depth-renderbuffer-initialization.html', ['mac', 'intel'], bug=731877) + self.Fail('conformance/rendering/rendering-stencil-large-viewport.html', + ['mac', 'intel'], bug=782317) + # Linux only. self.Flaky('conformance/textures/video/' + 'tex-2d-rgba-rgba-unsigned_byte.html',
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt index d770d70b..76eabf3 100644 --- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt +++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@ # AUTOGENERATED FILE - DO NOT EDIT # SEE roll_webgl_conformance.py -Current webgl revision e4919fa03c74bd561dcabf3e61668fa3c7e54353 +Current webgl revision 05591bbeae6592fd924caec8e728a4ea86cbb8c9
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm index cb6f7b7..fc999c3ff 100644 --- a/ios/chrome/app/main_controller.mm +++ b/ios/chrome/app/main_controller.mm
@@ -1873,14 +1873,18 @@ if (![self.mainTabModel isEmpty] || ![self.otrTabModel isEmpty]) return NO; - UIViewController* viewController = [self topPresentedViewController]; - while (viewController) { - if ([viewController.presentingViewController - isEqual:_tabSwitcherController]) { - return NO; - } - viewController = viewController.presentingViewController; + // If the tabSwitcher is contained, check if the parent container is + // presenting another view controller. + if ([[_tabSwitcherController parentViewController] + presentedViewController]) { + return NO; } + + // Check if the tabSwitcher is directly presenting another view controller. + if ([_tabSwitcherController presentedViewController]) { + return NO; + } + return YES; } return ![tabModel count] && [tabModel browserState] &&
diff --git a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h index 4ebc3aa..5db36bd 100644 --- a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h +++ b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h
@@ -12,9 +12,6 @@ public: virtual ~ChromeBroadcastObserverInterface(); - // Invoked by |-broadcastTabStripVisible:|. - virtual void OnTabStripVisbibleBroadcasted(bool visible) {} - // Invoked by |-broadcastContentScrollOffset:|. virtual void OnContentScrollOffsetBroadcasted(CGFloat offset) {}
diff --git a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.mm b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.mm index a9f34e3..6c4e3da 100644 --- a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.mm +++ b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.mm
@@ -23,10 +23,6 @@ return self; } -- (void)broadcastTabStripVisible:(BOOL)visible { - self.observer->OnTabStripVisbibleBroadcasted(visible); -} - - (void)broadcastContentScrollOffset:(CGFloat)offset { self.observer->OnContentScrollOffsetBroadcasted(offset); }
diff --git a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge_unittest.mm b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge_unittest.mm index 3c640e3..85dd2641 100644 --- a/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge_unittest.mm +++ b/ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge_unittest.mm
@@ -14,7 +14,6 @@ class TestChromeBroadcastObserver : public ChromeBroadcastObserverInterface { public: // Received broadcast values. - bool tab_strip_visible() const { return tab_strip_visible_; } CGFloat scroll_offset() const { return scroll_offset_; } bool scroll_view_scrolling() const { return scroll_view_scrolling_; } bool scroll_view_dragging() const { return scroll_view_dragging_; } @@ -22,9 +21,6 @@ private: // ChromeBroadcastObserverInterface: - void OnTabStripVisbibleBroadcasted(bool visible) override { - tab_strip_visible_ = visible; - } void OnContentScrollOffsetBroadcasted(CGFloat offset) override { scroll_offset_ = offset; } @@ -38,7 +34,6 @@ toolbar_height_ = toolbar_height; } - bool tab_strip_visible_ = false; CGFloat scroll_offset_ = 0.0; bool scroll_view_scrolling_ = false; bool scroll_view_dragging_ = false;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h index fefb9f7..4b4f6e24 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h
@@ -5,10 +5,13 @@ #ifndef IOS_CLEAN_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_CONTROLLER_H_ #define IOS_CLEAN_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_CONTROLLER_H_ +#import <Foundation/Foundation.h> #include <memory> #include "base/macros.h" +@class ChromeBroadcaster; +@class ChromeBroadcastOberverBridge; class FullscreenControllerObserver; class FullscreenModel; class FullscreenWebStateListObserver; @@ -19,7 +22,7 @@ // the page's content to be visible. class FullscreenController { public: - explicit FullscreenController(); + explicit FullscreenController(ChromeBroadcaster* broadcaster); ~FullscreenController(); // Adds and removes FullscreenControllerObservers. @@ -39,8 +42,12 @@ void DecrementDisabledCounter(); private: + // The broadcaster that drives the model. + __strong ChromeBroadcaster* broadcaster_ = nil; // The model used to calculate fullscreen state. std::unique_ptr<FullscreenModel> model_; + // The bridge used to forward brodcasted UI to |model_|. + __strong ChromeBroadcastOberverBridge* bridge_ = nil; // A WebStateListObserver that updates |model_| for WebStateList changes. std::unique_ptr<FullscreenWebStateListObserver> web_state_list_observer_;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.mm index 7754bec1..c496895 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.mm +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.mm
@@ -5,6 +5,8 @@ #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h" #include "base/memory/ptr_util.h" +#import "ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h" +#import "ios/chrome/browser/ui/broadcaster/chrome_broadcaster.h" #import "ios/chrome/browser/ui/fullscreen/fullscreen_model.h" #import "ios/chrome/browser/ui/fullscreen/fullscreen_web_state_list_observer.h" @@ -12,10 +14,32 @@ #error "This file requires ARC support." #endif -FullscreenController::FullscreenController() - : model_(base::MakeUnique<FullscreenModel>()) {} +FullscreenController::FullscreenController(ChromeBroadcaster* broadcaster) + : broadcaster_(broadcaster), + model_(base::MakeUnique<FullscreenModel>()), + bridge_([[ChromeBroadcastOberverBridge alloc] + initWithObserver:model_.get()]) { + DCHECK(broadcaster_); + [broadcaster_ addObserver:bridge_ + forSelector:@selector(broadcastContentScrollOffset:)]; + [broadcaster_ addObserver:bridge_ + forSelector:@selector(broadcastScrollViewIsScrolling:)]; + [broadcaster_ addObserver:bridge_ + forSelector:@selector(broadcastScrollViewIsDragging:)]; + [broadcaster_ addObserver:bridge_ + forSelector:@selector(broadcastToolbarHeight:)]; +} -FullscreenController::~FullscreenController() {} +FullscreenController::~FullscreenController() { + [broadcaster_ removeObserver:bridge_ + forSelector:@selector(broadcastContentScrollOffset:)]; + [broadcaster_ removeObserver:bridge_ + forSelector:@selector(broadcastScrollViewIsScrolling:)]; + [broadcaster_ removeObserver:bridge_ + forSelector:@selector(broadcastScrollViewIsDragging:)]; + [broadcaster_ removeObserver:bridge_ + forSelector:@selector(broadcastToolbarHeight:)]; +} void FullscreenController::AddObserver(FullscreenControllerObserver* observer) { // TODO(crbug.com/785671): Use FullscreenControllerObserverManager to keep
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h index 8ce61031..70df003 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
@@ -10,14 +10,15 @@ #include "base/macros.h" #include "base/observer_list.h" +#import "ios/chrome/browser/ui/broadcaster/chrome_broadcast_observer_bridge.h" class FullscreenModelObserver; // Model object used to calculate fullscreen state. -class FullscreenModel { +class FullscreenModel : public ChromeBroadcastObserverInterface { public: FullscreenModel(); - virtual ~FullscreenModel(); + ~FullscreenModel() override; // Adds and removes FullscreenModelObservers. void AddObserver(FullscreenModelObserver* observer) { @@ -88,6 +89,12 @@ // toolbar height. void UpdateBaseOffset(); + // ChromeBroadcastObserverInterface: + void OnContentScrollOffsetBroadcasted(CGFloat offset) override; + void OnScrollViewIsScrollingBroadcasted(bool scrolling) override; + void OnScrollViewIsDraggingBroadcasted(bool dragging) override; + void OnToolbarHeightBroadcasted(CGFloat toolbar_height) override; + // The observers for this model. base::ObserverList<FullscreenModelObserver> observers_; // The percentage of the toolbar that should be visible, where 1.0 denotes a
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm index e28e10d..4f0b885 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
@@ -126,3 +126,19 @@ void FullscreenModel::UpdateBaseOffset() { base_offset_ = y_content_offset_ - (1.0 - progress_) * toolbar_height_; } + +void FullscreenModel::OnContentScrollOffsetBroadcasted(CGFloat offset) { + SetYContentOffset(offset); +} + +void FullscreenModel::OnScrollViewIsScrollingBroadcasted(bool scrolling) { + SetScrollViewIsScrolling(scrolling); +} + +void FullscreenModel::OnScrollViewIsDraggingBroadcasted(bool dragging) { + SetScrollViewIsDragging(dragging); +} + +void FullscreenModel::OnToolbarHeightBroadcasted(CGFloat toolbar_height) { + SetToolbarHeight(toolbar_height); +}
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h b/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h index ca15252..c16d3669 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h
@@ -7,7 +7,6 @@ #import <UIKit/UIKit.h> -#if defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MIN_ALLOWED >= __IPHONE_10_0) // When a scroll event ends, the toolbar should be either completely hidden or // completely visible. If a scroll ends and the toolbar is partly visible, this // animator will be provided to UI elements to animate its state to a hidden or @@ -29,17 +28,8 @@ - (instancetype)initWithDuration:(NSTimeInterval)duration timingParameters:(id<UITimingCurveProvider>)parameters NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; @end -#else - -// Dummy object. -// TODO(crbug.com/768876): Remove this class and the #if guards once iOS9 -// support is dropped. -@interface FullscreenScrollEndAnimator : NSObject -@end - -#endif // __IPHONE_10_0 - #endif // IOS_CLEAN_CHROME_BROWSER_UI_FULLSCREEN_FULLSCREEN_SCROLL_END_ANIMATOR_H_
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.mm index 12604fd0..a9cbdaee 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.mm +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.mm
@@ -16,7 +16,6 @@ #error "This file requires ARC support." #endif -#if defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MIN_ALLOWED >= __IPHONE_10_0) @interface FullscreenScrollEndTimingCurveProvider : NSObject<UITimingCurveProvider> { std::unique_ptr<gfx::CubicBezier> _bezier; @@ -114,10 +113,3 @@ } @end - -#else - -@implementation FullscreenScrollEndAnimator -@end - -#endif // __IPHONE_10_0
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater_unittest.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater_unittest.mm index 0b9e6fc8..0a7a9317 100644 --- a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater_unittest.mm +++ b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater_unittest.mm
@@ -80,8 +80,10 @@ // Tests that the updater sends the animator to the UI element. TEST_F(FullscreenUIUpdaterTest, ScrollEnd) { ASSERT_FALSE(element().animator); + // Create a test animator. The start progress of 0.0 is a dummy value, as the + // animator's progress properties are unused in this test. FullscreenScrollEndAnimator* const kAnimator = - [[FullscreenScrollEndAnimator alloc] init]; + [[FullscreenScrollEndAnimator alloc] initWithStartProgress:0.0]; observer()->FullscreenScrollEventEnded(nullptr, kAnimator); EXPECT_EQ(element().animator, kAnimator); }
diff --git a/ios/chrome/browser/ui/history/history_collection_view_controller.mm b/ios/chrome/browser/ui/history/history_collection_view_controller.mm index 938adc5..b7332708 100644 --- a/ios/chrome/browser/ui/history/history_collection_view_controller.mm +++ b/ios/chrome/browser/ui/history/history_collection_view_controller.mm
@@ -43,6 +43,7 @@ #include "ios/chrome/browser/ui/history/ios_browsing_history_driver.h" #import "ios/chrome/browser/ui/url_loader.h" #import "ios/chrome/browser/ui/util/pasteboard_util.h" +#import "ios/chrome/browser/ui/util/top_view_controller.h" #include "ios/chrome/grit/ios_strings.h" #import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MDCActivityIndicator.h" #import "ios/third_party/material_components_ios/src/components/Collections/src/MaterialCollections.h" @@ -818,9 +819,9 @@ params.menu_title.reset([menuTitle copy]); // Present sheet/popover using controller that is added to view hierarchy. - UIViewController* topController = [params.view window].rootViewController; - while (topController.presentedViewController) - topController = topController.presentedViewController; + // TODO(crbug.com/754642): Remove TopPresentedViewController(). + UIViewController* topController = + top_view_controller::TopPresentedViewController(); self.contextMenuCoordinator = [[ContextMenuCoordinator alloc] initWithBaseViewController:topController
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h index b19c6991..0f20c86 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h +++ b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h
@@ -28,10 +28,6 @@ - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE; -// Delegate getter and setter. Overridden to use OmniboxTextFieldDelegate -// instead of UITextFieldDelegate. -- (id<OmniboxTextFieldDelegate>)delegate; -- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate; // Sets the field's text to |text|. If |userTextLength| is less than the length // of |text|, the excess is displayed as inline autocompleted text. When the @@ -64,15 +60,6 @@ // on older version of iOS. - (NSString*)markedText; -// Initial touch on the Omnibox triggers a "pre-edit" state. The current -// URL is shown without any insertion point. First character typed replaces -// the URL. A second touch turns on the insertion point. |preEditStaticLabel| -// is normally hidden. In pre-edit state, |preEditStaticLabel| is unhidden -// and displays the URL that will be edited on the second touch. -- (void)enterPreEditState; -- (void)exitPreEditState; -- (BOOL)isPreEditing; - // Returns the current selected text range as an NSRange. - (NSRange)selectedNSRange; @@ -93,21 +80,34 @@ // Called when animations added by |-animateFadeWithStyle:| can be removed. - (void)cleanUpFadeAnimations; -// Redeclare the delegate property to be the more specific -// OmniboxTextFieldDelegate. -@property(nonatomic, weak) id<OmniboxTextFieldDelegate> delegate; - -@property(nonatomic, strong) NSString* preEditText; -@property(nonatomic) BOOL clearingPreEditText; -@property(nonatomic, strong) UIColor* selectedTextBackgroundColor; -@property(nonatomic, strong) UIColor* placeholderTextColor; -@property(nonatomic, assign) BOOL incognito; - +// New animations API. Currently are behind a flag since they require iOS 10 +// APIs to work. They replace all animations above. - (void)addExpandOmniboxAnimations:(UIViewPropertyAnimator*)animator API_AVAILABLE(ios(10.0)); - (void)addContractOmniboxAnimations:(UIViewPropertyAnimator*)animator API_AVAILABLE(ios(10.0)); +// Initial touch on the Omnibox triggers a "pre-edit" state. The current +// URL is shown without any insertion point. First character typed replaces +// the URL. A second touch turns on the insertion point. |preEditStaticLabel| +// is normally hidden. In pre-edit state, |preEditStaticLabel| is unhidden +// and displays the URL that will be edited on the second touch. +- (void)enterPreEditState; +- (void)exitPreEditState; +- (BOOL)isPreEditing; + +// The delegate for this textfield. Overridden to use OmniboxTextFieldDelegate +// instead of UITextFieldDelegate. +@property(nonatomic, weak) id<OmniboxTextFieldDelegate> delegate; + +// Text displayed when in pre-edit state. +@property(nonatomic, strong) NSString* preEditText; + +@property(nonatomic) BOOL clearingPreEditText; +@property(nonatomic, strong) UIColor* selectedTextBackgroundColor; +@property(nonatomic, strong) UIColor* placeholderTextColor; +@property(nonatomic, assign) BOOL incognito; + @end // A category for defining new methods that access private ivars.
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm index 3d13c059..23d14134 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm
@@ -101,6 +101,7 @@ @synthesize placeholderTextColor = _placeholderTextColor; @synthesize incognito = _incognito; +#pragma mark - Public methods // Overload to allow for code-based initialization. - (instancetype)initWithFrame:(CGRect)frame { return [self initWithFrame:frame @@ -156,6 +157,149 @@ return nil; } +- (void)setText:(NSAttributedString*)text + userTextLength:(size_t)userTextLength { + DCHECK_LE(userTextLength, [text length]); + + NSUInteger autocompleteLength = [text length] - userTextLength; + [self setTextInternal:text autocompleteLength:autocompleteLength]; +} + +- (void)insertTextWhileEditing:(NSString*)text { + // This method should only be called while editing. + DCHECK([self isFirstResponder]); + + if ([self markedTextRange] != nil) + [self unmarkText]; + + NSRange selectedNSRange = [self selectedNSRange]; + if (![self delegate] || [[self delegate] textField:self + shouldChangeCharactersInRange:selectedNSRange + replacementString:text]) { + [self replaceRange:[self selectedTextRange] withText:text]; + } +} + +- (base::string16)displayedText { + return base::SysNSStringToUTF16([self nsDisplayedText]); +} + +- (base::string16)autocompleteText { + DCHECK_LT([[self text] length], [[_selection text] length]) + << "[_selection text] and [self text] are out of sync. " + << "Please email justincohen@ and rohitrao@ if you see this."; + if (_selection && [[_selection text] length] > [[self text] length]) { + return base::SysNSStringToUTF16( + [[_selection text] substringFromIndex:[[self text] length]]); + } + return base::string16(); +} + +- (BOOL)hasAutocompleteText { + return !!_selection; +} + +- (void)clearAutocompleteText { + if (_selection) { + [_selection removeFromSuperview]; + _selection = nil; + [self showTextAndCursor]; + } +} + +- (NSString*)markedText { + DCHECK([self conformsToProtocol:@protocol(UITextInput)]); + return [self textInRange:[self markedTextRange]]; +} + +- (NSRange)selectedNSRange { + DCHECK([self isFirstResponder]); + UITextPosition* beginning = [self beginningOfDocument]; + UITextRange* selectedRange = [self selectedTextRange]; + NSInteger start = + [self offsetFromPosition:beginning toPosition:[selectedRange start]]; + NSInteger length = [self offsetFromPosition:[selectedRange start] + toPosition:[selectedRange end]]; + return NSMakeRange(start, length); +} + +- (NSTextAlignment)bestTextAlignment { + if ([self isFirstResponder]) { + return [self bestAlignmentForText:[self text]]; + } + return NSTextAlignmentNatural; +} + +// Normally NSTextAlignmentNatural would handle text alignment automatically, +// but there are numerous edge case issues with it, so it's simpler to just +// manually update the text alignment and writing direction of the UITextField. +- (void)updateTextDirection { + // Setting the empty field to Natural seems to let iOS update the cursor + // position when the keyboard language is changed. + if (![self text].length) { + [self setTextAlignment:NSTextAlignmentNatural]; + return; + } + + NSTextAlignment alignment = [self bestTextAlignment]; + [self setTextAlignment:alignment]; + if (!base::ios::IsRunningOnIOS11OrLater()) { + // TODO(crbug.com/730461): Remove this entire block once it's been tested + // on trunk. + UITextWritingDirection writingDirection = + alignment == NSTextAlignmentLeft ? UITextWritingDirectionLeftToRight + : UITextWritingDirectionRightToLeft; + [self + setBaseWritingDirection:writingDirection + forRange: + [self + textRangeFromPosition:[self beginningOfDocument] + toPosition:[self endOfDocument]]]; + } +} + +- (UIColor*)displayedTextColor { + return _displayedTextColor; +} + +#pragma mark animations + +- (void)animateFadeWithStyle:(OmniboxTextFieldFadeStyle)style { + // Animation values + BOOL isFadingIn = (style == OMNIBOX_TEXT_FIELD_FADE_STYLE_IN); + CGFloat beginOpacity = isFadingIn ? 0.0 : 1.0; + CGFloat endOpacity = isFadingIn ? 1.0 : 0.0; + CAMediaTimingFunction* opacityTiming = ios::material::TimingFunction( + isFadingIn ? ios::material::CurveEaseOut : ios::material::CurveEaseIn); + CFTimeInterval delay = isFadingIn ? ios::material::kDuration8 : 0.0; + + CAAnimation* labelAnimation = OpacityAnimationMake(beginOpacity, endOpacity); + labelAnimation.duration = + isFadingIn ? ios::material::kDuration6 : ios::material::kDuration8; + labelAnimation.timingFunction = opacityTiming; + labelAnimation = DelayedAnimationMake(labelAnimation, delay); + CAAnimation* auxillaryViewAnimation = + OpacityAnimationMake(beginOpacity, endOpacity); + auxillaryViewAnimation.duration = ios::material::kDuration8; + auxillaryViewAnimation.timingFunction = opacityTiming; + auxillaryViewAnimation = DelayedAnimationMake(auxillaryViewAnimation, delay); + + for (UIView* subview in self.subviews) { + if ([subview isKindOfClass:[UILabel class]]) { + [subview.layer addAnimation:labelAnimation + forKey:kOmniboxFadeAnimationKey]; + } else { + [subview.layer addAnimation:auxillaryViewAnimation + forKey:kOmniboxFadeAnimationKey]; + } + } +} + +- (void)cleanUpFadeAnimations { + RemoveAnimationForKeyFromLayers(kOmniboxFadeAnimationKey, + [self fadeAnimationLayers]); +} + - (void)addExpandOmniboxAnimations:(UIViewPropertyAnimator*)animator API_AVAILABLE(ios(10.0)) { __weak OmniboxTextFieldIOS* weakSelf = self; @@ -192,68 +336,7 @@ }]; } -// Enforces that the delegate is an OmniboxTextFieldDelegate. -- (id<OmniboxTextFieldDelegate>)delegate { - id delegate = [super delegate]; - DCHECK(delegate == nil || - [[delegate class] - conformsToProtocol:@protocol(OmniboxTextFieldDelegate)]); - return delegate; -} - -// Overridden to require an OmniboxTextFieldDelegate. -- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate { - [super setDelegate:delegate]; -} - -// Exposed for testing. -- (UILabel*)preEditStaticLabel { - return _preEditStaticLabel; -} - -- (void)insertTextWhileEditing:(NSString*)text { - // This method should only be called while editing. - DCHECK([self isFirstResponder]); - - if ([self markedTextRange] != nil) - [self unmarkText]; - - NSRange selectedNSRange = [self selectedNSRange]; - if (![self delegate] || [[self delegate] textField:self - shouldChangeCharactersInRange:selectedNSRange - replacementString:text]) { - [self replaceRange:[self selectedTextRange] withText:text]; - } -} - -// Method called when the users touches the text input. This will accept the -// autocompleted text. -- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - if ([self isPreEditing]) { - [self exitPreEditState]; - [super selectAll:nil]; - } - - if (!_selection) { - [super touchesBegan:touches withEvent:event]; - return; - } - - // Only consider a single touch. - UITouch* touch = [touches anyObject]; - if (!touch) - return; - - // Accept selection. - NSString* newText = [[self nsDisplayedText] copy]; - [self clearAutocompleteText]; - [self setText:newText]; -} - -// Gets the bounds of the rect covering the URL. -- (CGRect)preEditLabelRectForBounds:(CGRect)bounds { - return [self editingRectForBounds:self.bounds]; -} +#pragma mark pre-edit // Creates a UILabel based on the current dimension of the text field and // displays the URL in the UILabel so it appears properly aligned to the URL. @@ -295,37 +378,216 @@ [self addSubview:_preEditStaticLabel]; } -- (NSTextAlignment)bestAlignmentForText:(NSString*)text { - if (text.length) { - NSString* lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage( - (CFStringRef)text, CFRangeMake(0, text.length))); +// Finishes pre-edit state by removing the UILabel with the URL. +- (void)exitPreEditState { + [self setPreEditText:nil]; + if (_preEditStaticLabel) { + [_preEditStaticLabel removeFromSuperview]; + _preEditStaticLabel = nil; + [self showTextAndCursor]; + } +} - if ([NSLocale characterDirectionForLanguage:lang] == - NSLocaleLanguageDirectionRightToLeft) { - return NSTextAlignmentRight; +// Returns whether we are processing the first touch event on the text field. +- (BOOL)isPreEditing { + return !![self preEditText]; +} + +#pragma mark - TestingUtilities category + +// Exposed for testing. +- (UILabel*)preEditStaticLabel { + return _preEditStaticLabel; +} + +#pragma mark - Properties + +// Enforces that the delegate is an OmniboxTextFieldDelegate. +- (id<OmniboxTextFieldDelegate>)delegate { + id delegate = [super delegate]; + DCHECK(delegate == nil || + [[delegate class] + conformsToProtocol:@protocol(OmniboxTextFieldDelegate)]); + return delegate; +} + +// Overridden to require an OmniboxTextFieldDelegate. +- (void)setDelegate:(id<OmniboxTextFieldDelegate>)delegate { + [super setDelegate:delegate]; +} + +#pragma mark - Private methods + +#pragma mark - UITextField + +// Ensures that attributedText always uses the proper style attributes. +- (void)setAttributedText:(NSAttributedString*)attributedText { + NSMutableAttributedString* mutableText = [attributedText mutableCopy]; + NSRange entireString = NSMakeRange(0, [mutableText length]); + + // Set the font. + [mutableText addAttribute:NSFontAttributeName value:_font range:entireString]; + + // When editing, use the default text color for all text. + if (self.editing) { + // Hide the text when the |_selection| label is displayed. + UIColor* textColor = + _selection ? [UIColor clearColor] : _displayedTextColor; + [mutableText addAttribute:NSForegroundColorAttributeName + value:textColor + range:entireString]; + } else { + NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init]; + // URLs have their text direction set to to LTR (avoids RTL characters + // making the URL render from right to left, as per the URL rendering + // standard described here: https://url.spec.whatwg.org/#url-rendering + [style setBaseWritingDirection:NSWritingDirectionLeftToRight]; + + // Set linebreak mode to 'clipping' to ensure the text is never elided. + // This is a workaround for iOS 6, where it appears that + // [self.attributedText size] is not wide enough for the string (e.g. a URL + // else ending with '.com' will be elided to end with '.c...'). It appears + // to be off by one point so clipping is acceptable as it doesn't actually + // cut off any of the text. + [style setLineBreakMode:NSLineBreakByClipping]; + + [mutableText addAttribute:NSParagraphStyleAttributeName + value:style + range:entireString]; + } + + [super setAttributedText:mutableText]; +} + +- (void)setPlaceholder:(NSString*)placeholder { + if (placeholder && _placeholderTextColor) { + NSDictionary* attributes = + @{NSForegroundColorAttributeName : _placeholderTextColor}; + self.attributedPlaceholder = + [[NSAttributedString alloc] initWithString:placeholder + attributes:attributes]; + } else { + [super setPlaceholder:placeholder]; + } +} + +- (void)setText:(NSString*)text { + NSAttributedString* as = [[NSAttributedString alloc] initWithString:text]; + if (self.text.length > 0 && as.length == 0) { + // Remove the fade animations before the subviews are removed. + [self cleanUpFadeAnimations]; + } + [self setTextInternal:as autocompleteLength:0]; +} + +- (CGRect)textRectForBounds:(CGRect)bounds { + CGRect newBounds = [super textRectForBounds:bounds]; + + LayoutRect textRectLayout = + LayoutRectForRectInBoundingRect(newBounds, bounds); + CGFloat textInset = kTextInsetNoLeftView; + + // Shift the text right and reduce the width to create empty space between the + // left view and the omnibox text. + textRectLayout.position.leading += textInset + kTextAreaLeadingOffset; + textRectLayout.size.width -= textInset - kTextAreaLeadingOffset; + + if (IsIPadIdiom()) { + if (!IsCompactTablet()) { + // Adjust the width so that the text doesn't overlap with the bookmark and + // voice search buttons which are displayed inside the omnibox. + textRectLayout.size.width += self.rightView.bounds.size.width - + kVoiceSearchButtonWidth - kStarButtonWidth; } } - return NSTextAlignmentLeft; + + return LayoutRectGetRect(textRectLayout); } -- (NSTextAlignment)bestTextAlignment { - if ([self isFirstResponder]) { - return [self bestAlignmentForText:[self text]]; +- (CGRect)editingRectForBounds:(CGRect)bounds { + CGRect newBounds = [super editingRectForBounds:bounds]; + + // -editingRectForBounds doesn't account for rightViews that aren't flush + // with the right edge, it just looks at the rightView's width. Account for + // the offset here. + CGFloat rightViewMaxX = CGRectGetMaxX([self rightViewRectForBounds:bounds]); + if (rightViewMaxX) + newBounds.size.width -= bounds.size.width - rightViewMaxX; + + LayoutRect editingRectLayout = + LayoutRectForRectInBoundingRect(newBounds, bounds); + editingRectLayout.position.leading += kTextAreaLeadingOffset; + editingRectLayout.position.leading += kTextInset; + editingRectLayout.size.width -= kTextInset + kEditingRectWidthInset; + if (IsIPadIdiom()) { + if (!IsCompactTablet() && !self.rightView) { + // Normally the clear button shrinks the edit box, but if the rightView + // isn't set, shrink behind the mic icons. + editingRectLayout.size.width -= kVoiceSearchButtonWidth; + } + } else { + CGFloat xDiff = editingRectLayout.position.leading - kEditingRectX; + editingRectLayout.position.leading = kEditingRectX; + editingRectLayout.size.width += xDiff; } - return NSTextAlignmentNatural; + // Don't let the edit rect extend over the clear button. The right view + // is hidden during animations, so fake its width here. + if (self.rightViewMode == UITextFieldViewModeNever) + editingRectLayout.size.width -= self.rightView.bounds.size.width; + + newBounds = LayoutRectGetRect(editingRectLayout); + + // Position the selection view appropriately. + [_selection setFrame:newBounds]; + + return newBounds; } -- (NSTextAlignment)preEditTextAlignment { - // If the pre-edit text is wider than the omnibox, right-align the text so it - // ends at the same x coord as the blue selection box. - CGSize textSize = - [_preEditStaticLabel.text cr_pixelAlignedSizeWithFont:_font]; - // Note, this does not need to support RTL, as URLs are always LTR. - return textSize.width < _preEditStaticLabel.frame.size.width - ? NSTextAlignmentLeft - : NSTextAlignmentRight; +// Enumerate url components (host, path) and draw each one in different rect. +- (void)drawTextInRect:(CGRect)rect { + if (base::ios::IsRunningOnOrLater(11, 1, 0)) { + // -[UITextField drawTextInRect:] ignores the argument, so we can't do + // anything on 11.1 and up. + [super drawTextInRect:rect]; + return; + } + + // Save and restore the graphics state because rectForDrawTextInRect may + // apply an image mask to fade out beginning and/or end of the URL. + gfx::ScopedCGContextSaveGState saver(UIGraphicsGetCurrentContext()); + [super drawTextInRect:[self rectForDrawTextInRect:rect]]; } +// Overriding this method to offset the rightView property +// (containing a clear text button). +- (CGRect)rightViewRectForBounds:(CGRect)bounds { + // iOS9 added updated RTL support, but only half implemented it for + // UITextField. leftView and rightView were not renamed, but are are correctly + // swapped and treated as leadingView / trailingView. However, + // -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as + // leading and trailing. Hence the swapping below. + if ([self isTextFieldLTR]) { + return [self layoutRightViewForBounds:bounds]; + } + return [self layoutLeftViewForBounds:bounds]; +} + +// Overriding this method to offset the leftView property +// (containing a placeholder image) consistently with omnibox text padding. +- (CGRect)leftViewRectForBounds:(CGRect)bounds { + // iOS9 added updated RTL support, but only half implemented it for + // UITextField. leftView and rightView were not renamed, but are are correctly + // swapped and treated as leadingView / trailingView. However, + // -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as + // leading and trailing. Hence the swapping below. + if ([self isTextFieldLTR]) { + return [self layoutLeftViewForBounds:bounds]; + } + return [self layoutRightViewForBounds:bounds]; +} + +#pragma mark - UIView + - (void)layoutSubviews { [super layoutSubviews]; if ([self isPreEditing]) { @@ -340,44 +602,38 @@ } } -// Finishes pre-edit state by removing the UILabel with the URL. -- (void)exitPreEditState { - [self setPreEditText:nil]; - if (_preEditStaticLabel) { - [_preEditStaticLabel removeFromSuperview]; - _preEditStaticLabel = nil; - [self showTextAndCursor]; +- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { + // Anything in the narrow bar above OmniboxTextFieldIOS view + // will also activate the text field. + if (point.y < 0) + point.y = 0; + return [super hitTest:point withEvent:event]; +} + +#pragma mark - UIResponder + +// Method called when the users touches the text input. This will accept the +// autocompleted text. +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + if ([self isPreEditing]) { + [self exitPreEditState]; + [super selectAll:nil]; } -} -- (UIColor*)displayedTextColor { - return _displayedTextColor; -} - -// Returns whether we are processing the first touch event on the text field. -- (BOOL)isPreEditing { - return !![self preEditText]; -} - -- (NSString*)nsDisplayedText { - if (_selection) - return [_selection text]; - return [self text]; -} - -- (base::string16)displayedText { - return base::SysNSStringToUTF16([self nsDisplayedText]); -} - -- (base::string16)autocompleteText { - DCHECK_LT([[self text] length], [[_selection text] length]) - << "[_selection text] and [self text] are out of sync. " - << "Please email justincohen@ and rohitrao@ if you see this."; - if (_selection && [[_selection text] length] > [[self text] length]) { - return base::SysNSStringToUTF16( - [[_selection text] substringFromIndex:[[self text] length]]); + if (!_selection) { + [super touchesBegan:touches withEvent:event]; + return; } - return base::string16(); + + // Only consider a single touch. + UITouch* touch = [touches anyObject]; + if (!touch) + return; + + // Accept selection. + NSString* newText = [[self nsDisplayedText] copy]; + [self clearAutocompleteText]; + [self setText:newText]; } - (void)select:(id)sender { @@ -399,6 +655,94 @@ [super selectAll:sender]; } +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { + // Disable the "Define" menu item. iOS7 implements this with a private + // selector. Avoid using private APIs by instead doing a string comparison. + if ([NSStringFromSelector(action) hasSuffix:@"define:"]) { + return NO; + } + + // Disable the RTL arrow menu item. The omnibox sets alignment based on the + // text in the field, and should not be overridden. + if ([NSStringFromSelector(action) hasPrefix:@"makeTextWritingDirection"]) { + return NO; + } + + return [super canPerformAction:action withSender:sender]; +} + +#pragma mark Copy/Paste + +// Overridden to allow for custom omnibox copy behavior. This includes +// preprending http:// to the copied URL if needed. +- (void)copy:(id)sender { + id<OmniboxTextFieldDelegate> delegate = [self delegate]; + BOOL handled = NO; + + // Must test for the onCopy method, since it's optional. + if ([delegate respondsToSelector:@selector(onCopy)]) + handled = [delegate onCopy]; + + // iOS 4 doesn't expose an API that allows the delegate to handle the copy + // operation, so let the superclass perform the copy if the delegate couldn't. + if (!handled) + [super copy:sender]; +} + +// Overridden to notify the delegate that a paste is in progress. +- (void)paste:(id)sender { + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(willPaste)]) + [delegate willPaste]; + [super paste:sender]; +} + +#pragma mark UIKeyInput + +- (void)deleteBackward { + // Must test for the onDeleteBackward method, since it's optional. + if ([[self delegate] respondsToSelector:@selector(onDeleteBackward)]) + [[self delegate] onDeleteBackward]; + [super deleteBackward]; +} + +#pragma mark - helpers + +// Gets the bounds of the rect covering the URL. +- (CGRect)preEditLabelRectForBounds:(CGRect)bounds { + return [self editingRectForBounds:self.bounds]; +} + +- (NSTextAlignment)bestAlignmentForText:(NSString*)text { + if (text.length) { + NSString* lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage( + (CFStringRef)text, CFRangeMake(0, text.length))); + + if ([NSLocale characterDirectionForLanguage:lang] == + NSLocaleLanguageDirectionRightToLeft) { + return NSTextAlignmentRight; + } + } + return NSTextAlignmentLeft; +} + +- (NSTextAlignment)preEditTextAlignment { + // If the pre-edit text is wider than the omnibox, right-align the text so it + // ends at the same x coord as the blue selection box. + CGSize textSize = + [_preEditStaticLabel.text cr_pixelAlignedSizeWithFont:_font]; + // Note, this does not need to support RTL, as URLs are always LTR. + return textSize.width < _preEditStaticLabel.frame.size.width + ? NSTextAlignmentLeft + : NSTextAlignmentRight; +} + +- (NSString*)nsDisplayedText { + if (_selection) + return [_selection text]; + return [self text]; +} + // Creates the SelectedTextLabel if it doesn't already exist and adds it as a // subview. - (void)createSelectionViewIfNecessary { @@ -414,13 +758,6 @@ [self hideTextAndCursor]; } -- (void)deleteBackward { - // Must test for the onDeleteBackward method, since it's optional. - if ([[self delegate] respondsToSelector:@selector(onDeleteBackward)]) - [[self delegate] onDeleteBackward]; - [super deleteBackward]; -} - // Helper method used to set the text of this field. Updates the selection view // to contain the correct inline autocomplete text. - (void)setTextInternal:(NSAttributedString*)text @@ -490,114 +827,6 @@ alpha:1.0]; } -// Ensures that attributedText always uses the proper style attributes. -- (void)setAttributedText:(NSAttributedString*)attributedText { - NSMutableAttributedString* mutableText = [attributedText mutableCopy]; - NSRange entireString = NSMakeRange(0, [mutableText length]); - - // Set the font. - [mutableText addAttribute:NSFontAttributeName value:_font range:entireString]; - - // When editing, use the default text color for all text. - if (self.editing) { - // Hide the text when the |_selection| label is displayed. - UIColor* textColor = - _selection ? [UIColor clearColor] : _displayedTextColor; - [mutableText addAttribute:NSForegroundColorAttributeName - value:textColor - range:entireString]; - } else { - NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init]; - // URLs have their text direction set to to LTR (avoids RTL characters - // making the URL render from right to left, as per the URL rendering - // standard described here: https://url.spec.whatwg.org/#url-rendering - [style setBaseWritingDirection:NSWritingDirectionLeftToRight]; - - // Set linebreak mode to 'clipping' to ensure the text is never elided. - // This is a workaround for iOS 6, where it appears that - // [self.attributedText size] is not wide enough for the string (e.g. a URL - // else ending with '.com' will be elided to end with '.c...'). It appears - // to be off by one point so clipping is acceptable as it doesn't actually - // cut off any of the text. - [style setLineBreakMode:NSLineBreakByClipping]; - - [mutableText addAttribute:NSParagraphStyleAttributeName - value:style - range:entireString]; - } - - [super setAttributedText:mutableText]; -} - -// Normally NSTextAlignmentNatural would handle text alignment automatically, -// but there are numerous edge case issues with it, so it's simpler to just -// manually update the text alignment and writing direction of the UITextField. -- (void)updateTextDirection { - // Setting the empty field to Natural seems to let iOS update the cursor - // position when the keyboard language is changed. - if (![self text].length) { - [self setTextAlignment:NSTextAlignmentNatural]; - return; - } - - NSTextAlignment alignment = [self bestTextAlignment]; - [self setTextAlignment:alignment]; - if (!base::ios::IsRunningOnIOS11OrLater()) { - // TODO(crbug.com/730461): Remove this entire block once it's been tested - // on trunk. - UITextWritingDirection writingDirection = - alignment == NSTextAlignmentLeft ? UITextWritingDirectionLeftToRight - : UITextWritingDirectionRightToLeft; - [self - setBaseWritingDirection:writingDirection - forRange: - [self - textRangeFromPosition:[self beginningOfDocument] - toPosition:[self endOfDocument]]]; - } -} - -- (void)setPlaceholder:(NSString*)placeholder { - if (placeholder && _placeholderTextColor) { - NSDictionary* attributes = - @{NSForegroundColorAttributeName : _placeholderTextColor}; - self.attributedPlaceholder = - [[NSAttributedString alloc] initWithString:placeholder - attributes:attributes]; - } else { - [super setPlaceholder:placeholder]; - } -} - -- (void)setText:(NSString*)text { - NSAttributedString* as = [[NSAttributedString alloc] initWithString:text]; - if (self.text.length > 0 && as.length == 0) { - // Remove the fade animations before the subviews are removed. - [self cleanUpFadeAnimations]; - } - [self setTextInternal:as autocompleteLength:0]; -} - -- (void)setText:(NSAttributedString*)text - userTextLength:(size_t)userTextLength { - DCHECK_LE(userTextLength, [text length]); - - NSUInteger autocompleteLength = [text length] - userTextLength; - [self setTextInternal:text autocompleteLength:autocompleteLength]; -} - -- (BOOL)hasAutocompleteText { - return !!_selection; -} - -- (void)clearAutocompleteText { - if (_selection) { - [_selection removeFromSuperview]; - _selection = nil; - [self showTextAndCursor]; - } -} - - (BOOL)isColorHidden:(UIColor*)color { return ([color isEqual:[UIColor clearColor]] || CGColorGetAlpha(color.CGColor) < 0.05); @@ -621,74 +850,6 @@ [self setTextColor:[UIColor clearColor]]; } -- (NSString*)markedText { - DCHECK([self conformsToProtocol:@protocol(UITextInput)]); - return [self textInRange:[self markedTextRange]]; -} - -- (CGRect)textRectForBounds:(CGRect)bounds { - CGRect newBounds = [super textRectForBounds:bounds]; - - LayoutRect textRectLayout = - LayoutRectForRectInBoundingRect(newBounds, bounds); - CGFloat textInset = kTextInsetNoLeftView; - - // Shift the text right and reduce the width to create empty space between the - // left view and the omnibox text. - textRectLayout.position.leading += textInset + kTextAreaLeadingOffset; - textRectLayout.size.width -= textInset - kTextAreaLeadingOffset; - - if (IsIPadIdiom()) { - if (!IsCompactTablet()) { - // Adjust the width so that the text doesn't overlap with the bookmark and - // voice search buttons which are displayed inside the omnibox. - textRectLayout.size.width += self.rightView.bounds.size.width - - kVoiceSearchButtonWidth - kStarButtonWidth; - } - } - - return LayoutRectGetRect(textRectLayout); -} - -- (CGRect)editingRectForBounds:(CGRect)bounds { - CGRect newBounds = [super editingRectForBounds:bounds]; - - // -editingRectForBounds doesn't account for rightViews that aren't flush - // with the right edge, it just looks at the rightView's width. Account for - // the offset here. - CGFloat rightViewMaxX = CGRectGetMaxX([self rightViewRectForBounds:bounds]); - if (rightViewMaxX) - newBounds.size.width -= bounds.size.width - rightViewMaxX; - - LayoutRect editingRectLayout = - LayoutRectForRectInBoundingRect(newBounds, bounds); - editingRectLayout.position.leading += kTextAreaLeadingOffset; - editingRectLayout.position.leading += kTextInset; - editingRectLayout.size.width -= kTextInset + kEditingRectWidthInset; - if (IsIPadIdiom()) { - if (!IsCompactTablet() && !self.rightView) { - // Normally the clear button shrinks the edit box, but if the rightView - // isn't set, shrink behind the mic icons. - editingRectLayout.size.width -= kVoiceSearchButtonWidth; - } - } else { - CGFloat xDiff = editingRectLayout.position.leading - kEditingRectX; - editingRectLayout.position.leading = kEditingRectX; - editingRectLayout.size.width += xDiff; - } - // Don't let the edit rect extend over the clear button. The right view - // is hidden during animations, so fake its width here. - if (self.rightViewMode == UITextFieldViewModeNever) - editingRectLayout.size.width -= self.rightView.bounds.size.width; - - newBounds = LayoutRectGetRect(editingRectLayout); - - // Position the selection view appropriately. - [_selection setFrame:newBounds]; - - return newBounds; -} - - (CGRect)rectForDrawTextInRect:(CGRect)rect { // The goal is to always show the most significant part of the hostname // (i.e. the end of the TLD). @@ -752,27 +913,11 @@ return rect; } -// Enumerate url components (host, path) and draw each one in different rect. -- (void)drawTextInRect:(CGRect)rect { - if (base::ios::IsRunningOnOrLater(11, 1, 0)) { - // -[UITextField drawTextInRect:] ignores the argument, so we can't do - // anything on 11.1 and up. - [super drawTextInRect:rect]; - return; - } - - // Save and restore the graphics state because rectForDrawTextInRect may - // apply an image mask to fade out beginning and/or end of the URL. - gfx::ScopedCGContextSaveGState saver(UIGraphicsGetCurrentContext()); - [super drawTextInRect:[self rectForDrawTextInRect:rect]]; -} - -- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { - // Anything in the narrow bar above OmniboxTextFieldIOS view - // will also activate the text field. - if (point.y < 0) - point.y = 0; - return [super hitTest:point withEvent:event]; +- (NSArray*)fadeAnimationLayers { + NSMutableArray* layers = [NSMutableArray array]; + for (UIView* subview in self.subviews) + [layers addObject:subview.layer]; + return layers; } - (BOOL)isTextFieldLTR { @@ -781,20 +926,6 @@ UIUserInterfaceLayoutDirectionLeftToRight; } -// Overriding this method to offset the rightView property -// (containing a clear text button). -- (CGRect)rightViewRectForBounds:(CGRect)bounds { - // iOS9 added updated RTL support, but only half implemented it for - // UITextField. leftView and rightView were not renamed, but are are correctly - // swapped and treated as leadingView / trailingView. However, - // -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as - // leading and trailing. Hence the swapping below. - if ([self isTextFieldLTR]) { - return [self layoutRightViewForBounds:bounds]; - } - return [self layoutLeftViewForBounds:bounds]; -} - - (CGRect)layoutRightViewForBounds:(CGRect)bounds { if ([self rightView]) { CGSize rightViewSize = self.rightView.bounds.size; @@ -817,118 +948,8 @@ return CGRectZero; } -// Overriding this method to offset the leftView property -// (containing a placeholder image) consistently with omnibox text padding. -- (CGRect)leftViewRectForBounds:(CGRect)bounds { - // iOS9 added updated RTL support, but only half implemented it for - // UITextField. leftView and rightView were not renamed, but are are correctly - // swapped and treated as leadingView / trailingView. However, - // -leftViewRectForBounds and -rightViewRectForBounds are *not* treated as - // leading and trailing. Hence the swapping below. - if ([self isTextFieldLTR]) { - return [self layoutLeftViewForBounds:bounds]; - } - return [self layoutRightViewForBounds:bounds]; -} - - (CGRect)layoutLeftViewForBounds:(CGRect)bounds { return CGRectZero; } -- (void)animateFadeWithStyle:(OmniboxTextFieldFadeStyle)style { - // Animation values - BOOL isFadingIn = (style == OMNIBOX_TEXT_FIELD_FADE_STYLE_IN); - CGFloat beginOpacity = isFadingIn ? 0.0 : 1.0; - CGFloat endOpacity = isFadingIn ? 1.0 : 0.0; - CAMediaTimingFunction* opacityTiming = ios::material::TimingFunction( - isFadingIn ? ios::material::CurveEaseOut : ios::material::CurveEaseIn); - CFTimeInterval delay = isFadingIn ? ios::material::kDuration8 : 0.0; - - CAAnimation* labelAnimation = OpacityAnimationMake(beginOpacity, endOpacity); - labelAnimation.duration = - isFadingIn ? ios::material::kDuration6 : ios::material::kDuration8; - labelAnimation.timingFunction = opacityTiming; - labelAnimation = DelayedAnimationMake(labelAnimation, delay); - CAAnimation* auxillaryViewAnimation = - OpacityAnimationMake(beginOpacity, endOpacity); - auxillaryViewAnimation.duration = ios::material::kDuration8; - auxillaryViewAnimation.timingFunction = opacityTiming; - auxillaryViewAnimation = DelayedAnimationMake(auxillaryViewAnimation, delay); - - for (UIView* subview in self.subviews) { - if ([subview isKindOfClass:[UILabel class]]) { - [subview.layer addAnimation:labelAnimation - forKey:kOmniboxFadeAnimationKey]; - } else { - [subview.layer addAnimation:auxillaryViewAnimation - forKey:kOmniboxFadeAnimationKey]; - } - } -} - -- (NSArray*)fadeAnimationLayers { - NSMutableArray* layers = [NSMutableArray array]; - for (UIView* subview in self.subviews) - [layers addObject:subview.layer]; - return layers; -} - -- (void)cleanUpFadeAnimations { - RemoveAnimationForKeyFromLayers(kOmniboxFadeAnimationKey, - [self fadeAnimationLayers]); -} - -#pragma mark - Copy/Paste - -// Overridden to allow for custom omnibox copy behavior. This includes -// preprending http:// to the copied URL if needed. -- (void)copy:(id)sender { - id<OmniboxTextFieldDelegate> delegate = [self delegate]; - BOOL handled = NO; - - // Must test for the onCopy method, since it's optional. - if ([delegate respondsToSelector:@selector(onCopy)]) - handled = [delegate onCopy]; - - // iOS 4 doesn't expose an API that allows the delegate to handle the copy - // operation, so let the superclass perform the copy if the delegate couldn't. - if (!handled) - [super copy:sender]; -} - -// Overridden to notify the delegate that a paste is in progress. -- (void)paste:(id)sender { - id delegate = [self delegate]; - if ([delegate respondsToSelector:@selector(willPaste)]) - [delegate willPaste]; - [super paste:sender]; -} - -- (NSRange)selectedNSRange { - DCHECK([self isFirstResponder]); - UITextPosition* beginning = [self beginningOfDocument]; - UITextRange* selectedRange = [self selectedTextRange]; - NSInteger start = - [self offsetFromPosition:beginning toPosition:[selectedRange start]]; - NSInteger length = [self offsetFromPosition:[selectedRange start] - toPosition:[selectedRange end]]; - return NSMakeRange(start, length); -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - // Disable the "Define" menu item. iOS7 implements this with a private - // selector. Avoid using private APIs by instead doing a string comparison. - if ([NSStringFromSelector(action) hasSuffix:@"define:"]) { - return NO; - } - - // Disable the RTL arrow menu item. The omnibox sets alignment based on the - // text in the field, and should not be overridden. - if ([NSStringFromSelector(action) hasPrefix:@"makeTextWritingDirection"]) { - return NO; - } - - return [super canPerformAction:action withSender:sender]; -} - @end
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_egtest.mm index 13b54618..ded8ecb 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_egtest.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_egtest.mm
@@ -132,6 +132,14 @@ // to fail. @implementation TabSwitcherTransitionTestCase +// Rotate the device back to portrait if needed, since some tests attempt to run +// in landscape. +- (void)tearDown { + [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait + errorOrNil:nil]; + [super tearDown]; +} + // Sets up the EmbeddedTestServer as needed for tests. - (void)setUpTestServer { self.testServer->RegisterDefaultHandler( @@ -446,4 +454,36 @@ [ChromeEarlGreyUI openNewTab]; } +// Tests rotating the device while the switcher is not active. This is a +// regression test case for https://crbug.com/789975. +- (void)testRotationsWhileSwitcherIsNotActive { + NSString* tab_title = @"NormalTabLongerStringForTest1"; + [self setUpTestServer]; + [ChromeEarlGrey loadURL:[self makeURLForTitle:tab_title]]; + + // Show the tab switcher and return to the BVC, in portrait. + [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait + errorOrNil:nil]; + ShowTabSwitcher(); + SelectTab(tab_title); + [ChromeEarlGrey + waitForWebViewContainingText:base::SysNSStringToUTF8(tab_title)]; + + // Show the tab switcher and return to the BVC, in landscape. + [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft + errorOrNil:nil]; + ShowTabSwitcher(); + SelectTab(tab_title); + [ChromeEarlGrey + waitForWebViewContainingText:base::SysNSStringToUTF8(tab_title)]; + + // Show the tab switcher and return to the BVC, in portrait. + [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait + errorOrNil:nil]; + ShowTabSwitcher(); + SelectTab(tab_title); + [ChromeEarlGrey + waitForWebViewContainingText:base::SysNSStringToUTF8(tab_title)]; +} + @end
diff --git a/ios/web/web_state/js/resources/common.js b/ios/web/web_state/js/resources/common.js index 0784d37b..43eaf3a2 100644 --- a/ios/web/web_state/js/resources/common.js +++ b/ios/web/web_state/js/resources/common.js
@@ -230,6 +230,38 @@ element.type === 'number'; }; + /** + * Sets the value of a data-bound input using AngularJS. + * + * The method first set the value using the val() method. Then, if input is + * bound to a model value, it sets the model value. + * Documentation of relevant modules of AngularJS can be found at + * https://docs.angularjs.org/guide/databinding + * https://docs.angularjs.org/api/auto/service/$injector + * https://docs.angularjs.org/api/ng/service/$parse + * + * @param {string} value The value the input element will be set. + * @param {Element} input The input element of which the value is set. + **/ + function setInputElementAngularValue_(value, input) { + if (!input || !window['angular']) { + return; + } + var angular_element = window['angular'].element(input); + if (!angular_element) { + return; + } + angular_element.val(value); + var angular_model = angular_element.data('ngModel'); + if (!angular_model) { + return; + } + angular_element.injector().invoke(['$parse', function(parse) { + var setter = parse(angular_model); + setter.assign(angular_element.scope(), value); + }]) + } + /** * Sets the value of an input and dispatches a change event if * |shouldSendChangeEvent|. @@ -245,19 +277,16 @@ * void setChecked(bool nowChecked, TextFieldEventBehavior eventBehavior) * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp. * - * @param {(string|boolean)} value The value the input element will be set. - * For text input, it is the value to set in the field. - * For select, it is the value of the option to select. - * For checkable element, it is the checked value (true/false). + * @param {string} value The value the input element will be set. * @param {Element} input The input element of which the value is set. * @param {boolean} shouldSendChangeEvent Whether a change event should be * dispatched. */ __gCrWeb.common.setInputElementValue = function( value, input, shouldSendChangeEvent) { - if (!input) { - return; - } + if (!input) { + return; + } var changed = false; if (input.type === 'checkbox' || input.type === 'radio') { changed = input.checked !== value; @@ -271,10 +300,15 @@ // autofill and this method is only used for autofill for now, there is no // such check in this implementation. var sanitizedValue = __gCrWeb.common.sanitizeValueForInputElement( - /** @type {string} */ (value), input); + value, input); changed = sanitizedValue !== input.value; input.value = sanitizedValue; } + if (window['angular']) { + // The page uses the AngularJS framework. Update the angular value before + // sending events. + setInputElementAngularValue_(value, input); + } if (changed && shouldSendChangeEvent) { __gCrWeb.common.notifyElementValueChanged(input); }
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc index 1b7b8d24..a0abdad 100644 --- a/media/audio/mac/audio_manager_mac.cc +++ b/media/audio/mac/audio_manager_mac.cc
@@ -1108,13 +1108,39 @@ OSSTATUS_DLOG_IF(WARNING, result != noErr, result) << "Could not get audio device latency."; - property_address.mSelector = kAudioStreamPropertyLatency; + // Retrieve stream ids and take the stream latency from the first stream. + // There may be multiple streams with different latencies, but since we're + // likely using this delay information for a/v sync we must choose one of + // them; Apple recommends just taking the first entry. + // + // TODO(dalecurtis): Refactor all these "get data size" + "get data" calls + // into a common utility function that just returns a std::unique_ptr. UInt32 stream_latency_frames = 0; - size = sizeof(stream_latency_frames); - result = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr, - &size, &stream_latency_frames); - OSSTATUS_DLOG_IF(WARNING, result != noErr, result) - << "Could not get stream latency."; + property_address.mSelector = kAudioDevicePropertyStreams; + result = AudioObjectGetPropertyDataSize(device_id, &property_address, 0, + nullptr, &size); + if (result == noErr && size >= sizeof(AudioStreamID)) { + std::unique_ptr<uint8_t[]> stream_id_storage(new uint8_t[size]); + AudioStreamID* stream_ids = + reinterpret_cast<AudioStreamID*>(stream_id_storage.get()); + result = AudioObjectGetPropertyData(device_id, &property_address, 0, + nullptr, &size, stream_ids); + if (result == noErr) { + property_address.mSelector = kAudioStreamPropertyLatency; + size = sizeof(stream_latency_frames); + result = + AudioObjectGetPropertyData(stream_ids[0], &property_address, 0, + nullptr, &size, &stream_latency_frames); + OSSTATUS_DLOG_IF(WARNING, result != noErr, result) + << "Could not get stream latency for stream #0."; + } else { + OSSTATUS_DLOG(WARNING, result) + << "Could not get audio device stream ids."; + } + } else { + OSSTATUS_DLOG_IF(WARNING, result != noErr, result) + << "Could not get audio device stream ids size."; + } return base::TimeDelta::FromSecondsD(audio_unit_latency_sec) + AudioTimestampHelper::FramesToTime(
diff --git a/remoting/protocol/webrtc_dummy_video_encoder.cc b/remoting/protocol/webrtc_dummy_video_encoder.cc index b10b396..d0e2b50 100644 --- a/remoting/protocol/webrtc_dummy_video_encoder.cc +++ b/remoting/protocol/webrtc_dummy_video_encoder.cc
@@ -11,6 +11,7 @@ #include "base/callback.h" #include "base/logging.h" #include "base/memory/ptr_util.h" +#include "base/rand_util.h" #include "base/stl_util.h" #include "base/synchronization/lock.h" #include "base/threading/thread_task_runner_handle.h" @@ -58,7 +59,11 @@ base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer) : main_task_runner_(main_task_runner), state_(kUninitialized), - video_channel_state_observer_(video_channel_state_observer) {} + video_channel_state_observer_(video_channel_state_observer) { + // Initialize randomly to avoid replay attacks. + base::RandBytes(&picture_id_, sizeof(picture_id_)); + picture_id_ &= 0x7fff; +} WebrtcDummyVideoEncoder::~WebrtcDummyVideoEncoder() = default; @@ -163,7 +168,8 @@ vp8_info->simulcastIdx = 0; vp8_info->temporalIdx = webrtc::kNoTemporalIdx; vp8_info->tl0PicIdx = webrtc::kNoTl0PicIdx; - vp8_info->pictureId = webrtc::kNoPictureId; + vp8_info->pictureId = picture_id_; + picture_id_ = (picture_id_ + 1) & 0x7fff; } else if (frame.codec == webrtc::kVideoCodecVP9) { webrtc::CodecSpecificInfoVP9* vp9_info = &codec_specific_info.codecSpecific.VP9; @@ -179,7 +185,8 @@ vp9_info->temporal_idx = webrtc::kNoTemporalIdx; vp9_info->spatial_idx = webrtc::kNoSpatialIdx; vp9_info->tl0_pic_idx = webrtc::kNoTl0PicIdx; - vp9_info->picture_id = webrtc::kNoPictureId; + vp9_info->picture_id = picture_id_; + picture_id_ = (picture_id_ + 1) & 0x7fff; } else if (frame.codec == webrtc::kVideoCodecH264) { #if defined(USE_H264_ENCODER) webrtc::CodecSpecificInfoH264* h264_info =
diff --git a/remoting/protocol/webrtc_dummy_video_encoder.h b/remoting/protocol/webrtc_dummy_video_encoder.h index eae4bce..c8d2f468 100644 --- a/remoting/protocol/webrtc_dummy_video_encoder.h +++ b/remoting/protocol/webrtc_dummy_video_encoder.h
@@ -64,6 +64,10 @@ webrtc::EncodedImageCallback* encoded_callback_ = nullptr; base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer_; + + // 15-bit incrementing ID applied to RTP payload for each video frame when + // VPX is used. + uint16_t picture_id_ = 0; }; // This is the external encoder factory implementation that is passed to
diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc index 277e61a..152dceb 100644 --- a/sandbox/linux/syscall_broker/broker_client.cc +++ b/sandbox/linux/syscall_broker/broker_client.cc
@@ -229,5 +229,50 @@ return return_value; } +int BrokerClient::Readlink(const char* path, char* buf, size_t bufsize) { + if (fast_check_in_client_) { + bool ignore; + if (!broker_policy_.GetFileNameIfAllowedToOpen(path, O_RDONLY, nullptr, + &ignore)) { + return -broker_policy_.denied_errno(); + } + } + + base::Pickle write_pickle; + write_pickle.WriteInt(COMMAND_READLINK); + write_pickle.WriteString(path); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsg( + ipc_channel_.get(), reply_buf, sizeof(reply_buf), &returned_fd, + write_pickle); + + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + int return_length = 0; + const char* return_data = nullptr; + if (!iter.ReadInt(&return_value)) + return -ENOMEM; + if (return_value < 0) + return return_value; + if (!iter.ReadData(&return_data, &return_length)) + return -ENOMEM; + if (return_length < 0) + return -ENOMEM; + if (static_cast<size_t>(return_length) > bufsize) + return -ENAMETOOLONG; + memcpy(buf, return_data, return_length); + return return_value; +} + } // namespace syscall_broker } // namespace sandbox
diff --git a/sandbox/linux/syscall_broker/broker_client.h b/sandbox/linux/syscall_broker/broker_client.h index 9c81857..3ad3f25 100644 --- a/sandbox/linux/syscall_broker/broker_client.h +++ b/sandbox/linux/syscall_broker/broker_client.h
@@ -65,6 +65,9 @@ // This is async signal safe. int Rename(const char* oldpath, const char* newpath); + // Can be used in place of Readlink(). + int Readlink(const char* path, char* buf, size_t bufsize); + // Get the file descriptor used for IPC. This is used for tests. int GetIPCDescriptor() const { return ipc_channel_.get(); }
diff --git a/sandbox/linux/syscall_broker/broker_common.h b/sandbox/linux/syscall_broker/broker_common.h index 30b2ce19..dbdc327 100644 --- a/sandbox/linux/syscall_broker/broker_common.h +++ b/sandbox/linux/syscall_broker/broker_common.h
@@ -9,7 +9,6 @@ #include <stddef.h> namespace sandbox { - namespace syscall_broker { const size_t kMaxMessageLength = 4096; @@ -35,10 +34,10 @@ COMMAND_STAT, COMMAND_STAT64, COMMAND_RENAME, + COMMAND_READLINK, }; } // namespace syscall_broker - } // namespace sandbox #endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_
diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc index 375747e..68a5260 100644 --- a/sandbox/linux/syscall_broker/broker_host.cc +++ b/sandbox/linux/syscall_broker/broker_host.cc
@@ -6,6 +6,7 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <stddef.h> #include <sys/socket.h> #include <sys/stat.h> @@ -110,7 +111,6 @@ IPCCommand command_type, const std::string& requested_filename, base::Pickle* write_pickle) { - DCHECK(write_pickle); DCHECK(command_type == COMMAND_STAT || command_type == COMMAND_STAT64); const char* file_to_access = nullptr; if (!policy.GetFileNameIfAllowedToAccess(requested_filename.c_str(), F_OK, @@ -143,7 +143,6 @@ const std::string& old_filename, const std::string& new_filename, base::Pickle* write_pickle) { - DCHECK(write_pickle); bool ignore; const char* old_file_to_access = nullptr; const char* new_file_to_access = nullptr; @@ -161,6 +160,27 @@ write_pickle->WriteInt(0); } +// Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes. +void ReadlinkFileForIPC(const BrokerPolicy& policy, + const std::string& filename, + base::Pickle* write_pickle) { + bool ignore; + const char* file_to_access = nullptr; + if (!policy.GetFileNameIfAllowedToOpen(filename.c_str(), O_RDONLY, + &file_to_access, &ignore)) { + write_pickle->WriteInt(-policy.denied_errno()); + return; + } + char buf[PATH_MAX]; + ssize_t result = readlink(file_to_access, buf, sizeof(buf)); + if (result < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(result); + write_pickle->WriteData(buf, result); +} + // Handle a |command_type| request contained in |iter| and write the reply // to |write_pickle|, adding any files opened to |opened_files|. bool HandleRemoteCommand(const BrokerPolicy& policy, @@ -206,6 +226,13 @@ RenameFileForIPC(policy, old_filename, new_filename, write_pickle); break; } + case COMMAND_READLINK: { + std::string filename; + if (!iter.ReadString(&filename)) + return false; + ReadlinkFileForIPC(policy, filename, write_pickle); + break; + } default: LOG(ERROR) << "Invalid IPC command"; return false;
diff --git a/sandbox/linux/syscall_broker/broker_process.cc b/sandbox/linux/syscall_broker/broker_process.cc index 46e207ed..9bec522 100644 --- a/sandbox/linux/syscall_broker/broker_process.cc +++ b/sandbox/linux/syscall_broker/broker_process.cc
@@ -129,6 +129,11 @@ return broker_client_->Rename(oldpath, newpath); } +int BrokerProcess::Readlink(const char* path, char* buf, size_t bufsize) const { + RAW_CHECK(initialized_); + return broker_client_->Readlink(path, buf, bufsize); +} + #if defined(MEMORY_SANITIZER) #define BROKER_UNPOISON_STRING(x) __msan_unpoison_string(x) #else @@ -196,6 +201,22 @@ return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), reinterpret_cast<struct stat*>(args.args[2])); #endif +#if defined(__NR_readlink) + case __NR_readlink: + return broker_process->Readlink( + reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<char*>(args.args[1]), + static_cast<size_t>(args.args[2])); +#endif +#if defined(__NR_readlinkat) + case __NR_readlinkat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return broker_process->Readlink( + reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<char*>(args.args[2]), + static_cast<size_t>(args.args[3])); +#endif #if defined(__NR_rename) case __NR_rename: return broker_process->Rename(
diff --git a/sandbox/linux/syscall_broker/broker_process.h b/sandbox/linux/syscall_broker/broker_process.h index efa84bb..f9a571d9 100644 --- a/sandbox/linux/syscall_broker/broker_process.h +++ b/sandbox/linux/syscall_broker/broker_process.h
@@ -83,6 +83,10 @@ // It's similar to the rename() system call and will return -errno on errors. int Rename(const char* oldpath, const char* newpath) const; + // Can be used in place of readlink(). Will be async signal safe. + // It's similar to the read() system call and will return -errno on errors. + int Readlink(const char* path, char* buf, size_t bufsize) const; + int broker_pid() const { return broker_pid_; } // Handler to be used with a bpf_dsl Trap() function to forward system calls
diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc index a536003..df49a50 100644 --- a/sandbox/linux/syscall_broker/broker_process_unittest.cc +++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc
@@ -60,7 +60,7 @@ std::unique_ptr<BrokerProcess> open_broker( new BrokerProcess(EPERM, permissions)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker->Init(base::BindRepeating(&NoOpCallback))); ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); // Destroy the broker and check it has exited properly. @@ -71,7 +71,7 @@ TEST(BrokerProcess, TestOpenAccessNull) { std::vector<BrokerFilePermission> empty; BrokerProcess open_broker(EPERM, empty); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = open_broker.Open(NULL, O_RDONLY); ASSERT_EQ(fd, -EFAULT); @@ -97,7 +97,7 @@ permissions.push_back(BrokerFilePermission::ReadWrite(kRW_WhiteListed)); BrokerProcess open_broker(denied_errno, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = -1; fd = open_broker.Open(kR_WhiteListed, O_RDONLY); @@ -254,7 +254,7 @@ permissions.push_back(BrokerFilePermission::ReadOnlyRecursive("/proc/")); std::unique_ptr<BrokerProcess> open_broker( new BrokerProcess(EPERM, permissions, fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker->Init(base::BindRepeating(&NoOpCallback))); // Open cpuinfo via the broker. int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); @@ -313,7 +313,7 @@ std::unique_ptr<BrokerProcess> open_broker( new BrokerProcess(EPERM, permissions, fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker->Init(base::BindRepeating(&NoOpCallback))); int fd = -1; fd = open_broker->Open(kFileCpuInfo, O_RDWR); @@ -390,7 +390,7 @@ permissions.push_back(BrokerFilePermission::ReadWrite(tempfile_name)); BrokerProcess open_broker(EPERM, permissions); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); // Check we can access that file with read or write. int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); @@ -425,7 +425,7 @@ BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, true /* quiet_failures_for_tests */); - SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + SANDBOX_ASSERT(open_broker.Init(base::BindRepeating(&NoOpCallback))); const pid_t broker_pid = open_broker.broker_pid(); SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); @@ -449,7 +449,7 @@ permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. int fd = -1; int ret = 0; @@ -538,7 +538,7 @@ permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); BrokerProcess open_broker(EPERM, permissions); - SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + SANDBOX_ASSERT(open_broker.Init(base::BindRepeating(&NoOpCallback))); const int ipc_fd = BrokerProcessTestHelper::GetIPCDescriptor(&open_broker); SANDBOX_ASSERT(ipc_fd >= 0); @@ -588,7 +588,7 @@ BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, false /* quiet_failures_for_tests */); - ASSERT_TRUE(open_broker.Init(base::Bind(&CloseFD, lifeline_fds[0]))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&CloseFD, lifeline_fds[0]))); // Make sure the writing end only exists in the broker process. CloseFD(lifeline_fds[1]); base::ScopedFD reader(lifeline_fds[0]); @@ -624,7 +624,7 @@ permissions.push_back(BrokerFilePermission::ReadWriteCreate(tempfile_name)); BrokerProcess open_broker(EPERM, permissions); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = -1; @@ -674,7 +674,7 @@ // Nonexistent file with no permissions to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-EPERM, open_broker.Stat(nonesuch_name, &sb)); @@ -683,7 +683,7 @@ // Actual file with no permission to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-EPERM, open_broker.Stat(tempfile_name, &sb)); @@ -693,7 +693,7 @@ std::vector<BrokerFilePermission> permissions; permissions.push_back(BrokerFilePermission::ReadOnly(nonesuch_name)); BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, &sb)); @@ -703,7 +703,7 @@ std::vector<BrokerFilePermission> permissions; permissions.push_back(BrokerFilePermission::ReadOnly(tempfile_name)); BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(0, open_broker.Stat(tempfile_name, &sb)); @@ -754,7 +754,7 @@ bool fast_check_in_client = false; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. @@ -768,7 +768,7 @@ bool fast_check_in_client = false; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. @@ -783,7 +783,7 @@ bool fast_check_in_client = false; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. @@ -798,7 +798,7 @@ bool fast_check_in_client = false; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. @@ -813,7 +813,7 @@ bool fast_check_in_client = false; BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(0, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and files were moved around. @@ -825,5 +825,77 @@ unlink(newpath.c_str()); } +void TestReadlinkHelper(bool fast_check_in_client) { + std::string oldpath; + std::string newpath; + { + // Just to generate names and ensure they do not exist upon scope exit. + ScopedTemporaryFile oldfile; + ScopedTemporaryFile newfile; + oldpath = oldfile.full_file_name(); + newpath = newfile.full_file_name(); + } + + // Now make a link from old to new path name. + EXPECT_TRUE(symlink(oldpath.c_str(), newpath.c_str()) == 0); + + const char* nonesuch_name = "/mbogo/nonesuch"; + const char* oldpath_name = oldpath.c_str(); + const char* newpath_name = newpath.c_str(); + char buf[1024]; + { + // Nonexistent file with no permissions to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + } + { + // Actual file with no permissions to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-EPERM, open_broker.Readlink(newpath_name, buf, sizeof(buf))); + } + { + // Nonexistent file with permissions to see file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(nonesuch_name)); + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-ENOENT, open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + } + { + // Actual file with permissions to see file. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(newpath_name)); + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + ssize_t retlen = open_broker.Readlink(newpath_name, buf, sizeof(buf)); + EXPECT_TRUE(retlen == static_cast<ssize_t>(strlen(oldpath_name))); + EXPECT_EQ(0, memcmp(oldpath_name, buf, retlen)); + } + { + // Actual file with permissions to see file, but too small a buffer. + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(newpath_name)); + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-ENAMETOOLONG, open_broker.Readlink(newpath_name, buf, 4)); + } + + // Cleanup both paths. + unlink(oldpath.c_str()); + unlink(newpath.c_str()); +} + +TEST(BrokerProcess, ReadlinkFileClient) { + TestReadlinkHelper(true); +} + +TEST(BrokerProcess, ReadlinkFileHost) { + TestReadlinkHelper(false); +} + } // namespace syscall_broker } // namespace sandbox
diff --git a/testing/buildbot/filters/OWNERS b/testing/buildbot/filters/OWNERS index 83f9ffc..42c55bb 100644 --- a/testing/buildbot/filters/OWNERS +++ b/testing/buildbot/filters/OWNERS
@@ -1,15 +1,4 @@ * -# PlzNavigate: please check with team before disabling any tests. -per-file browser-side-navigation*=set noparent -per-file browser-side-navigation*=ananta@chromium.org -per-file browser-side-navigation*=arthursonzogni@chromium.org -per-file browser-side-navigation*=clamy@chromium.org -per-file browser-side-navigation*=creis@chromium.org -per-file browser-side-navigation*=jam@chromium.org -per-file browser-side-navigation*=nasko@chromium.org -per-file browser-side-navigation*=scottmg@chromium.org -per-file browser-side-navigation*=yzshen@chromium.org - per-file fuchsia*=file://build/fuchsia/OWNERS # TEAM: infra-dev@chromium.org
diff --git a/testing/libfuzzer/fuzzers/v8_fuzzer.cc b/testing/libfuzzer/fuzzers/v8_fuzzer.cc index 89e447c..6908515 100644 --- a/testing/libfuzzer/fuzzers/v8_fuzzer.cc +++ b/testing/libfuzzer/fuzzers/v8_fuzzer.cc
@@ -27,7 +27,7 @@ // kSleepSeconds + kMaxExecutionSeconds. // TODO(metzman): Determine if having such a short timeout causes too much // indeterminism. -static const seconds kMaxExecutionSeconds(12); +static const seconds kMaxExecutionSeconds(7); // Inspired by/copied from d8 code, this allocator will return nullptr when // an allocation request is made that puts currently_allocated_ over @@ -153,11 +153,11 @@ } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + static Environment* env = new Environment(); + if (size < 1) return 0; - static Environment* env = new Environment(); - v8::Isolate::Scope isolate_scope(env->isolate); v8::HandleScope handle_scope(env->isolate); v8::Local<v8::Context> context = v8::Context::New(env->isolate);
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index a3a4fa4..dbde41f 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -3257,6 +3257,37 @@ ] } ], + "SavePreviousDocumentResources": [ + { + "platforms": [ + "android", + "chromeos", + "linux", + "mac", + "win" + ], + "experiments": [ + { + "name": "until-dcl", + "params": { + "until": "onDOMContentLoaded" + }, + "enable_features": [ + "SavePreviousDocumentResources" + ] + }, + { + "name": "until-onload", + "params": { + "until": "onload" + }, + "enable_features": [ + "SavePreviousDocumentResources" + ] + } + ] + } + ], "SearchEnginePromo": [ { "platforms": [
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls b/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls index 531662d..7627acd 100644 --- a/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls +++ b/third_party/WebKit/LayoutTests/FlagExpectations/root-layer-scrolls
@@ -47,12 +47,6 @@ crbug.com/417782 inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot.js [ Failure ] crbug.com/417782 inspector-protocol/layers/paint-profiler.js [ Crash ] crbug.com/417782 inspector-protocol/page/get-layout-metrics.js [ Failure ] -crbug.com/417782 paint/invalidation/resize-iframe-text.html [ Failure ] -crbug.com/417782 paint/invalidation/scroll/overflow-scroll-body-appear.html [ Failure ] -crbug.com/417782 paint/invalidation/svg/absolute-sized-document-no-scrollbars.svg [ Failure ] -crbug.com/417782 paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1.html [ Failure ] -crbug.com/417782 paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2.html [ Failure ] -crbug.com/417782 paint/invalidation/svg/window.svg [ Failure ] crbug.com/417782 paint/invalidation/window-resize/window-resize-vertical-writing-mode.html [ Crash ] crbug.com/417782 [ Mac Win ] paint/overflow/fixed-background-scroll-window.html [ Failure ] crbug.com/417782 plugins/webview-plugin-nested-iframe-scroll.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/absolute-sized-document-no-scrollbars-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/absolute-sized-document-no-scrollbars-expected.txt index edd7fe5..2aae581 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/absolute-sized-document-no-scrollbars-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/absolute-sized-document-no-scrollbars-expected.txt
@@ -3,7 +3,14 @@ { "name": "LayoutView #document", "bounds": [800, 600], - "backgroundColor": "#FFFFFF" + "backgroundColor": "#FFFFFF", + "paintInvalidations": [ + { + "object": "LayoutView #document", + "rect": [0, 0, 800, 600], + "reason": "style change" + } + ] }, { "name": "Scrolling Layer", @@ -17,6 +24,11 @@ "backgroundColor": "#FFFFFF", "paintInvalidations": [ { + "object": "LayoutView #document", + "rect": [0, 0, 800, 600], + "reason": "background on scrolling contents layer" + }, + { "object": "LayoutSVGRect rect", "rect": [0, 0, 576, 432], "reason": "style change" @@ -36,6 +48,14 @@ ], "objectPaintInvalidations": [ { + "object": "Scrolling Contents Layer", + "reason": "background on scrolling contents layer" + }, + { + "object": "LayoutView #document", + "reason": "style change" + }, + { "object": "LayoutSVGRoot svg", "reason": "style change" },
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1-expected.txt new file mode 100644 index 0000000..3f738b5 --- /dev/null +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-1-expected.txt
@@ -0,0 +1,73 @@ +{ + "objectPaintInvalidations": [ + { + "object": "LayoutBlockFlow BODY", + "reason": "geometry" + }, + { + "object": "RootInlineBox", + "reason": "geometry" + }, + { + "object": "LayoutEmbeddedObject OBJECT", + "reason": "style change" + }, + { + "object": "HorizontalScrollbar", + "reason": "scroll control" + }, + { + "object": "VerticalScrollbar", + "reason": "scroll control" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow HTML", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow BODY", + "reason": "geometry" + }, + { + "object": "RootInlineBox", + "reason": "geometry" + }, + { + "object": "LayoutEmbeddedObject OBJECT", + "reason": "geometry" + }, + { + "object": "LayoutText #text", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutSVGRoot svg", + "reason": "geometry" + }, + { + "object": "LayoutSVGRect rect", + "reason": "geometry" + }, + { + "object": "LayoutSVGRect rect", + "reason": "geometry" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2-expected.txt new file mode 100644 index 0000000..3f738b5 --- /dev/null +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/paint/invalidation/svg/deep-nested-embedded-svg-size-changes-no-layout-triggers-2-expected.txt
@@ -0,0 +1,73 @@ +{ + "objectPaintInvalidations": [ + { + "object": "LayoutBlockFlow BODY", + "reason": "geometry" + }, + { + "object": "RootInlineBox", + "reason": "geometry" + }, + { + "object": "LayoutEmbeddedObject OBJECT", + "reason": "style change" + }, + { + "object": "HorizontalScrollbar", + "reason": "scroll control" + }, + { + "object": "VerticalScrollbar", + "reason": "scroll control" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow HTML", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow BODY", + "reason": "geometry" + }, + { + "object": "RootInlineBox", + "reason": "geometry" + }, + { + "object": "LayoutEmbeddedObject OBJECT", + "reason": "geometry" + }, + { + "object": "LayoutText #text", + "reason": "geometry" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutSVGRoot svg", + "reason": "geometry" + }, + { + "object": "LayoutSVGRect rect", + "reason": "geometry" + }, + { + "object": "LayoutSVGRect rect", + "reason": "geometry" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt index 3801109..f36dc547 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt
@@ -7,6 +7,11 @@ "paintInvalidations": [ { "object": "LayoutView #document", + "rect": [0, 0, 800, 600], + "reason": "style change" + }, + { + "object": "LayoutView #document", "rect": [0, 585, 785, 15], "reason": "scroll control" }, @@ -74,6 +79,10 @@ "objectPaintInvalidations": [ { "object": "LayoutView #document", + "reason": "style change" + }, + { + "object": "LayoutView #document", "reason": "geometry" }, {
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/svg/window-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/svg/window-expected.txt index e0ae16f..f43ad6f5 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/svg/window-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/linux/paint/invalidation/svg/window-expected.txt
@@ -518,7 +518,7 @@ }, { "object": "LayoutSVGContainer g id='windowTitlebarGroupnavWindow'", - "rect": [755, 93, 39, 13], + "rect": [756, 94, 37, 11], "reason": "appeared" }, { @@ -537,91 +537,6 @@ "reason": "appeared" }, { - "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 122, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [426, 387, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [348, 191, 14, 5], - "reason": "appeared" - }, - { "object": "LayoutSVGInlineText #text", "rect": [615, 153, 13, 81], "reason": "appeared" @@ -632,169 +547,34 @@ "reason": "appeared" }, { - "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [208, 204, 13, 6], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [544, 152, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [362, 353, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonbigWindow'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='closeButton'", + "object": "LayoutSVGPath line", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" }, { "object": "LayoutSVGRect rect", @@ -802,13 +582,33 @@ "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], "reason": "appeared" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [615, 123, 12, 3], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [349, 192, 12, 3], "reason": "appeared" }, { @@ -862,11 +662,41 @@ "reason": "appeared" }, { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [441, 379, 11, 11], "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnestedWindow'", "rect": [363, 184, 11, 11], "reason": "appeared" @@ -887,6 +717,36 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonsmallWindow'", "rect": [441, 380, 11, 10], "reason": "appeared" @@ -907,6 +767,86 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [545, 153, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [363, 354, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [428, 388, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [209, 206, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonbigWindow'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnavWindow'", "rect": [616, 100, 10, 10], "reason": "full" @@ -917,26 +857,76 @@ "reason": "full" }, { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 84, 10, 10], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [455, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [428, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [236, 198, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtoncolourPickerWindow'", "rect": [223, 198, 10, 10], "reason": "appeared" @@ -957,11 +947,21 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 116, 10, 9], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [572, 146, 10, 9], "reason": "appeared"
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/resize-iframe-text-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/resize-iframe-text-expected.txt new file mode 100644 index 0000000..338f2b9 --- /dev/null +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/resize-iframe-text-expected.txt
@@ -0,0 +1,89 @@ +{ + "layers": [ + { + "name": "LayoutView #document", + "bounds": [500, 400], + "backgroundColor": "#FFFFFF", + "paintInvalidations": [ + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "incremental" + } + ] + }, + { + "name": "Scrolling Layer", + "bounds": [500, 400], + "drawsContent": false + }, + { + "name": "Scrolling Contents Layer", + "bounds": [500, 400], + "contentsOpaque": true, + "backgroundColor": "#FFFFFF", + "paintInvalidations": [ + { + "object": "LayoutIFrame (positioned) IFRAME", + "rect": [0, 200, 500, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "background on scrolling contents layer" + }, + { + "object": "LayoutBlockFlow H3", + "rect": [8, 300, 400, 22], + "reason": "appeared" + }, + { + "object": "LayoutView #document", + "rect": [485, 0, 15, 200], + "reason": "scroll control" + } + ] + } + ], + "objectPaintInvalidations": [ + { + "object": "Scrolling Contents Layer", + "reason": "background on scrolling contents layer" + }, + { + "object": "LayoutView #document", + "reason": "incremental" + }, + { + "object": "LayoutIFrame (positioned) IFRAME", + "reason": "incremental" + }, + { + "object": "VerticalScrollbar", + "reason": "scroll control" + }, + { + "object": "LayoutView #document", + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow H3", + "reason": "appeared" + }, + { + "object": "RootInlineBox", + "reason": "appeared" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt index 5f80b67..0e0fb86 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt
@@ -7,6 +7,11 @@ "paintInvalidations": [ { "object": "LayoutView #document", + "rect": [0, 0, 800, 600], + "reason": "style change" + }, + { + "object": "LayoutView #document", "rect": [0, 585, 785, 15], "reason": "scroll control" }, @@ -74,6 +79,10 @@ "objectPaintInvalidations": [ { "object": "LayoutView #document", + "reason": "style change" + }, + { + "object": "LayoutView #document", "reason": "geometry" }, {
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/svg/window-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/svg/window-expected.txt index bfccfe72..af0c4d6 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/svg/window-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/mac/paint/invalidation/svg/window-expected.txt
@@ -518,7 +518,7 @@ }, { "object": "LayoutSVGContainer g id='windowTitlebarGroupnavWindow'", - "rect": [755, 93, 39, 13], + "rect": [756, 94, 37, 11], "reason": "appeared" }, { @@ -538,253 +538,33 @@ }, { "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 122, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [426, 387, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [348, 191, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [208, 204, 13, 6], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [544, 152, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [362, 353, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonbigWindow'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='closeButton'", + "object": "LayoutSVGPath line", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" }, { "object": "LayoutSVGRect rect", @@ -792,13 +572,33 @@ "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], "reason": "appeared" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [615, 123, 12, 3], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [349, 192, 12, 3], "reason": "appeared" }, { @@ -862,11 +662,41 @@ "reason": "appeared" }, { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [441, 379, 11, 11], "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnestedWindow'", "rect": [363, 184, 11, 11], "reason": "appeared" @@ -887,6 +717,36 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonsmallWindow'", "rect": [441, 380, 11, 10], "reason": "appeared" @@ -907,6 +767,86 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [545, 153, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [363, 354, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [428, 388, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [209, 206, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonbigWindow'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnavWindow'", "rect": [616, 100, 10, 10], "reason": "full" @@ -917,26 +857,76 @@ "reason": "full" }, { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 84, 10, 10], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [455, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [428, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [236, 198, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtoncolourPickerWindow'", "rect": [223, 198, 10, 10], "reason": "appeared" @@ -957,11 +947,21 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 116, 10, 9], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [572, 146, 10, 9], "reason": "appeared"
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/resize-iframe-text-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/resize-iframe-text-expected.txt new file mode 100644 index 0000000..d0603a7 --- /dev/null +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/resize-iframe-text-expected.txt
@@ -0,0 +1,89 @@ +{ + "layers": [ + { + "name": "LayoutView #document", + "bounds": [500, 400], + "backgroundColor": "#FFFFFF", + "paintInvalidations": [ + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "incremental" + } + ] + }, + { + "name": "Scrolling Layer", + "bounds": [500, 400], + "drawsContent": false + }, + { + "name": "Scrolling Contents Layer", + "bounds": [500, 400], + "contentsOpaque": true, + "backgroundColor": "#FFFFFF", + "paintInvalidations": [ + { + "object": "LayoutIFrame (positioned) IFRAME", + "rect": [0, 200, 500, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [0, 200, 500, 200], + "reason": "background on scrolling contents layer" + }, + { + "object": "LayoutBlockFlow H3", + "rect": [8, 300, 400, 23], + "reason": "appeared" + }, + { + "object": "LayoutView #document", + "rect": [485, 0, 15, 200], + "reason": "scroll control" + } + ] + } + ], + "objectPaintInvalidations": [ + { + "object": "Scrolling Contents Layer", + "reason": "background on scrolling contents layer" + }, + { + "object": "LayoutView #document", + "reason": "incremental" + }, + { + "object": "LayoutIFrame (positioned) IFRAME", + "reason": "incremental" + }, + { + "object": "VerticalScrollbar", + "reason": "scroll control" + }, + { + "object": "LayoutView #document", + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "reason": "geometry" + }, + { + "object": "LayoutBlockFlow H3", + "reason": "appeared" + }, + { + "object": "RootInlineBox", + "reason": "appeared" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt index 884e17e8..3d0f5d1 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/scroll/overflow-scroll-body-appear-expected.txt
@@ -7,6 +7,11 @@ "paintInvalidations": [ { "object": "LayoutView #document", + "rect": [0, 0, 800, 600], + "reason": "style change" + }, + { + "object": "LayoutView #document", "rect": [0, 585, 785, 15], "reason": "scroll control" }, @@ -74,6 +79,10 @@ "objectPaintInvalidations": [ { "object": "LayoutView #document", + "reason": "style change" + }, + { + "object": "LayoutView #document", "reason": "geometry" }, {
diff --git a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/svg/window-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/svg/window-expected.txt index 0552b50f..f20edaa 100644 --- a/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/svg/window-expected.txt +++ b/third_party/WebKit/LayoutTests/flag-specific/root-layer-scrolls/platform/win/paint/invalidation/svg/window-expected.txt
@@ -518,7 +518,7 @@ }, { "object": "LayoutSVGContainer g id='windowTitlebarGroupnavWindow'", - "rect": [755, 93, 39, 13], + "rect": [756, 94, 37, 11], "reason": "appeared" }, { @@ -537,91 +537,6 @@ "reason": "appeared" }, { - "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 82, 14, 14], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [453, 378, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [234, 196, 14, 14], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [781, 93, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [570, 144, 14, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [754, 101, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [614, 122, 14, 5], - "reason": "geometry" - }, - { - "object": "LayoutSVGPath line", - "rect": [426, 387, 14, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [348, 191, 14, 5], - "reason": "appeared" - }, - { "object": "LayoutSVGInlineText #text", "rect": [615, 153, 13, 81], "reason": "appeared" @@ -632,169 +547,34 @@ "reason": "appeared" }, { - "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [453, 378, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [375, 183, 13, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [362, 346, 13, 12], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [755, 95, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [544, 146, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [427, 380, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [208, 198, 13, 11], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [208, 204, 13, 6], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [544, 152, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGPath line", - "rect": [362, 353, 13, 5], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "full" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [782, 93, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonbigWindow'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [571, 144, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGViewportContainer svg id='closeButton'", - "rect": [235, 196, 12, 13], - "reason": "appeared" - }, - { - "object": "LayoutSVGContainer use id='closeButtonnavWindow'", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='closeButton'", + "object": "LayoutSVGPath line", "rect": [615, 83, 12, 12], - "reason": "full" + "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [615, 116, 12, 11], - "reason": "full" + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [782, 94, 12, 11], + "reason": "appeared" }, { "object": "LayoutSVGRect rect", @@ -802,13 +582,33 @@ "reason": "geometry" }, { - "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], "reason": "appeared" }, { - "object": "LayoutSVGViewportContainer svg id='minimizeButton'", - "rect": [349, 185, 12, 11], + "object": "LayoutSVGPath line", + "rect": [571, 145, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [235, 197, 12, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [615, 123, 12, 3], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [349, 192, 12, 3], "reason": "appeared" }, { @@ -862,11 +662,41 @@ "reason": "appeared" }, { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [454, 379, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [441, 379, 11, 11], "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='closeButtonnestedWindow'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [376, 184, 11, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnestedWindow'", "rect": [363, 184, 11, 11], "reason": "appeared" @@ -887,6 +717,36 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [756, 95, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonbigWindow'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [545, 146, 11, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonsmallWindow'", "rect": [441, 380, 11, 10], "reason": "appeared" @@ -907,6 +767,86 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonstatusWindow'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [363, 346, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtoncolourPickerWindow'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [209, 198, 11, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [545, 153, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [363, 354, 11, 3], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "geometry" + }, + { + "object": "LayoutSVGPath line", + "rect": [756, 103, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [428, 388, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGPath line", + "rect": [209, 206, 11, 2], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "full" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [783, 94, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtonbigWindow'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [572, 145, 10, 11], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtonnavWindow'", "rect": [616, 100, 10, 10], "reason": "full" @@ -917,26 +857,76 @@ "reason": "full" }, { + "object": "LayoutSVGContainer use id='closeButtonnavWindow'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 84, 10, 10], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [616, 84, 10, 10], + "reason": "full" + }, + { + "object": "LayoutSVGContainer use id='closeButtonsmallWindow'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [455, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [455, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonsmallWindow'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [428, 380, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [428, 380, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='minimizeButtonnestedWindow'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [350, 185, 10, 10], + "reason": "appeared" + }, + { + "object": "LayoutSVGContainer use id='closeButtoncolourPickerWindow'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGRect rect", "rect": [236, 198, 10, 10], "reason": "appeared" }, { + "object": "LayoutSVGViewportContainer svg id='closeButton'", + "rect": [236, 198, 10, 10], + "reason": "appeared" + }, + { "object": "LayoutSVGContainer use id='maximizeButtoncolourPickerWindow'", "rect": [223, 198, 10, 10], "reason": "appeared" @@ -957,11 +947,21 @@ "reason": "appeared" }, { + "object": "LayoutSVGContainer use id='minimizeButtonnavWindow'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [616, 116, 10, 9], "reason": "geometry" }, { + "object": "LayoutSVGViewportContainer svg id='minimizeButton'", + "rect": [616, 116, 10, 9], + "reason": "full" + }, + { "object": "LayoutSVGRect rect", "rect": [572, 146, 10, 9], "reason": "appeared"
diff --git a/third_party/WebKit/Source/DEPS b/third_party/WebKit/Source/DEPS index 76ff6b1..a5a830a 100644 --- a/third_party/WebKit/Source/DEPS +++ b/third_party/WebKit/Source/DEPS
@@ -1,6 +1,7 @@ include_rules = [ "+base/debug", "+base/macros.h", + "+base/memory/weak_ptr.h", "+base/gtest_prod_util.h", "+build", "+services/service_manager/public/cpp/connector.h",
diff --git a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.cpp b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.cpp index bca3ac7..e0d6b7fa 100644 --- a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.cpp +++ b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.cpp
@@ -39,7 +39,6 @@ #include "core/frame/LocalFrame.h" #include "core/inspector/InspectorTraceEvents.h" #include "core/inspector/ThreadDebugger.h" -#include "core/loader/resource/ScriptResource.h" #include "core/probe/CoreProbes.h" #include "platform/Histogram.h" #include "platform/bindings/ScriptForbiddenScope.h" @@ -403,9 +402,8 @@ return CompileScript(script_state, V8String(isolate, source.Source()), source.Url(), source.SourceMapUrl(), source.StartPosition(), source.SourceLocationType(), - source.GetResource(), source.Streamer(), - cache_metadata_handler, access_control_status, - v8_cache_options, referrer_info); + source.Streamer(), cache_metadata_handler, + access_control_status, v8_cache_options, referrer_info); } v8::MaybeLocal<v8::Script> V8ScriptRunner::CompileScript( @@ -415,7 +413,6 @@ const String& source_map_url, const TextPosition& script_start_position, ScriptSourceLocationType source_location_type, - ScriptResource* resource, ScriptStreamer* streamer, CachedMetadataHandler* cache_handler, AccessControlStatus access_control_status, @@ -428,11 +425,6 @@ script_start_position.line_.ZeroBasedInt(), script_start_position.column_.ZeroBasedInt()); - DCHECK(!streamer || resource); - DCHECK(!resource || resource->CacheHandler() == cache_handler); - DCHECK(!resource || - source_location_type == ScriptSourceLocationType::kExternalFile); - // NOTE: For compatibility with WebCore, ScriptSourceCode's line starts at // 1, whereas v8 starts at 0. v8::Isolate* isolate = script_state->GetIsolate(); @@ -558,7 +550,7 @@ // - parser_state: always "not parser inserted" for internal scripts. if (!V8ScriptRunner::CompileScript( script_state, source, file_name, String(), script_start_position, - ScriptSourceLocationType::kInternal, nullptr, nullptr, nullptr, + ScriptSourceLocationType::kInternal, nullptr, nullptr, kSharableCrossOrigin, kV8CacheOptionsDefault, ReferrerScriptInfo()) .ToLocal(&script)) return v8::MaybeLocal<v8::Value>();
diff --git a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.h b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.h index 0abb6f3..99755a2 100644 --- a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.h +++ b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunner.h
@@ -50,7 +50,6 @@ class CachedMetadata; class CachedMetadataHandler; class ExecutionContext; -class ScriptResource; class ScriptSourceCode; class ScriptStreamer; @@ -65,23 +64,19 @@ // For the following methods, the caller sites have to hold // a HandleScope and a ContextScope. + // CachedMetadataHandler is set when metadata caching is supported. static v8::MaybeLocal<v8::Script> CompileScript(ScriptState*, const ScriptSourceCode&, CachedMetadataHandler*, AccessControlStatus, V8CacheOptions, const ReferrerScriptInfo&); - // CachedMetadataHandler is set when metadata caching is supported. For - // normal scripe resources, CachedMetadataHandler is from ScriptResource. - // For worker script, ScriptResource is null but CachedMetadataHandler may be - // set. When ScriptStreamer is set, ScriptResource must be set. static v8::MaybeLocal<v8::Script> CompileScript(ScriptState*, v8::Local<v8::String>, const String& file_name, const String& source_map_url, const TextPosition&, ScriptSourceLocationType, - ScriptResource*, ScriptStreamer*, CachedMetadataHandler*, AccessControlStatus,
diff --git a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunnerTest.cpp b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunnerTest.cpp index befa8b5..9c87c8f 100644 --- a/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunnerTest.cpp +++ b/third_party/WebKit/Source/bindings/core/v8/V8ScriptRunnerTest.cpp
@@ -62,8 +62,8 @@ return !V8ScriptRunner::CompileScript( script_state, V8String(script_state->GetIsolate(), Code()), Filename(), String(), WTF::TextPosition(), - ScriptSourceLocationType::kExternalFile, resource_.Get(), - nullptr, resource_.Get() ? resource_->CacheHandler() : nullptr, + ScriptSourceLocationType::kExternalFile, nullptr, + resource_.Get() ? resource_->CacheHandler() : nullptr, kNotSharableCrossOrigin, cache_options, ReferrerScriptInfo()) .IsEmpty(); }
diff --git a/third_party/WebKit/Source/core/inspector/BUILD.gn b/third_party/WebKit/Source/core/inspector/BUILD.gn index fe0f95c..60e2eca 100644 --- a/third_party/WebKit/Source/core/inspector/BUILD.gn +++ b/third_party/WebKit/Source/core/inspector/BUILD.gn
@@ -129,7 +129,7 @@ config_values = [ "imported.path=$_imported" ] inputs = [ - "browser_protocol.json", + "$blink_core_output_dir/inspector/browser_protocol.json", v8_inspector_js_protocol, "inspector_protocol_config.json", ] @@ -194,6 +194,7 @@ ] deps = [ + ":protocol_convert_to_json", ":protocol_version", ] } @@ -224,11 +225,30 @@ ] } +action("protocol_convert_to_json") { + script = "ConvertProtocolToJSON.py" + inputs = [ + "ConvertProtocolToJSON.py", + "browser_protocol.pdl", + ] + output_file = "$blink_core_output_dir/inspector/browser_protocol.json" + outputs = [ + output_file, + ] + args = [ + rebase_path("browser_protocol.pdl", root_build_dir), + rebase_path(output_file, root_build_dir), + ] +} + action("protocol_compatibility_check") { script = _inspector_protocol_dir + "/CheckProtocolCompatibility.py" + deps = [ + ":protocol_convert_to_json", + ] inputs = [ - "browser_protocol.json", + "$blink_core_output_dir/inspector/browser_protocol.json", "browser_protocol-1.3.json", v8_inspector_js_protocol, ] @@ -240,7 +260,8 @@ args = [ "--stamp", rebase_path(_stamp, root_build_dir), - rebase_path("browser_protocol.json", root_build_dir), + rebase_path("$blink_core_output_dir/inspector/browser_protocol.json", + root_build_dir), rebase_path(v8_inspector_js_protocol, root_build_dir), ] } @@ -248,11 +269,12 @@ action("protocol_version") { deps = [ ":protocol_compatibility_check", + ":protocol_convert_to_json", ] script = _inspector_protocol_dir + "/ConcatenateProtocols.py" inputs = [ - "browser_protocol.json", + "$blink_core_output_dir/inspector/browser_protocol.json", v8_inspector_js_protocol, ] output_file = "$blink_core_output_dir/inspector/protocol.json" @@ -261,7 +283,8 @@ ] args = [ - rebase_path("browser_protocol.json", root_build_dir), + rebase_path("$blink_core_output_dir/inspector/browser_protocol.json", + root_build_dir), rebase_path(v8_inspector_js_protocol, root_build_dir), rebase_path(output_file, root_build_dir), ]
diff --git a/third_party/WebKit/Source/core/inspector/ConvertProtocolToJSON.py b/third_party/WebKit/Source/core/inspector/ConvertProtocolToJSON.py index d5afe33..0b2fcfc 100644 --- a/third_party/WebKit/Source/core/inspector/ConvertProtocolToJSON.py +++ b/third_party/WebKit/Source/core/inspector/ConvertProtocolToJSON.py
@@ -161,18 +161,20 @@ def main(argv): if len(argv) < 2: - sys.stderr.write("Usage: %s stamp <protocol.pdl>\n" % sys.argv[0]) + sys.stderr.write("Usage: %s <protocol.pdl> <protocol.json>\n" % sys.argv[0]) return 1 global file_name - file_name = os.path.normpath(argv[1]) + file_name = os.path.normpath(argv[0]) input_file = open(file_name, "r") pdl_string = input_file.read() protocol = parse(pdl_string) - output_file = open(argv[1].replace('.pdl', '.json'), "w") + output_file = open(argv[0].replace('.pdl', '.json'), "w") json.dump(protocol, output_file, indent=4, separators=(',', ': ')) output_file.close() - with open(os.path.normpath(argv[0]), 'a') as _: - pass + + output_file = open(os.path.normpath(argv[1]), "w") + json.dump(protocol, output_file, indent=4, separators=(',', ': ')) + output_file.close() if __name__ == '__main__':
diff --git a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp index 862c501..2fe43cf7 100644 --- a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp +++ b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp
@@ -32,6 +32,7 @@ #include "core/loader/DocumentThreadableLoader.h" #include <memory> +#include "base/memory/weak_ptr.h" #include "core/dom/Document.h" #include "core/frame/FrameConsole.h" #include "core/frame/LocalFrame.h" @@ -59,7 +60,6 @@ #include "platform/weborigin/SecurityPolicy.h" #include "platform/wtf/Assertions.h" #include "platform/wtf/PtrUtil.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/Platform.h" #include "public/platform/TaskType.h" #include "public/platform/WebCORS.h" @@ -81,7 +81,7 @@ : factory_(this) { Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostTask( BLINK_FROM_HERE, - WTF::Bind(&EmptyDataReader::Notify, factory_.CreateWeakPtr(), + WTF::Bind(&EmptyDataReader::Notify, factory_.GetWeakPtr(), WTF::Unretained(client))); } @@ -99,7 +99,7 @@ void Notify(WebDataConsumerHandle::Client* client) { client->DidGetReadable(); } - WeakPtrFactory<EmptyDataReader> factory_; + base::WeakPtrFactory<EmptyDataReader> factory_; }; std::unique_ptr<Reader> ObtainReader(Client* client) override {
diff --git a/third_party/WebKit/Source/core/loader/ImageLoader.cpp b/third_party/WebKit/Source/core/loader/ImageLoader.cpp index 02111450..d46dfdd 100644 --- a/third_party/WebKit/Source/core/loader/ImageLoader.cpp +++ b/third_party/WebKit/Source/core/loader/ImageLoader.cpp
@@ -131,14 +131,14 @@ script_state_ = nullptr; } - WeakPtr<Task> CreateWeakPtr() { return weak_factory_.CreateWeakPtr(); } + base::WeakPtr<Task> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } private: WeakPersistent<ImageLoader> loader_; BypassMainWorldBehavior should_bypass_main_world_csp_; UpdateFromElementBehavior update_behavior_; scoped_refptr<ScriptState> script_state_; - WeakPtrFactory<Task> weak_factory_; + base::WeakPtrFactory<Task> weak_factory_; ReferrerPolicy referrer_policy_; KURL request_url_; }; @@ -342,7 +342,7 @@ ReferrerPolicy referrer_policy) { std::unique_ptr<Task> task = Task::Create(this, update_behavior, referrer_policy); - pending_task_ = task->CreateWeakPtr(); + pending_task_ = task->GetWeakPtr(); Microtask::EnqueueMicrotask( WTF::Bind(&Task::Run, WTF::Passed(std::move(task)))); delay_until_do_update_from_element_ =
diff --git a/third_party/WebKit/Source/core/loader/ImageLoader.h b/third_party/WebKit/Source/core/loader/ImageLoader.h index 6c26172..3e53ed1 100644 --- a/third_party/WebKit/Source/core/loader/ImageLoader.h +++ b/third_party/WebKit/Source/core/loader/ImageLoader.h
@@ -24,6 +24,7 @@ #define ImageLoader_h #include <memory> +#include "base/memory/weak_ptr.h" #include "bindings/core/v8/ScriptPromise.h" #include "bindings/core/v8/ScriptPromiseResolver.h" #include "core/CoreExport.h" @@ -32,7 +33,6 @@ #include "core/loader/resource/ImageResourceObserver.h" #include "platform/heap/Handle.h" #include "platform/wtf/HashSet.h" -#include "platform/wtf/WeakPtr.h" #include "platform/wtf/text/AtomicString.h" #include "public/platform/TaskType.h" @@ -184,7 +184,7 @@ Member<ImageResource> image_resource_for_image_document_; AtomicString failed_load_url_; - WeakPtr<Task> pending_task_; // owned by Microtask + base::WeakPtr<Task> pending_task_; // owned by Microtask std::unique_ptr<IncrementLoadEventDelayCount> delay_until_do_update_from_element_;
diff --git a/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp b/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp index 9cc47b0..ae8e0aac7 100644 --- a/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp +++ b/third_party/WebKit/Source/core/offscreencanvas/OffscreenCanvas.cpp
@@ -247,8 +247,8 @@ ImageBuffer* OffscreenCanvas::GetOrCreateImageBuffer() { if (!image_buffer_) { bool is_accelerated_2d_canvas_blacklisted = true; - WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper = - SharedGpuContext::ContextProviderWrapper(); + base::WeakPtr<WebGraphicsContext3DProviderWrapper> + context_provider_wrapper = SharedGpuContext::ContextProviderWrapper(); if (context_provider_wrapper) { const gpu::GpuFeatureInfo& gpu_feature_info = context_provider_wrapper->ContextProvider()->GetGpuFeatureInfo();
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h index 136fa0b..f73f652 100644 --- a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h +++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h
@@ -38,7 +38,6 @@ #include "core/workers/ThreadedObjectProxyBase.h" #include "core/workers/WorkerReportingProxy.h" #include "platform/heap/Handle.h" -#include "platform/wtf/WeakPtr.h" namespace v8_inspector { struct V8StackTraceId;
diff --git a/third_party/WebKit/Source/modules/accessibility/AXRelationCache.h b/third_party/WebKit/Source/modules/accessibility/AXRelationCache.h index 5a131d4..599692c7 100644 --- a/third_party/WebKit/Source/modules/accessibility/AXRelationCache.h +++ b/third_party/WebKit/Source/modules/accessibility/AXRelationCache.h
@@ -33,7 +33,6 @@ #include "modules/accessibility/AXObjectCacheImpl.h" #include "platform/wtf/HashMap.h" #include "platform/wtf/HashSet.h" -#include "platform/wtf/WeakPtr.h" namespace blink {
diff --git a/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.cpp b/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.cpp index 059423b..e490a47 100644 --- a/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.cpp +++ b/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.cpp
@@ -22,7 +22,7 @@ void OnRequestCanvasDrawListener::SendNewFrame( sk_sp<SkImage> image, - WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) { + base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) { frame_capture_requested_ = false; CanvasDrawListener::SendNewFrame(image, context_provider); }
diff --git a/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.h b/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.h index 1fda609..dec7668 100644 --- a/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.h +++ b/third_party/WebKit/Source/modules/mediacapturefromelement/OnRequestCanvasDrawListener.h
@@ -6,9 +6,9 @@ #define OnRequestCanvasDrawListener_h #include <memory> +#include "base/memory/weak_ptr.h" #include "core/html/canvas/CanvasDrawListener.h" #include "platform/heap/Handle.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebCanvasCaptureHandler.h" #include "third_party/skia/include/core/SkRefCnt.h" @@ -23,8 +23,9 @@ ~OnRequestCanvasDrawListener() override; static OnRequestCanvasDrawListener* Create( std::unique_ptr<WebCanvasCaptureHandler>); - void SendNewFrame(sk_sp<SkImage>, - WeakPtr<WebGraphicsContext3DProviderWrapper>) override; + void SendNewFrame( + sk_sp<SkImage>, + base::WeakPtr<WebGraphicsContext3DProviderWrapper>) override; void Trace(blink::Visitor* visitor) override {}
diff --git a/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.cpp b/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.cpp index ea50e4d1..cc41ac9a 100644 --- a/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.cpp +++ b/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.cpp
@@ -37,7 +37,7 @@ void TimedCanvasDrawListener::SendNewFrame( sk_sp<SkImage> image, - WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) { + base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) { frame_capture_requested_ = false; CanvasDrawListener::SendNewFrame(image, context_provider); }
diff --git a/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.h b/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.h index c1a43be11..0795ac1 100644 --- a/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.h +++ b/third_party/WebKit/Source/modules/mediacapturefromelement/TimedCanvasDrawListener.h
@@ -6,10 +6,10 @@ #define TimedCanvasDrawListener_h #include <memory> +#include "base/memory/weak_ptr.h" #include "core/html/canvas/CanvasDrawListener.h" #include "platform/Timer.h" #include "platform/heap/Handle.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebCanvasCaptureHandler.h" #include "third_party/skia/include/core/SkRefCnt.h" @@ -28,8 +28,9 @@ std::unique_ptr<WebCanvasCaptureHandler>, double frame_rate, ExecutionContext*); - void SendNewFrame(sk_sp<SkImage>, - WeakPtr<WebGraphicsContext3DProviderWrapper>) override; + void SendNewFrame( + sk_sp<SkImage>, + base::WeakPtr<WebGraphicsContext3DProviderWrapper>) override; void Trace(blink::Visitor* visitor) override {}
diff --git a/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadManager.idl b/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadManager.idl index 7c830b6..6176378 100644 --- a/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadManager.idl +++ b/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadManager.idl
@@ -2,17 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(falken): Revise link when this lands in the spec: -// https://github.com/w3c/ServiceWorker/issues/920 +// https://w3c.github.io/ServiceWorker/#navigation-preload-manager [ + SecureContext, Exposed=(Window,Worker) ] interface NavigationPreloadManager { - // TODO(mgiuca): Put SecureContext on the interface, not individual methods. - // Currently prevented due to clash with OriginTrialEnabled. This can be - // resolved either when OriginTrialEnabled is removed, or - // https://crbug.com/695123 is fixed. - [SecureContext, CallWith=ScriptState] Promise<void> enable(); - [SecureContext, CallWith=ScriptState] Promise<void> disable(); - [SecureContext, CallWith=ScriptState] Promise<void> setHeaderValue(ByteString value); - [SecureContext, CallWith=ScriptState] Promise<NavigationPreloadState> getState(); + [CallWith=ScriptState] Promise<void> enable(); + [CallWith=ScriptState] Promise<void> disable(); + [CallWith=ScriptState] Promise<void> setHeaderValue(ByteString value); + [CallWith=ScriptState] Promise<NavigationPreloadState> getState(); };
diff --git a/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadState.idl b/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadState.idl index 2a06381..1c8c4a1 100644 --- a/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadState.idl +++ b/third_party/WebKit/Source/modules/serviceworkers/NavigationPreloadState.idl
@@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(falken): Revise link when this lands in the spec: -// https://github.com/w3c/ServiceWorker/issues/920 +// https://w3c.github.io/ServiceWorker/#navigation-preload-manager dictionary NavigationPreloadState { boolean enabled = false; ByteString headerValue;
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp index 1802d96..12b7d0ad 100644 --- a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp +++ b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp
@@ -793,7 +793,7 @@ scoped_refptr<StaticBitmapImage> WebGLRenderingContextBase::MakeImageSnapshot( SkImageInfo& image_info) { GetDrawingBuffer()->ResolveAndBindForReadAndDraw(); - WeakPtr<WebGraphicsContext3DProviderWrapper> shared_context_wrapper = + base::WeakPtr<WebGraphicsContext3DProviderWrapper> shared_context_wrapper = SharedGpuContext::ContextProviderWrapper(); if (!shared_context_wrapper) return nullptr;
diff --git a/third_party/WebKit/Source/platform/CrossThreadCopier.h b/third_party/WebKit/Source/platform/CrossThreadCopier.h index c7fff9fc..8cc982f3 100644 --- a/third_party/WebKit/Source/platform/CrossThreadCopier.h +++ b/third_party/WebKit/Source/platform/CrossThreadCopier.h
@@ -33,6 +33,7 @@ #include <memory> #include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" #include "mojo/public/cpp/bindings/interface_ptr_info.h" #include "mojo/public/cpp/bindings/interface_request.h" #include "platform/PlatformExport.h" @@ -40,7 +41,6 @@ #include "platform/wtf/Forward.h" #include "platform/wtf/Functional.h" // FunctionThreadAffinity #include "platform/wtf/TypeTraits.h" -#include "platform/wtf/WeakPtr.h" #include "third_party/WebKit/common/message_port/message_port_channel.h" namespace base { @@ -192,8 +192,8 @@ }; template <typename T> -struct CrossThreadCopier<WeakPtr<T>> - : public CrossThreadCopierPassThrough<WeakPtr<T>> { +struct CrossThreadCopier<base::WeakPtr<T>> + : public CrossThreadCopierPassThrough<base::WeakPtr<T>> { STATIC_ONLY(CrossThreadCopier); };
diff --git a/third_party/WebKit/Source/platform/Timer.cpp b/third_party/WebKit/Source/platform/Timer.cpp index f922674..1a74be8 100644 --- a/third_party/WebKit/Source/platform/Timer.cpp +++ b/third_party/WebKit/Source/platform/Timer.cpp
@@ -72,7 +72,7 @@ repeat_interval_ = TimeDelta(); next_fire_time_ = TimeTicks(); - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); } TimeDelta TimerBase::NextFireIntervalDelta() const { @@ -94,7 +94,7 @@ } bool active = IsActive(); - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); web_task_runner_ = std::move(task_runner); if (!active) @@ -127,11 +127,11 @@ next_fire_time_ = new_time; // Cancel any previously posted task. - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); TimerTaskRunner()->PostDelayedTask( location_, - WTF::Bind(&TimerBase::RunInternal, weak_ptr_factory_.CreateWeakPtr()), + WTF::Bind(&TimerBase::RunInternal, weak_ptr_factory_.GetWeakPtr()), delay); } } @@ -141,7 +141,7 @@ if (!CanFire()) return; - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); TRACE_EVENT0("blink", "TimerBase::run"); #if DCHECK_IS_ON()
diff --git a/third_party/WebKit/Source/platform/Timer.h b/third_party/WebKit/Source/platform/Timer.h index 2dfb7c8..85204772 100644 --- a/third_party/WebKit/Source/platform/Timer.h +++ b/third_party/WebKit/Source/platform/Timer.h
@@ -26,6 +26,7 @@ #ifndef Timer_h #define Timer_h +#include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "platform/PlatformExport.h" #include "platform/WebTaskRunner.h" @@ -35,7 +36,6 @@ #include "platform/wtf/Noncopyable.h" #include "platform/wtf/Threading.h" #include "platform/wtf/Time.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebTraceLocation.h" namespace blink { @@ -125,7 +125,7 @@ #if DCHECK_IS_ON() ThreadIdentifier thread_; #endif - WTF::WeakPtrFactory<TimerBase> weak_ptr_factory_; + base::WeakPtrFactory<TimerBase> weak_ptr_factory_; friend class ThreadTimers; friend class TimerHeapLessThanFunction;
diff --git a/third_party/WebKit/Source/platform/WebTaskRunner.cpp b/third_party/WebKit/Source/platform/WebTaskRunner.cpp index 0eb1065..5e2b383 100644 --- a/third_party/WebKit/Source/platform/WebTaskRunner.cpp +++ b/third_party/WebKit/Source/platform/WebTaskRunner.cpp
@@ -15,11 +15,11 @@ template <> struct CallbackCancellationTraits< RunnerMethodType, - std::tuple<WTF::WeakPtr<blink::TaskHandle::Runner>, blink::TaskHandle>> { + std::tuple<base::WeakPtr<blink::TaskHandle::Runner>, blink::TaskHandle>> { static constexpr bool is_cancellable = true; static bool IsCancelled(RunnerMethodType, - const WTF::WeakPtr<blink::TaskHandle::Runner>&, + const base::WeakPtr<blink::TaskHandle::Runner>&, const blink::TaskHandle& handle) { return !handle.IsActive(); } @@ -42,13 +42,13 @@ explicit Runner(WTF::Closure task) : task_(std::move(task)), weak_ptr_factory_(this) {} - WTF::WeakPtr<Runner> AsWeakPtr() { return weak_ptr_factory_.CreateWeakPtr(); } + base::WeakPtr<Runner> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } bool IsActive() const { return task_ && !task_.IsCancelled(); } void Cancel() { WTF::Closure task = std::move(task_); - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); } ~Runner() { Cancel(); } @@ -71,13 +71,13 @@ // |m_task| when the wrapped WTF::Closure is deleted. void Run(const TaskHandle&) { WTF::Closure task = std::move(task_); - weak_ptr_factory_.RevokeAll(); + weak_ptr_factory_.InvalidateWeakPtrs(); std::move(task).Run(); } private: WTF::Closure task_; - WTF::WeakPtrFactory<Runner> weak_ptr_factory_; + base::WeakPtrFactory<Runner> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(Runner); };
diff --git a/third_party/WebKit/Source/platform/WebTaskRunner.h b/third_party/WebKit/Source/platform/WebTaskRunner.h index e2c2d59..08886f88 100644 --- a/third_party/WebKit/Source/platform/WebTaskRunner.h +++ b/third_party/WebKit/Source/platform/WebTaskRunner.h
@@ -12,7 +12,6 @@ #include "platform/wtf/Functional.h" #include "platform/wtf/RefCounted.h" #include "platform/wtf/Time.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebCommon.h" #include "public/platform/WebTraceLocation.h"
diff --git a/third_party/WebKit/Source/platform/WebTaskRunnerTest.cpp b/third_party/WebKit/Source/platform/WebTaskRunnerTest.cpp index ba09c95..3591cc3 100644 --- a/third_party/WebKit/Source/platform/WebTaskRunnerTest.cpp +++ b/third_party/WebKit/Source/platform/WebTaskRunnerTest.cpp
@@ -22,17 +22,17 @@ public: CancellationTestHelper() : weak_ptr_factory_(this) {} - WeakPtr<CancellationTestHelper> CreateWeakPtr() { - return weak_ptr_factory_.CreateWeakPtr(); + base::WeakPtr<CancellationTestHelper> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); } - void RevokeWeakPtrs() { weak_ptr_factory_.RevokeAll(); } + void RevokeWeakPtrs() { weak_ptr_factory_.InvalidateWeakPtrs(); } void IncrementCounter() { ++counter_; } int Counter() const { return counter_; } private: int counter_ = 0; - WeakPtrFactory<CancellationTestHelper> weak_ptr_factory_; + base::WeakPtrFactory<CancellationTestHelper> weak_ptr_factory_; }; } // namespace @@ -140,7 +140,7 @@ CancellationTestHelper helper; handle = task_runner->PostCancellableTask( BLINK_FROM_HERE, WTF::Bind(&CancellationTestHelper::IncrementCounter, - helper.CreateWeakPtr())); + helper.GetWeakPtr())); EXPECT_EQ(0, helper.Counter()); // The cancellation of the posted task should be propagated to TaskHandle.
diff --git a/third_party/WebKit/Source/platform/audio/VectorMath.cpp b/third_party/WebKit/Source/platform/audio/VectorMath.cpp index 2f1c36f9..0a7cf24 100644 --- a/third_party/WebKit/Source/platform/audio/VectorMath.cpp +++ b/third_party/WebKit/Source/platform/audio/VectorMath.cpp
@@ -683,8 +683,38 @@ float low_threshold = *low_threshold_p; float high_threshold = *high_threshold_p; -// FIXME: Optimize for SSE2. -#if WTF_CPU_ARM_NEON +#if DCHECK_IS_ON() + // Do the same DCHECKs that |clampTo| would do so that optimization paths do + // not have to do them. + for (size_t i = 0u; i < frames_to_process; ++i) + DCHECK(!std::isnan(source_p[i])); + // This also ensures that thresholds are not NaNs. + DCHECK_LE(low_threshold, high_threshold); +#endif + +#if defined(ARCH_CPU_X86_FAMILY) + if (source_stride == 1 && dest_stride == 1) { + size_t i = 0u; + + // If the source_p address is not 16-byte aligned, the first several + // frames (at most three) should be processed separately. + for (; !SSE::IsAligned(source_p + i) && i < frames_to_process; ++i) + dest_p[i] = clampTo(source_p[i], low_threshold, high_threshold); + + // Now the source_p+i address is 16-byte aligned. Start to apply SSE. + size_t sse_frames_to_process = + (frames_to_process - i) & SSE::kFramesToProcessMask; + if (sse_frames_to_process > 0u) { + SSE::Vclip(source_p + i, &low_threshold, &high_threshold, dest_p + i, + sse_frames_to_process); + i += sse_frames_to_process; + } + + source_p += i; + dest_p += i; + n -= i; + } +#elif WTF_CPU_ARM_NEON if ((source_stride == 1) && (dest_stride == 1)) { int tail_frames = n % 4; const float* end_p = dest_p + n - tail_frames;
diff --git a/third_party/WebKit/Source/platform/audio/cpu/x86/VectorMathImpl.cpp b/third_party/WebKit/Source/platform/audio/cpu/x86/VectorMathImpl.cpp index 4953f6b8..c802ab4e 100644 --- a/third_party/WebKit/Source/platform/audio/cpu/x86/VectorMathImpl.cpp +++ b/third_party/WebKit/Source/platform/audio/cpu/x86/VectorMathImpl.cpp
@@ -9,6 +9,7 @@ #include "platform/wtf/Assertions.h" #include <algorithm> +#include <cmath> #include <xmmintrin.h> @@ -72,6 +73,40 @@ #undef ADD_ALL } +// dest[k] = clip(source[k], low_threshold, high_threshold) +// = max(low_threshold, min(high_threshold, source[k])) +void Vclip(const float* source_p, + const float* low_threshold_p, + const float* high_threshold_p, + float* dest_p, + size_t frames_to_process) { + const float* const source_end_p = source_p + frames_to_process; + + DCHECK(IsAligned(source_p)); + DCHECK_EQ(0u, frames_to_process % kPackedFloatsPerRegister); + + MType m_low_threshold = MM_PS(set1)(*low_threshold_p); + MType m_high_threshold = MM_PS(set1)(*high_threshold_p); + +#define CLIP_ALL(storeDest) \ + while (source_p < source_end_p) { \ + MType m_source = MM_PS(load)(source_p); \ + MType m_dest = \ + MM_PS(max)(m_low_threshold, MM_PS(min)(m_high_threshold, m_source)); \ + MM_PS(storeDest)(dest_p, m_dest); \ + source_p += kPackedFloatsPerRegister; \ + dest_p += kPackedFloatsPerRegister; \ + } + + if (IsAligned(dest_p)) { + CLIP_ALL(store); + } else { + CLIP_ALL(storeu); + } + +#undef CLIP_ALL +} + // max = max(abs(source[k])) for all k void Vmaxmgv(const float* source_p, float* max_p, size_t frames_to_process) { constexpr uint32_t kMask = 0x7FFFFFFFu;
diff --git a/third_party/WebKit/Source/platform/fonts/FontFallbackList.h b/third_party/WebKit/Source/platform/fonts/FontFallbackList.h index 923f366..49fad83 100644 --- a/third_party/WebKit/Source/platform/fonts/FontFallbackList.h +++ b/third_party/WebKit/Source/platform/fonts/FontFallbackList.h
@@ -21,6 +21,7 @@ #ifndef FontFallbackList_h #define FontFallbackList_h +#include "base/memory/weak_ptr.h" #include "platform/fonts/FallbackListCompositeKey.h" #include "platform/fonts/FontCache.h" #include "platform/fonts/FontSelector.h" @@ -29,7 +30,6 @@ #include "platform/wtf/Allocator.h" #include "platform/wtf/Forward.h" #include "platform/wtf/RefCounted.h" -#include "platform/wtf/WeakPtr.h" namespace blink { @@ -99,7 +99,7 @@ mutable int family_index_; unsigned short generation_; mutable bool has_loading_fallback_ : 1; - mutable WeakPtr<ShapeCache> shape_cache_; + mutable base::WeakPtr<ShapeCache> shape_cache_; }; } // namespace blink
diff --git a/third_party/WebKit/Source/platform/fonts/shaping/ShapeCache.h b/third_party/WebKit/Source/platform/fonts/shaping/ShapeCache.h index 0e1ee81..e1d8f9b 100644 --- a/third_party/WebKit/Source/platform/fonts/shaping/ShapeCache.h +++ b/third_party/WebKit/Source/platform/fonts/shaping/ShapeCache.h
@@ -27,13 +27,13 @@ #ifndef ShapeCache_h #define ShapeCache_h +#include "base/memory/weak_ptr.h" #include "platform/fonts/shaping/ShapeResult.h" #include "platform/text/TextRun.h" #include "platform/wtf/Forward.h" #include "platform/wtf/HashFunctions.h" #include "platform/wtf/HashSet.h" #include "platform/wtf/HashTableDeletedValueType.h" -#include "platform/wtf/WeakPtr.h" namespace blink { @@ -149,7 +149,7 @@ return self_byte_size; } - WeakPtr<ShapeCache> GetWeakPtr() { return weak_factory_.CreateWeakPtr(); } + base::WeakPtr<ShapeCache> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } private: ShapeCacheEntry* AddSlowCase(const TextRun& run, ShapeCacheEntry entry) { @@ -236,7 +236,7 @@ SingleCharMap single_char_map_; SmallStringMap short_string_map_; - WeakPtrFactory<ShapeCache> weak_factory_; + base::WeakPtrFactory<ShapeCache> weak_factory_; unsigned version_; };
diff --git a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h index 65dfd5d..8d59b043 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h +++ b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h
@@ -24,12 +24,12 @@ #define RawResource_h #include <memory> + #include "platform/PlatformExport.h" #include "platform/loader/fetch/BufferingDataPipeWriter.h" #include "platform/loader/fetch/Resource.h" #include "platform/loader/fetch/ResourceClient.h" #include "platform/loader/fetch/ResourceLoaderOptions.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebDataConsumerHandle.h" namespace blink {
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue.h b/third_party/WebKit/Source/platform/scheduler/base/task_queue.h index 0088630..9af508e 100644 --- a/third_party/WebKit/Source/platform/scheduler/base/task_queue.h +++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue.h
@@ -6,6 +6,7 @@ #define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_BASE_TASK_QUEUE_H_ #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/optional.h" #include "base/single_thread_task_runner.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/base/thread_controller_impl.h b/third_party/WebKit/Source/platform/scheduler/base/thread_controller_impl.h index 6010f81..31dd466 100644 --- a/third_party/WebKit/Source/platform/scheduler/base/thread_controller_impl.h +++ b/third_party/WebKit/Source/platform/scheduler/base/thread_controller_impl.h
@@ -10,6 +10,7 @@ #include "base/cancelable_callback.h" #include "base/debug/task_annotator.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/run_loop.h" #include "base/sequence_checker.h" #include "base/single_thread_task_runner.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/child/idle_canceled_delayed_task_sweeper.h b/third_party/WebKit/Source/platform/scheduler/child/idle_canceled_delayed_task_sweeper.h index b43620f..88a42fb7 100644 --- a/third_party/WebKit/Source/platform/scheduler/child/idle_canceled_delayed_task_sweeper.h +++ b/third_party/WebKit/Source/platform/scheduler/child/idle_canceled_delayed_task_sweeper.h
@@ -6,6 +6,7 @@ #define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_CHILD_IDLE_CANCELED_DELAYED_TASK_SWEEPER_H_ #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "platform/scheduler/child/scheduler_helper.h" #include "public/platform/scheduler/child/single_thread_idle_task_runner.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/child/idle_helper.h b/third_party/WebKit/Source/platform/scheduler/child/idle_helper.h index b498c22..ff7905a 100644 --- a/third_party/WebKit/Source/platform/scheduler/child/idle_helper.h +++ b/third_party/WebKit/Source/platform/scheduler/child/idle_helper.h
@@ -6,6 +6,7 @@ #define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_CHILD_IDLE_HELPER_H_ #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "platform/PlatformExport.h" #include "platform/scheduler/base/task_queue_selector.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/deadline_task_runner.h b/third_party/WebKit/Source/platform/scheduler/renderer/deadline_task_runner.h index 0d3494e6..9041e00 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/deadline_task_runner.h +++ b/third_party/WebKit/Source/platform/scheduler/renderer/deadline_task_runner.h
@@ -7,7 +7,6 @@ #include "base/callback.h" #include "base/macros.h" -#include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/time/time.h" #include "platform/PlatformExport.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h index 2dec8ce..8da453a 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h +++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
@@ -8,6 +8,7 @@ #include "base/atomicops.h" #include "base/gtest_prod_util.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/metrics/single_sample_metrics.h" #include "base/single_thread_task_runner.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/task_queue_throttler.h b/third_party/WebKit/Source/platform/scheduler/renderer/task_queue_throttler.h index ff9cb57..9d7cc850 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/task_queue_throttler.h +++ b/third_party/WebKit/Source/platform/scheduler/renderer/task_queue_throttler.h
@@ -10,6 +10,7 @@ #include "base/logging.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/optional.h" #include "base/threading/thread_checker.h" #include "platform/PlatformExport.h"
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/web_view_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/web_view_scheduler_impl.h index ae6e4514..41cb292e 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/web_view_scheduler_impl.h +++ b/third_party/WebKit/Source/platform/scheduler/renderer/web_view_scheduler_impl.h
@@ -10,6 +10,7 @@ #include <string> #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "platform/PlatformExport.h" #include "platform/scheduler/base/task_queue.h" @@ -83,8 +84,8 @@ void AsValueInto(base::trace_event::TracedValue* state) const; - WTF::WeakPtr<WebViewSchedulerImpl> CreateWeakPtr() { - return weak_factory_.CreateWeakPtr(); + base::WeakPtr<WebViewSchedulerImpl> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); } private: @@ -116,7 +117,7 @@ bool nested_runloop_; CPUTimeBudgetPool* background_time_budget_pool_; // Not owned. WebViewScheduler::WebViewSchedulerDelegate* delegate_; // Not owned. - WTF::WeakPtrFactory<WebViewSchedulerImpl> weak_factory_; + base::WeakPtrFactory<WebViewSchedulerImpl> weak_factory_; DISALLOW_COPY_AND_ASSIGN(WebViewSchedulerImpl); };
diff --git a/third_party/WebKit/Source/platform/scheduler/test/test_task_queue.h b/third_party/WebKit/Source/platform/scheduler/test/test_task_queue.h index 472192c..67b10e8 100644 --- a/third_party/WebKit/Source/platform/scheduler/test/test_task_queue.h +++ b/third_party/WebKit/Source/platform/scheduler/test/test_task_queue.h
@@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_TEST_TEST_TASK_QUEUE_H_ #define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_TEST_TEST_TASK_QUEUE_H_ +#include "base/memory/weak_ptr.h" #include "platform/scheduler/base/task_queue.h" namespace blink {
diff --git a/third_party/WebKit/Source/platform/testing/weburl_loader_mock.cc b/third_party/WebKit/Source/platform/testing/weburl_loader_mock.cc index f317d0e..85b0feb 100644 --- a/third_party/WebKit/Source/platform/testing/weburl_loader_mock.cc +++ b/third_party/WebKit/Source/platform/testing/weburl_loader_mock.cc
@@ -54,7 +54,7 @@ // didReceiveResponse() and didReceiveData() might end up getting ::cancel() // to be called which will make the ResourceLoader to delete |this|. - WeakPtr<WebURLLoaderMock> self = weak_factory_.CreateWeakPtr(); + base::WeakPtr<WebURLLoaderMock> self = weak_factory_.GetWeakPtr(); delegate->DidReceiveResponse(client_, response); if (!self) @@ -85,7 +85,7 @@ const WebURLResponse& redirect_response) { KURL redirect_url(redirect_response.HttpHeaderField("Location")); - WeakPtr<WebURLLoaderMock> self = weak_factory_.CreateWeakPtr(); + base::WeakPtr<WebURLLoaderMock> self = weak_factory_.GetWeakPtr(); bool report_raw_headers = false; bool follow = client_->WillFollowRedirect( @@ -158,8 +158,8 @@ void WebURLLoaderMock::DidChangePriority(WebURLRequest::Priority new_priority, int intra_priority_value) {} -WeakPtr<WebURLLoaderMock> WebURLLoaderMock::GetWeakPtr() { - return weak_factory_.CreateWeakPtr(); +base::WeakPtr<WebURLLoaderMock> WebURLLoaderMock::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); } } // namespace blink
diff --git a/third_party/WebKit/Source/platform/testing/weburl_loader_mock.h b/third_party/WebKit/Source/platform/testing/weburl_loader_mock.h index 12640a1..1d049ea 100644 --- a/third_party/WebKit/Source/platform/testing/weburl_loader_mock.h +++ b/third_party/WebKit/Source/platform/testing/weburl_loader_mock.h
@@ -7,8 +7,8 @@ #include <memory> #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "platform/wtf/Optional.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebURLError.h" #include "public/platform/WebURLLoader.h" @@ -61,7 +61,7 @@ bool is_deferred() { return is_deferred_; } bool is_cancelled() { return !client_; } - WeakPtr<WebURLLoaderMock> GetWeakPtr(); + base::WeakPtr<WebURLLoaderMock> GetWeakPtr(); private: WebURLLoaderMockFactoryImpl* factory_ = nullptr; @@ -70,7 +70,7 @@ bool using_default_loader_ = false; bool is_deferred_ = false; - WeakPtrFactory<WebURLLoaderMock> weak_factory_; + base::WeakPtrFactory<WebURLLoaderMock> weak_factory_; DISALLOW_COPY_AND_ASSIGN(WebURLLoaderMock); };
diff --git a/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.cc b/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.cc index ca318b9..f3ff52f 100644 --- a/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.cc +++ b/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.cc
@@ -111,7 +111,7 @@ // pending_loaders_ as it might get modified. while (!pending_loaders_.IsEmpty()) { LoaderToRequestMap::iterator iter = pending_loaders_.begin(); - WeakPtr<WebURLLoaderMock> loader(iter->key->GetWeakPtr()); + base::WeakPtr<WebURLLoaderMock> loader(iter->key->GetWeakPtr()); const WebURLRequest request = iter->value; pending_loaders_.erase(loader.get());
diff --git a/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.h b/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.h index 43082d9..1739376 100644 --- a/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.h +++ b/third_party/WebKit/Source/platform/testing/weburl_loader_mock_factory_impl.h
@@ -9,11 +9,11 @@ #include "base/files/file_path.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "platform/weborigin/KURL.h" #include "platform/weborigin/KURLHash.h" #include "platform/wtf/HashMap.h" #include "platform/wtf/Optional.h" -#include "platform/wtf/WeakPtr.h" #include "public/platform/WebURL.h" #include "public/platform/WebURLError.h" #include "public/platform/WebURLLoaderMockFactory.h" @@ -87,7 +87,7 @@ WebData* data); // Checks if the loader is pending. Otherwise, it may have been deleted. - bool IsPending(WeakPtr<WebURLLoaderMock> loader); + bool IsPending(base::WeakPtr<WebURLLoaderMock> loader); // Looks up an URL in the mock URL table. //
diff --git a/third_party/WebKit/Tools/Scripts/audit-non-blink-usage.py b/third_party/WebKit/Tools/Scripts/audit-non-blink-usage.py index 5d2078d9..0888d66 100755 --- a/third_party/WebKit/Tools/Scripts/audit-non-blink-usage.py +++ b/third_party/WebKit/Tools/Scripts/audit-non-blink-usage.py
@@ -35,6 +35,8 @@ 'base::Optional', 'base::SingleThreadTaskRunner', 'base::UnguessableToken', + 'base::WeakPtr', + 'base::WeakPtrFactory', 'base::make_optional', 'base::make_span', 'base::nullopt',
diff --git a/third_party/WebKit/public/platform/DEPS b/third_party/WebKit/public/platform/DEPS index d61d0b8..a709e9e6 100644 --- a/third_party/WebKit/public/platform/DEPS +++ b/third_party/WebKit/public/platform/DEPS
@@ -7,6 +7,7 @@ "+base/logging.h", "+base/memory/ref_counted.h", "+base/memory/scoped_refptr.h", + "+base/memory/weak_ptr.h", "+base/metrics", "+base/optional.h", "+base/strings",
diff --git a/third_party/WebKit/public/platform/scheduler/child/single_thread_idle_task_runner.h b/third_party/WebKit/public/platform/scheduler/child/single_thread_idle_task_runner.h index f78c237..3a4b000 100644 --- a/third_party/WebKit/public/platform/scheduler/child/single_thread_idle_task_runner.h +++ b/third_party/WebKit/public/platform/scheduler/child/single_thread_idle_task_runner.h
@@ -11,6 +11,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h"
diff --git a/third_party/ink/closure/array/array.js b/third_party/ink/closure/array/array.js new file mode 100644 index 0000000..926df8a --- /dev/null +++ b/third_party/ink/closure/array/array.js
@@ -0,0 +1,1667 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating arrays. + * + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + * @author pallosp@google.com (Peter Pallos) + */ + + +goog.provide('goog.array'); + +goog.require('goog.asserts'); + + +/** + * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should + * rely on Array.prototype functions, if available. + * + * The Array.prototype functions can be defined by external libraries like + * Prototype and setting this flag to false forces closure to use its own + * goog.array implementation. + * + * If your javascript can be loaded by a third party site and you are wary about + * relying on the prototype functions, specify + * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler. + * + * Setting goog.TRUSTED_SITE to false will automatically set + * NATIVE_ARRAY_PROTOTYPES to false. + */ +goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE); + + +/** + * @define {boolean} If true, JSCompiler will use the native implementation of + * array functions where appropriate (e.g., {@code Array#filter}) and remove the + * unused pure JS implementation. + */ +goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false); + + +/** + * Returns the last element in an array without removing it. + * Same as goog.array.last. + * @param {IArrayLike<T>|string} array The array. + * @return {T} Last item in array. + * @template T + */ +goog.array.peek = function(array) { + return array[array.length - 1]; +}; + + +/** + * Returns the last element in an array without removing it. + * Same as goog.array.peek. + * @param {IArrayLike<T>|string} array The array. + * @return {T} Last item in array. + * @template T + */ +goog.array.last = goog.array.peek; + +// NOTE(arv): Since most of the array functions are generic it allows you to +// pass an array-like object. Strings have a length and are considered array- +// like. However, the 'in' operator does not work on strings so we cannot just +// use the array path even if the browser supports indexing into strings. We +// therefore end up splitting the string. + + +/** + * Returns the index of the first element of an array with a specified value, or + * -1 if the element is not present in the array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof} + * + * @param {IArrayLike<T>|string} arr The array to be searched. + * @param {T} obj The object for which we are searching. + * @param {number=} opt_fromIndex The index at which to start the search. If + * omitted the search starts at index 0. + * @return {number} The index of the first matching array element. + * @template T + */ +goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.indexOf) ? + function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.indexOf.call(arr, obj, opt_fromIndex); + } : + function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? + 0 : + (opt_fromIndex < 0 ? Math.max(0, arr.length + opt_fromIndex) : + opt_fromIndex); + + if (goog.isString(arr)) { + // Array.prototype.indexOf uses === so only strings should be found. + if (!goog.isString(obj) || obj.length != 1) { + return -1; + } + return arr.indexOf(obj, fromIndex); + } + + for (var i = fromIndex; i < arr.length; i++) { + if (i in arr && arr[i] === obj) return i; + } + return -1; + }; + + +/** + * Returns the index of the last element of an array with a specified value, or + * -1 if the element is not present in the array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof} + * + * @param {!IArrayLike<T>|string} arr The array to be searched. + * @param {T} obj The object for which we are searching. + * @param {?number=} opt_fromIndex The index at which to start the search. If + * omitted the search starts at the end of the array. + * @return {number} The index of the last matching array element. + * @template T + */ +goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.lastIndexOf) ? + function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr.length != null); + + // Firefox treats undefined and null as 0 in the fromIndex argument which + // leads it to always return -1 + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + return Array.prototype.lastIndexOf.call(arr, obj, fromIndex); + } : + function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + + if (fromIndex < 0) { + fromIndex = Math.max(0, arr.length + fromIndex); + } + + if (goog.isString(arr)) { + // Array.prototype.lastIndexOf uses === so only strings should be found. + if (!goog.isString(obj) || obj.length != 1) { + return -1; + } + return arr.lastIndexOf(obj, fromIndex); + } + + for (var i = fromIndex; i >= 0; i--) { + if (i in arr && arr[i] === obj) return i; + } + return -1; + }; + + +/** + * Calls a function for each element in an array. Skips holes in the array. + * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach} + * + * @param {IArrayLike<T>|string} arr Array or array like object over + * which to iterate. + * @param {?function(this: S, T, number, ?): ?} f The function to call for every + * element. This function takes 3 arguments (the element, the index and the + * array). The return value is ignored. + * @param {S=} opt_obj The object to be used as the value of 'this' within f. + * @template T,S + */ +goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.forEach) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + Array.prototype.forEach.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr); + } + } + }; + + +/** + * Calls a function for each element in an array, starting from the last + * element rather than the first. + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this: S, T, number, ?): ?} f The function to call for every + * element. This function + * takes 3 arguments (the element, the index and the array). The return + * value is ignored. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @template T,S + */ +goog.array.forEachRight = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = l - 1; i >= 0; --i) { + if (i in arr2) { + f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr); + } + } +}; + + +/** + * Calls a function for each element in an array, and if the function returns + * true adds the element to a new array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-filter} + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?):boolean} f The function to call for + * every element. This function + * takes 3 arguments (the element, the index and the array) and must + * return a Boolean. If the return value is true the element is added to the + * result array. If it is false the element is not included. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {!Array<T>} a new array in which only elements that passed the test + * are present. + * @template T,S + */ +goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.filter) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.filter.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var res = []; + var resLength = 0; + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + var val = arr2[i]; // in case f mutates arr2 + if (f.call(/** @type {?} */ (opt_obj), val, i, arr)) { + res[resLength++] = val; + } + } + } + return res; + }; + + +/** + * Calls a function for each element in an array and inserts the result into a + * new array. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-map} + * + * @param {IArrayLike<VALUE>|string} arr Array or array like object + * over which to iterate. + * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call + * for every element. This function takes 3 arguments (the element, + * the index and the array) and should return something. The result will be + * inserted into a new array. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within f. + * @return {!Array<RESULT>} a new array with the results from f. + * @template THIS, VALUE, RESULT + */ +goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.map) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.map.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var res = new Array(l); + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2) { + res[i] = f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr); + } + } + return res; + }; + + +/** + * Passes every element of an array into a function and accumulates the result. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce} + * + * For example: + * var a = [1, 2, 3, 4]; + * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0); + * returns 10 + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {function(this:S, R, T, number, ?) : R} f The function to call for + * every element. This function + * takes 4 arguments (the function's previous result or the initial value, + * the value of the current array element, the current array index, and the + * array itself) + * function(previousValue, currentValue, index, array). + * @param {?} val The initial value to pass into the function on the first call. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {R} Result of evaluating f repeatedly across the values of the array. + * @template T,S,R + */ +goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.reduce) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return Array.prototype.reduce.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEach(arr, function(val, index) { + rval = f.call(/** @type {?} */ (opt_obj), rval, val, index, arr); + }); + return rval; + }; + + +/** + * Passes every element of an array into a function and accumulates the result, + * starting from the last element and working towards the first. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright} + * + * For example: + * var a = ['a', 'b', 'c']; + * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, ''); + * returns 'cba' + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, R, T, number, ?) : R} f The function to call for + * every element. This function + * takes 4 arguments (the function's previous result or the initial value, + * the value of the current array element, the current array index, and the + * array itself) + * function(previousValue, currentValue, index, array). + * @param {?} val The initial value to pass into the function on the first call. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {R} Object returned as a result of evaluating f repeatedly across the + * values of the array. + * @template T,S,R + */ +goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.reduceRight) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + goog.asserts.assert(f != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return Array.prototype.reduceRight.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEachRight(arr, function(val, index) { + rval = f.call(/** @type {?} */ (opt_obj), rval, val, index, arr); + }); + return rval; + }; + + +/** + * Calls f for each element of an array. If any call returns true, some() + * returns true (without checking the remaining elements). If all calls + * return false, some() returns false. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-some} + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {boolean} true if any element passes the test. + * @template T,S + */ +goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.some) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.some.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) { + return true; + } + } + return false; + }; + + +/** + * Call f for each element of an array. If all calls return true, every() + * returns true. If any call returns false, every() returns false and + * does not continue to check the remaining elements. + * + * See {@link http://tinyurl.com/developer-mozilla-org-array-every} + * + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within f. + * @return {boolean} false if any element fails the test. + * @template T,S + */ +goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.every) ? + function(arr, f, opt_obj) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.every.call(arr, f, opt_obj); + } : + function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && !f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) { + return false; + } + } + return true; + }; + + +/** + * Counts the array elements that fulfill the predicate, i.e. for which the + * callback function returns true. Skips holes in the array. + * + * @param {!IArrayLike<T>|string} arr Array or array like object + * over which to iterate. + * @param {function(this: S, T, number, ?): boolean} f The function to call for + * every element. Takes 3 arguments (the element, the index and the array). + * @param {S=} opt_obj The object to be used as the value of 'this' within f. + * @return {number} The number of the matching elements. + * @template T,S + */ +goog.array.count = function(arr, f, opt_obj) { + var count = 0; + goog.array.forEach(arr, function(element, index, arr) { + if (f.call(/** @type {?} */ (opt_obj), element, index, arr)) { + ++count; + } + }, opt_obj); + return count; +}; + + +/** + * Search an array for the first element that satisfies a given condition and + * return that element. + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {T|null} The first array element that passes the test, or null if no + * element is found. + * @template T,S + */ +goog.array.find = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]; +}; + + +/** + * Search an array for the first element that satisfies a given condition and + * return its index. + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call for + * every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {number} The index of the first array element that passes the test, + * or -1 if no element is found. + * @template T,S + */ +goog.array.findIndex = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = 0; i < l; i++) { + if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) { + return i; + } + } + return -1; +}; + + +/** + * Search an array (in reverse order) for the last element that satisfies a + * given condition and return that element. + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {T|null} The last array element that passes the test, or null if no + * element is found. + * @template T,S + */ +goog.array.findRight = function(arr, f, opt_obj) { + var i = goog.array.findIndexRight(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i]; +}; + + +/** + * Search an array (in reverse order) for the last element that satisfies a + * given condition and return its index. + * @param {IArrayLike<T>|string} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {number} The index of the last array element that passes the test, + * or -1 if no element is found. + * @template T,S + */ +goog.array.findIndexRight = function(arr, f, opt_obj) { + var l = arr.length; // must be fixed during loop... see docs + var arr2 = goog.isString(arr) ? arr.split('') : arr; + for (var i = l - 1; i >= 0; i--) { + if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) { + return i; + } + } + return -1; +}; + + +/** + * Whether the array contains the given object. + * @param {IArrayLike<?>|string} arr The array to test for the presence of the + * element. + * @param {*} obj The object for which to test. + * @return {boolean} true if obj is present. + */ +goog.array.contains = function(arr, obj) { + return goog.array.indexOf(arr, obj) >= 0; +}; + + +/** + * Whether the array is empty. + * @param {IArrayLike<?>|string} arr The array to test. + * @return {boolean} true if empty. + */ +goog.array.isEmpty = function(arr) { + return arr.length == 0; +}; + + +/** + * Clears the array. + * @param {IArrayLike<?>} arr Array or array like object to clear. + */ +goog.array.clear = function(arr) { + // For non real arrays we don't have the magic length so we delete the + // indices. + if (!goog.isArray(arr)) { + for (var i = arr.length - 1; i >= 0; i--) { + delete arr[i]; + } + } + arr.length = 0; +}; + + +/** + * Pushes an item into an array, if it's not already in the array. + * @param {Array<T>} arr Array into which to insert the item. + * @param {T} obj Value to add. + * @template T + */ +goog.array.insert = function(arr, obj) { + if (!goog.array.contains(arr, obj)) { + arr.push(obj); + } +}; + + +/** + * Inserts an object at the given index of the array. + * @param {IArrayLike<?>} arr The array to modify. + * @param {*} obj The object to insert. + * @param {number=} opt_i The index at which to insert the object. If omitted, + * treated as 0. A negative index is counted from the end of the array. + */ +goog.array.insertAt = function(arr, obj, opt_i) { + goog.array.splice(arr, opt_i, 0, obj); +}; + + +/** + * Inserts at the given index of the array, all elements of another array. + * @param {IArrayLike<?>} arr The array to modify. + * @param {IArrayLike<?>} elementsToAdd The array of elements to add. + * @param {number=} opt_i The index at which to insert the object. If omitted, + * treated as 0. A negative index is counted from the end of the array. + */ +goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) { + goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd); +}; + + +/** + * Inserts an object into an array before a specified object. + * @param {Array<T>} arr The array to modify. + * @param {T} obj The object to insert. + * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2 + * is omitted or not found, obj is inserted at the end of the array. + * @template T + */ +goog.array.insertBefore = function(arr, obj, opt_obj2) { + var i; + if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) { + arr.push(obj); + } else { + goog.array.insertAt(arr, obj, i); + } +}; + + +/** + * Removes the first occurrence of a particular value from an array. + * @param {IArrayLike<T>} arr Array from which to remove + * value. + * @param {T} obj Object to remove. + * @return {boolean} True if an element was removed. + * @template T + */ +goog.array.remove = function(arr, obj) { + var i = goog.array.indexOf(arr, obj); + var rv; + if ((rv = i >= 0)) { + goog.array.removeAt(arr, i); + } + return rv; +}; + + +/** + * Removes the last occurrence of a particular value from an array. + * @param {!IArrayLike<T>} arr Array from which to remove value. + * @param {T} obj Object to remove. + * @return {boolean} True if an element was removed. + * @template T + */ +goog.array.removeLast = function(arr, obj) { + var i = goog.array.lastIndexOf(arr, obj); + if (i >= 0) { + goog.array.removeAt(arr, i); + return true; + } + return false; +}; + + +/** + * Removes from an array the element at index i + * @param {IArrayLike<?>} arr Array or array like object from which to + * remove value. + * @param {number} i The index to remove. + * @return {boolean} True if an element was removed. + */ +goog.array.removeAt = function(arr, i) { + goog.asserts.assert(arr.length != null); + + // use generic form of splice + // splice returns the removed items and if successful the length of that + // will be 1 + return Array.prototype.splice.call(arr, i, 1).length == 1; +}; + + +/** + * Removes the first value that satisfies the given condition. + * @param {IArrayLike<T>} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {boolean} True if an element was removed. + * @template T,S + */ +goog.array.removeIf = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + if (i >= 0) { + goog.array.removeAt(arr, i); + return true; + } + return false; +}; + + +/** + * Removes all values that satisfy the given condition. + * @param {IArrayLike<T>} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {number} The number of items removed + * @template T,S + */ +goog.array.removeAllIf = function(arr, f, opt_obj) { + var removedCount = 0; + goog.array.forEachRight(arr, function(val, index) { + if (f.call(/** @type {?} */ (opt_obj), val, index, arr)) { + if (goog.array.removeAt(arr, index)) { + removedCount++; + } + } + }); + return removedCount; +}; + + +/** + * Returns a new array that is the result of joining the arguments. If arrays + * are passed then their items are added, however, if non-arrays are passed they + * will be added to the return array as is. + * + * Note that ArrayLike objects will be added as is, rather than having their + * items added. + * + * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4] + * goog.array.concat(0, [1, 2]) -> [0, 1, 2] + * goog.array.concat([1, 2], null) -> [1, 2, null] + * + * There is bug in all current versions of IE (6, 7 and 8) where arrays created + * in an iframe become corrupted soon (not immediately) after the iframe is + * destroyed. This is common if loading data via goog.net.IframeIo, for example. + * This corruption only affects the concat method which will start throwing + * Catastrophic Errors (#-2147418113). + * + * See http://endoflow.com/scratch/corrupted-arrays.html for a test case. + * + * Internally goog.array should use this, so that all methods will continue to + * work on these broken array objects. + * + * @param {...*} var_args Items to concatenate. Arrays will have each item + * added, while primitives and objects will be added as is. + * @return {!Array<?>} The new resultant array. + */ +goog.array.concat = function(var_args) { + return Array.prototype.concat.apply([], arguments); +}; + + +/** + * Returns a new array that contains the contents of all the arrays passed. + * @param {...!Array<T>} var_args + * @return {!Array<T>} + * @template T + */ +goog.array.join = function(var_args) { + return Array.prototype.concat.apply([], arguments); +}; + + +/** + * Converts an object to an array. + * @param {IArrayLike<T>|string} object The object to convert to an + * array. + * @return {!Array<T>} The object converted into an array. If object has a + * length property, every property indexed with a non-negative number + * less than length will be included in the result. If object does not + * have a length property, an empty array will be returned. + * @template T + */ +goog.array.toArray = function(object) { + var length = object.length; + + // If length is not a number the following it false. This case is kept for + // backwards compatibility since there are callers that pass objects that are + // not array like. + if (length > 0) { + var rv = new Array(length); + for (var i = 0; i < length; i++) { + rv[i] = object[i]; + } + return rv; + } + return []; +}; + + +/** + * Does a shallow copy of an array. + * @param {IArrayLike<T>|string} arr Array or array-like object to + * clone. + * @return {!Array<T>} Clone of the input array. + * @template T + */ +goog.array.clone = goog.array.toArray; + + +/** + * Extends an array with another array, element, or "array like" object. + * This function operates 'in-place', it does not create a new Array. + * + * Example: + * var a = []; + * goog.array.extend(a, [0, 1]); + * a; // [0, 1] + * goog.array.extend(a, 2); + * a; // [0, 1, 2] + * + * @param {Array<VALUE>} arr1 The array to modify. + * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements + * to add to arr1. + * @template VALUE + */ +goog.array.extend = function(arr1, var_args) { + for (var i = 1; i < arguments.length; i++) { + var arr2 = arguments[i]; + if (goog.isArrayLike(arr2)) { + var len1 = arr1.length || 0; + var len2 = arr2.length || 0; + arr1.length = len1 + len2; + for (var j = 0; j < len2; j++) { + arr1[len1 + j] = arr2[j]; + } + } else { + arr1.push(arr2); + } + } +}; + + +/** + * Adds or removes elements from an array. This is a generic version of Array + * splice. This means that it might work on other objects similar to arrays, + * such as the arguments object. + * + * @param {IArrayLike<T>} arr The array to modify. + * @param {number|undefined} index The index at which to start changing the + * array. If not defined, treated as 0. + * @param {number} howMany How many elements to remove (0 means no removal. A + * value below 0 is treated as zero and so is any other non number. Numbers + * are floored). + * @param {...T} var_args Optional, additional elements to insert into the + * array. + * @return {!Array<T>} the removed elements. + * @template T + */ +goog.array.splice = function(arr, index, howMany, var_args) { + goog.asserts.assert(arr.length != null); + + return Array.prototype.splice.apply(arr, goog.array.slice(arguments, 1)); +}; + + +/** + * Returns a new array from a segment of an array. This is a generic version of + * Array slice. This means that it might work on other objects similar to + * arrays, such as the arguments object. + * + * @param {IArrayLike<T>|string} arr The array from + * which to copy a segment. + * @param {number} start The index of the first element to copy. + * @param {number=} opt_end The index after the last element to copy. + * @return {!Array<T>} A new array containing the specified segment of the + * original array. + * @template T + */ +goog.array.slice = function(arr, start, opt_end) { + goog.asserts.assert(arr.length != null); + + // passing 1 arg to slice is not the same as passing 2 where the second is + // null or undefined (in that case the second argument is treated as 0). + // we could use slice on the arguments object and then use apply instead of + // testing the length + if (arguments.length <= 2) { + return Array.prototype.slice.call(arr, start); + } else { + return Array.prototype.slice.call(arr, start, opt_end); + } +}; + + +/** + * Removes all duplicates from an array (retaining only the first + * occurrence of each array element). This function modifies the + * array in place and doesn't change the order of the non-duplicate items. + * + * For objects, duplicates are identified as having the same unique ID as + * defined by {@link goog.getUid}. + * + * Alternatively you can specify a custom hash function that returns a unique + * value for each item in the array it should consider unique. + * + * Runtime: N, + * Worstcase space: 2N (no dupes) + * + * @param {IArrayLike<T>} arr The array from which to remove + * duplicates. + * @param {Array=} opt_rv An optional array in which to return the results, + * instead of performing the removal inplace. If specified, the original + * array will remain unchanged. + * @param {function(T):string=} opt_hashFn An optional function to use to + * apply to every item in the array. This function should return a unique + * value for each item in the array it should consider unique. + * @template T + */ +goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) { + var returnArray = opt_rv || arr; + var defaultHashFn = function(item) { + // Prefix each type with a single character representing the type to + // prevent conflicting keys (e.g. true and 'true'). + return goog.isObject(item) ? 'o' + goog.getUid(item) : + (typeof item).charAt(0) + item; + }; + var hashFn = opt_hashFn || defaultHashFn; + + var seen = {}, cursorInsert = 0, cursorRead = 0; + while (cursorRead < arr.length) { + var current = arr[cursorRead++]; + var key = hashFn(current); + if (!Object.prototype.hasOwnProperty.call(seen, key)) { + seen[key] = true; + returnArray[cursorInsert++] = current; + } + } + returnArray.length = cursorInsert; +}; + + +/** + * Searches the specified array for the specified target using the binary + * search algorithm. If no opt_compareFn is specified, elements are compared + * using <code>goog.array.defaultCompare</code>, which compares the elements + * using the built in < and > operators. This will produce the expected + * behavior for homogeneous arrays of String(s) and Number(s). The array + * specified <b>must</b> be sorted in ascending order (as defined by the + * comparison function). If the array is not sorted, results are undefined. + * If the array contains multiple instances of the specified target value, any + * of these instances may be found. + * + * Runtime: O(log n) + * + * @param {IArrayLike<VALUE>} arr The array to be searched. + * @param {TARGET} target The sought value. + * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {number} Lowest index of the target value if found, otherwise + * (-(insertion point) - 1). The insertion point is where the value should + * be inserted into arr to preserve the sorted property. Return value >= 0 + * iff target is found. + * @template TARGET, VALUE + */ +goog.array.binarySearch = function(arr, target, opt_compareFn) { + return goog.array.binarySearch_( + arr, opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */, + target); +}; + + +/** + * Selects an index in the specified array using the binary search algorithm. + * The evaluator receives an element and determines whether the desired index + * is before, at, or after it. The evaluator must be consistent (formally, + * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign) + * must be monotonically non-increasing). + * + * Runtime: O(log n) + * + * @param {IArrayLike<VALUE>} arr The array to be searched. + * @param {function(this:THIS, VALUE, number, ?): number} evaluator + * Evaluator function that receives 3 arguments (the element, the index and + * the array). Should return a negative number, zero, or a positive number + * depending on whether the desired index is before, at, or after the + * element passed to it. + * @param {THIS=} opt_obj The object to be used as the value of 'this' + * within evaluator. + * @return {number} Index of the leftmost element matched by the evaluator, if + * such exists; otherwise (-(insertion point) - 1). The insertion point is + * the index of the first element for which the evaluator returns negative, + * or arr.length if no such element exists. The return value is non-negative + * iff a match is found. + * @template THIS, VALUE + */ +goog.array.binarySelect = function(arr, evaluator, opt_obj) { + return goog.array.binarySearch_( + arr, evaluator, true /* isEvaluator */, undefined /* opt_target */, + opt_obj); +}; + + +/** + * Implementation of a binary search algorithm which knows how to use both + * comparison functions and evaluators. If an evaluator is provided, will call + * the evaluator with the given optional data object, conforming to the + * interface defined in binarySelect. Otherwise, if a comparison function is + * provided, will call the comparison function against the given data object. + * + * This implementation purposefully does not use goog.bind or goog.partial for + * performance reasons. + * + * Runtime: O(log n) + * + * @param {IArrayLike<?>} arr The array to be searched. + * @param {function(?, ?, ?): number | function(?, ?): number} compareFn + * Either an evaluator or a comparison function, as defined by binarySearch + * and binarySelect above. + * @param {boolean} isEvaluator Whether the function is an evaluator or a + * comparison function. + * @param {?=} opt_target If the function is a comparison function, then + * this is the target to binary search for. + * @param {Object=} opt_selfObj If the function is an evaluator, this is an + * optional this object for the evaluator. + * @return {number} Lowest index of the target value if found, otherwise + * (-(insertion point) - 1). The insertion point is where the value should + * be inserted into arr to preserve the sorted property. Return value >= 0 + * iff target is found. + * @private + */ +goog.array.binarySearch_ = function( + arr, compareFn, isEvaluator, opt_target, opt_selfObj) { + var left = 0; // inclusive + var right = arr.length; // exclusive + var found; + while (left < right) { + var middle = (left + right) >> 1; + var compareResult; + if (isEvaluator) { + compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr); + } else { + // NOTE(dimvar): To avoid this cast, we'd have to use function overloading + // for the type of binarySearch_, which the type system can't express yet. + compareResult = /** @type {function(?, ?): number} */ (compareFn)( + opt_target, arr[middle]); + } + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + // We are looking for the lowest index so we can't return immediately. + found = !compareResult; + } + } + // left is the index if found, or the insertion point otherwise. + // ~left is a shorthand for -left - 1. + return found ? left : ~left; +}; + + +/** + * Sorts the specified array into ascending order. If no opt_compareFn is + * specified, elements are compared using + * <code>goog.array.defaultCompare</code>, which compares the elements using + * the built in < and > operators. This will produce the expected behavior + * for homogeneous arrays of String(s) and Number(s), unlike the native sort, + * but will give unpredictable results for heterogeneous lists of strings and + * numbers with different numbers of digits. + * + * This sort is not guaranteed to be stable. + * + * Runtime: Same as <code>Array.prototype.sort</code> + * + * @param {Array<T>} arr The array to be sorted. + * @param {?function(T,T):number=} opt_compareFn Optional comparison + * function by which the + * array is to be ordered. Should take 2 arguments to compare, and return a + * negative number, zero, or a positive number depending on whether the + * first argument is less than, equal to, or greater than the second. + * @template T + */ +goog.array.sort = function(arr, opt_compareFn) { + // TODO(arv): Update type annotation since null is not accepted. + arr.sort(opt_compareFn || goog.array.defaultCompare); +}; + + +/** + * Sorts the specified array into ascending order in a stable way. If no + * opt_compareFn is specified, elements are compared using + * <code>goog.array.defaultCompare</code>, which compares the elements using + * the built in < and > operators. This will produce the expected behavior + * for homogeneous arrays of String(s) and Number(s). + * + * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional + * O(n) overhead of copying the array twice. + * + * @param {Array<T>} arr The array to be sorted. + * @param {?function(T, T): number=} opt_compareFn Optional comparison function + * by which the array is to be ordered. Should take 2 arguments to compare, + * and return a negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template T + */ +goog.array.stableSort = function(arr, opt_compareFn) { + var compArr = new Array(arr.length); + for (var i = 0; i < arr.length; i++) { + compArr[i] = {index: i, value: arr[i]}; + } + var valueCompareFn = opt_compareFn || goog.array.defaultCompare; + function stableCompareFn(obj1, obj2) { + return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index; + } + goog.array.sort(compArr, stableCompareFn); + for (var i = 0; i < arr.length; i++) { + arr[i] = compArr[i].value; + } +}; + + +/** + * Sort the specified array into ascending order based on item keys + * returned by the specified key function. + * If no opt_compareFn is specified, the keys are compared in ascending order + * using <code>goog.array.defaultCompare</code>. + * + * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code> + * and f(n) is runtime of the key function. + * + * @param {Array<T>} arr The array to be sorted. + * @param {function(T): K} keyFn Function taking array element and returning + * a key used for sorting this element. + * @param {?function(K, K): number=} opt_compareFn Optional comparison function + * by which the keys are to be ordered. Should take 2 arguments to compare, + * and return a negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template T,K + */ +goog.array.sortByKey = function(arr, keyFn, opt_compareFn) { + var keyCompareFn = opt_compareFn || goog.array.defaultCompare; + goog.array.sort( + arr, function(a, b) { return keyCompareFn(keyFn(a), keyFn(b)); }); +}; + + +/** + * Sorts an array of objects by the specified object key and compare + * function. If no compare function is provided, the key values are + * compared in ascending order using <code>goog.array.defaultCompare</code>. + * This won't work for keys that get renamed by the compiler. So use + * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}. + * @param {Array<Object>} arr An array of objects to sort. + * @param {string} key The object key to sort by. + * @param {Function=} opt_compareFn The function to use to compare key + * values. + */ +goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) { + goog.array.sortByKey(arr, function(obj) { return obj[key]; }, opt_compareFn); +}; + + +/** + * Tells if the array is sorted. + * @param {!Array<T>} arr The array. + * @param {?function(T,T):number=} opt_compareFn Function to compare the + * array elements. + * Should take 2 arguments to compare, and return a negative number, zero, + * or a positive number depending on whether the first argument is less + * than, equal to, or greater than the second. + * @param {boolean=} opt_strict If true no equal elements are allowed. + * @return {boolean} Whether the array is sorted. + * @template T + */ +goog.array.isSorted = function(arr, opt_compareFn, opt_strict) { + var compare = opt_compareFn || goog.array.defaultCompare; + for (var i = 1; i < arr.length; i++) { + var compareResult = compare(arr[i - 1], arr[i]); + if (compareResult > 0 || compareResult == 0 && opt_strict) { + return false; + } + } + return true; +}; + + +/** + * Compares two arrays for equality. Two arrays are considered equal if they + * have the same length and their corresponding elements are equal according to + * the comparison function. + * + * @param {IArrayLike<?>} arr1 The first array to compare. + * @param {IArrayLike<?>} arr2 The second array to compare. + * @param {Function=} opt_equalsFn Optional comparison function. + * Should take 2 arguments to compare, and return true if the arguments + * are equal. Defaults to {@link goog.array.defaultCompareEquality} which + * compares the elements using the built-in '===' operator. + * @return {boolean} Whether the two arrays are equal. + */ +goog.array.equals = function(arr1, arr2, opt_equalsFn) { + if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) || + arr1.length != arr2.length) { + return false; + } + var l = arr1.length; + var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; + for (var i = 0; i < l; i++) { + if (!equalsFn(arr1[i], arr2[i])) { + return false; + } + } + return true; +}; + + +/** + * 3-way array compare function. + * @param {!IArrayLike<VALUE>} arr1 The first array to + * compare. + * @param {!IArrayLike<VALUE>} arr2 The second array to + * compare. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is to be ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {number} Negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template VALUE + */ +goog.array.compare3 = function(arr1, arr2, opt_compareFn) { + var compare = opt_compareFn || goog.array.defaultCompare; + var l = Math.min(arr1.length, arr2.length); + for (var i = 0; i < l; i++) { + var result = compare(arr1[i], arr2[i]); + if (result != 0) { + return result; + } + } + return goog.array.defaultCompare(arr1.length, arr2.length); +}; + + +/** + * Compares its two arguments for order, using the built in < and > + * operators. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is less than, equal to, or greater than the second, + * respectively. + * @template VALUE + */ +goog.array.defaultCompare = function(a, b) { + return a > b ? 1 : a < b ? -1 : 0; +}; + + +/** + * Compares its two arguments for inverse order, using the built in < and > + * operators. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is greater than, equal to, or less than the second, + * respectively. + * @template VALUE + */ +goog.array.inverseDefaultCompare = function(a, b) { + return -goog.array.defaultCompare(a, b); +}; + + +/** + * Compares its two arguments for equality, using the built in === operator. + * @param {*} a The first object to compare. + * @param {*} b The second object to compare. + * @return {boolean} True if the two arguments are equal, false otherwise. + */ +goog.array.defaultCompareEquality = function(a, b) { + return a === b; +}; + + +/** + * Inserts a value into a sorted array. The array is not modified if the + * value is already present. + * @param {IArrayLike<VALUE>} array The array to modify. + * @param {VALUE} value The object to insert. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {boolean} True if an element was inserted. + * @template VALUE + */ +goog.array.binaryInsert = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + if (index < 0) { + goog.array.insertAt(array, value, -(index + 1)); + return true; + } + return false; +}; + + +/** + * Removes a value from a sorted array. + * @param {!IArrayLike<VALUE>} array The array to modify. + * @param {VALUE} value The object to remove. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. + * @return {boolean} True if an element was removed. + * @template VALUE + */ +goog.array.binaryRemove = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + return (index >= 0) ? goog.array.removeAt(array, index) : false; +}; + + +/** + * Splits an array into disjoint buckets according to a splitting function. + * @param {Array<T>} array The array. + * @param {function(this:S, T, number, !Array<T>):?} sorter Function to call for + * every element. This takes 3 arguments (the element, the index and the + * array) and must return a valid object key (a string, number, etc), or + * undefined, if that object should not be placed in a bucket. + * @param {S=} opt_obj The object to be used as the value of 'this' within + * sorter. + * @return {!Object<!Array<T>>} An object, with keys being all of the unique + * return values of sorter, and values being arrays containing the items for + * which the splitter returned that key. + * @template T,S + */ +goog.array.bucket = function(array, sorter, opt_obj) { + var buckets = {}; + + for (var i = 0; i < array.length; i++) { + var value = array[i]; + var key = sorter.call(/** @type {?} */ (opt_obj), value, i, array); + if (goog.isDef(key)) { + // Push the value to the right bucket, creating it if necessary. + var bucket = buckets[key] || (buckets[key] = []); + bucket.push(value); + } + } + + return buckets; +}; + + +/** + * Creates a new object built from the provided array and the key-generation + * function. + * @param {IArrayLike<T>} arr Array or array like object over + * which to iterate whose elements will be the values in the new object. + * @param {?function(this:S, T, number, ?) : string} keyFunc The function to + * call for every element. This function takes 3 arguments (the element, the + * index and the array) and should return a string that will be used as the + * key for the element in the new object. If the function returns the same + * key for more than one element, the value for that key is + * implementation-defined. + * @param {S=} opt_obj The object to be used as the value of 'this' + * within keyFunc. + * @return {!Object<T>} The new object. + * @template T,S + */ +goog.array.toObject = function(arr, keyFunc, opt_obj) { + var ret = {}; + goog.array.forEach(arr, function(element, index) { + ret[keyFunc.call(/** @type {?} */ (opt_obj), element, index, arr)] = + element; + }); + return ret; +}; + + +/** + * Creates a range of numbers in an arithmetic progression. + * + * Range takes 1, 2, or 3 arguments: + * <pre> + * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4] + * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4] + * range(-2, -5, -1) produces [-2, -3, -4] + * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5. + * </pre> + * + * @param {number} startOrEnd The starting value of the range if an end argument + * is provided. Otherwise, the start value is 0, and this is the end value. + * @param {number=} opt_end The optional end value of the range. + * @param {number=} opt_step The step size between range values. Defaults to 1 + * if opt_step is undefined or 0. + * @return {!Array<number>} An array of numbers for the requested range. May be + * an empty array if adding the step would not converge toward the end + * value. + */ +goog.array.range = function(startOrEnd, opt_end, opt_step) { + var array = []; + var start = 0; + var end = startOrEnd; + var step = opt_step || 1; + if (opt_end !== undefined) { + start = startOrEnd; + end = opt_end; + } + + if (step * (end - start) < 0) { + // Sign mismatch: start + step will never reach the end value. + return []; + } + + if (step > 0) { + for (var i = start; i < end; i += step) { + array.push(i); + } + } else { + for (var i = start; i > end; i += step) { + array.push(i); + } + } + return array; +}; + + +/** + * Returns an array consisting of the given value repeated N times. + * + * @param {VALUE} value The value to repeat. + * @param {number} n The repeat count. + * @return {!Array<VALUE>} An array with the repeated value. + * @template VALUE + */ +goog.array.repeat = function(value, n) { + var array = []; + for (var i = 0; i < n; i++) { + array[i] = value; + } + return array; +}; + + +/** + * Returns an array consisting of every argument with all arrays + * expanded in-place recursively. + * + * @param {...*} var_args The values to flatten. + * @return {!Array<?>} An array containing the flattened values. + */ +goog.array.flatten = function(var_args) { + var CHUNK_SIZE = 8192; + + var result = []; + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (goog.isArray(element)) { + for (var c = 0; c < element.length; c += CHUNK_SIZE) { + var chunk = goog.array.slice(element, c, c + CHUNK_SIZE); + var recurseResult = goog.array.flatten.apply(null, chunk); + for (var r = 0; r < recurseResult.length; r++) { + result.push(recurseResult[r]); + } + } + } else { + result.push(element); + } + } + return result; +}; + + +/** + * Rotates an array in-place. After calling this method, the element at + * index i will be the element previously at index (i - n) % + * array.length, for all values of i between 0 and array.length - 1, + * inclusive. + * + * For example, suppose list comprises [t, a, n, k, s]. After invoking + * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k]. + * + * @param {!Array<T>} array The array to rotate. + * @param {number} n The amount to rotate. + * @return {!Array<T>} The array. + * @template T + */ +goog.array.rotate = function(array, n) { + goog.asserts.assert(array.length != null); + + if (array.length) { + n %= array.length; + if (n > 0) { + Array.prototype.unshift.apply(array, array.splice(-n, n)); + } else if (n < 0) { + Array.prototype.push.apply(array, array.splice(0, -n)); + } + } + return array; +}; + + +/** + * Moves one item of an array to a new position keeping the order of the rest + * of the items. Example use case: keeping a list of JavaScript objects + * synchronized with the corresponding list of DOM elements after one of the + * elements has been dragged to a new position. + * @param {!IArrayLike<?>} arr The array to modify. + * @param {number} fromIndex Index of the item to move between 0 and + * {@code arr.length - 1}. + * @param {number} toIndex Target index between 0 and {@code arr.length - 1}. + */ +goog.array.moveItem = function(arr, fromIndex, toIndex) { + goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length); + goog.asserts.assert(toIndex >= 0 && toIndex < arr.length); + // Remove 1 item at fromIndex. + var removedItems = Array.prototype.splice.call(arr, fromIndex, 1); + // Insert the removed item at toIndex. + Array.prototype.splice.call(arr, toIndex, 0, removedItems[0]); + // We don't use goog.array.insertAt and goog.array.removeAt, because they're + // significantly slower than splice. +}; + + +/** + * Creates a new array for which the element at position i is an array of the + * ith element of the provided arrays. The returned array will only be as long + * as the shortest array provided; additional values are ignored. For example, + * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]]. + * + * This is similar to the zip() function in Python. See {@link + * http://docs.python.org/library/functions.html#zip} + * + * @param {...!IArrayLike<?>} var_args Arrays to be combined. + * @return {!Array<!Array<?>>} A new array of arrays created from + * provided arrays. + */ +goog.array.zip = function(var_args) { + if (!arguments.length) { + return []; + } + var result = []; + var minLen = arguments[0].length; + for (var i = 1; i < arguments.length; i++) { + if (arguments[i].length < minLen) { + minLen = arguments[i].length; + } + } + for (var i = 0; i < minLen; i++) { + var value = []; + for (var j = 0; j < arguments.length; j++) { + value.push(arguments[j][i]); + } + result.push(value); + } + return result; +}; + + +/** + * Shuffles the values in the specified array using the Fisher-Yates in-place + * shuffle (also known as the Knuth Shuffle). By default, calls Math.random() + * and so resets the state of that random number generator. Similarly, may reset + * the state of the any other specified random number generator. + * + * Runtime: O(n) + * + * @param {!Array<?>} arr The array to be shuffled. + * @param {function():number=} opt_randFn Optional random function to use for + * shuffling. + * Takes no arguments, and returns a random number on the interval [0, 1). + * Defaults to Math.random() using JavaScript's built-in Math library. + */ +goog.array.shuffle = function(arr, opt_randFn) { + var randFn = opt_randFn || Math.random; + + for (var i = arr.length - 1; i > 0; i--) { + // Choose a random array index in [0, i] (inclusive with i). + var j = Math.floor(randFn() * (i + 1)); + + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +}; + + +/** + * Returns a new array of elements from arr, based on the indexes of elements + * provided by index_arr. For example, the result of index copying + * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c']. + * + * @param {!Array<T>} arr The array to get a indexed copy from. + * @param {!Array<number>} index_arr An array of indexes to get from arr. + * @return {!Array<T>} A new array of elements from arr in index_arr order. + * @template T + */ +goog.array.copyByIndex = function(arr, index_arr) { + var result = []; + goog.array.forEach(index_arr, function(index) { result.push(arr[index]); }); + return result; +}; + + +/** + * Maps each element of the input array into zero or more elements of the output + * array. + * + * @param {!IArrayLike<VALUE>|string} arr Array or array like object + * over which to iterate. + * @param {function(this:THIS, VALUE, number, ?): !Array<RESULT>} f The function + * to call for every element. This function takes 3 arguments (the element, + * the index and the array) and should return an array. The result will be + * used to extend a new array. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within f. + * @return {!Array<RESULT>} a new array with the concatenation of all arrays + * returned from f. + * @template THIS, VALUE, RESULT + */ +goog.array.concatMap = function(arr, f, opt_obj) { + return goog.array.concat.apply([], goog.array.map(arr, f, opt_obj)); +};
diff --git a/third_party/ink/closure/asserts/asserts.js b/third_party/ink/closure/asserts/asserts.js new file mode 100644 index 0000000..89cad0a --- /dev/null +++ b/third_party/ink/closure/asserts/asserts.js
@@ -0,0 +1,391 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities to check the preconditions, postconditions and + * invariants runtime. + * + * Methods in this package should be given special treatment by the compiler + * for type-inference. For example, <code>goog.asserts.assert(foo)</code> + * will restrict <code>foo</code> to a truthy value. + * + * The compiler has an option to disable asserts. So code like: + * <code> + * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); + * </code> + * will be transformed into: + * <code> + * var x = foo(); + * </code> + * The compiler will leave in foo() (because its return value is used), + * but it will remove bar() because it assumes it does not have side-effects. + * + * @author pallosp@google.com (Peter Pallos) + * @author agrieve@google.com (Andrew Grieve) + */ + +goog.provide('goog.asserts'); +goog.provide('goog.asserts.AssertionError'); + +goog.require('goog.debug.Error'); +goog.require('goog.dom.NodeType'); +goog.require('goog.string'); + + +/** + * @define {boolean} Whether to strip out asserts or to leave them in. + */ +goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); + + + +/** + * Error object for failed assertions. + * @param {string} messagePattern The pattern that was used to form message. + * @param {!Array<*>} messageArgs The items to substitute into the pattern. + * @constructor + * @extends {goog.debug.Error} + * @final + */ +goog.asserts.AssertionError = function(messagePattern, messageArgs) { + messageArgs.unshift(messagePattern); + goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs)); + // Remove the messagePattern afterwards to avoid permanently modifying the + // passed in array. + messageArgs.shift(); + + /** + * The message pattern used to format the error message. Error handlers can + * use this to uniquely identify the assertion. + * @type {string} + */ + this.messagePattern = messagePattern; +}; +goog.inherits(goog.asserts.AssertionError, goog.debug.Error); + + +/** @override */ +goog.asserts.AssertionError.prototype.name = 'AssertionError'; + + +/** + * The default error handler. + * @param {!goog.asserts.AssertionError} e The exception to be handled. + */ +goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { + throw e; +}; + + +/** + * The handler responsible for throwing or logging assertion errors. + * @private {function(!goog.asserts.AssertionError)} + */ +goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER; + + +/** + * Throws an exception with the given message and "Assertion failed" prefixed + * onto it. + * @param {string} defaultMessage The message to use if givenMessage is empty. + * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage. + * @param {string|undefined} givenMessage Message supplied by the caller. + * @param {Array<*>} givenArgs The substitution arguments for givenMessage. + * @throws {goog.asserts.AssertionError} When the value is not a number. + * @private + */ +goog.asserts.doAssertFailure_ = function( + defaultMessage, defaultArgs, givenMessage, givenArgs) { + var message = 'Assertion failed'; + if (givenMessage) { + message += ': ' + givenMessage; + var args = givenArgs; + } else if (defaultMessage) { + message += ': ' + defaultMessage; + args = defaultArgs; + } + // The '' + works around an Opera 10 bug in the unit tests. Without it, + // a stack trace is added to var message above. With this, a stack trace is + // not added until this line (it causes the extra garbage to be added after + // the assertion message instead of in the middle of it). + var e = new goog.asserts.AssertionError('' + message, args || []); + goog.asserts.errorHandler_(e); +}; + + +/** + * Sets a custom error handler that can be used to customize the behavior of + * assertion failures, for example by turning all assertion failures into log + * messages. + * @param {function(!goog.asserts.AssertionError)} errorHandler + */ +goog.asserts.setErrorHandler = function(errorHandler) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.errorHandler_ = errorHandler; + } +}; + + +/** + * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is + * true. + * @template T + * @param {T} condition The condition to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {T} The value of the condition. + * @throws {goog.asserts.AssertionError} When the condition evaluates to false. + */ +goog.asserts.assert = function(condition, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !condition) { + goog.asserts.doAssertFailure_( + '', null, opt_message, Array.prototype.slice.call(arguments, 2)); + } + return condition; +}; + + +/** + * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case + * when we want to add a check in the unreachable area like switch-case + * statement: + * + * <pre> + * switch(type) { + * case FOO: doSomething(); break; + * case BAR: doSomethingElse(); break; + * default: goog.asserts.fail('Unrecognized type: ' + type); + * // We have only 2 types - "default:" section is unreachable code. + * } + * </pre> + * + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} Failure. + */ +goog.asserts.fail = function(opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.errorHandler_( + new goog.asserts.AssertionError( + 'Failure' + (opt_message ? ': ' + opt_message : ''), + Array.prototype.slice.call(arguments, 1))); + } +}; + + +/** + * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {number} The value, guaranteed to be a number when asserts enabled. + * @throws {goog.asserts.AssertionError} When the value is not a number. + */ +goog.asserts.assertNumber = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) { + goog.asserts.doAssertFailure_( + 'Expected number but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {number} */ (value); +}; + + +/** + * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {string} The value, guaranteed to be a string when asserts enabled. + * @throws {goog.asserts.AssertionError} When the value is not a string. + */ +goog.asserts.assertString = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) { + goog.asserts.doAssertFailure_( + 'Expected string but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {string} */ (value); +}; + + +/** + * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Function} The value, guaranteed to be a function when asserts + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a function. + */ +goog.asserts.assertFunction = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) { + goog.asserts.doAssertFailure_( + 'Expected function but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Function} */ (value); +}; + + +/** + * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Object} The value, guaranteed to be a non-null object. + * @throws {goog.asserts.AssertionError} When the value is not an object. + */ +goog.asserts.assertObject = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) { + goog.asserts.doAssertFailure_( + 'Expected object but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Object} */ (value); +}; + + +/** + * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Array<?>} The value, guaranteed to be a non-null array. + * @throws {goog.asserts.AssertionError} When the value is not an array. + */ +goog.asserts.assertArray = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) { + goog.asserts.doAssertFailure_( + 'Expected array but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Array<?>} */ (value); +}; + + +/** + * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {boolean} The value, guaranteed to be a boolean when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a boolean. + */ +goog.asserts.assertBoolean = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) { + goog.asserts.doAssertFailure_( + 'Expected boolean but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {boolean} */ (value); +}; + + +/** + * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Element} The value, likely to be a DOM Element when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not an Element. + */ +goog.asserts.assertElement = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && + (!goog.isObject(value) || value.nodeType != goog.dom.NodeType.ELEMENT)) { + goog.asserts.doAssertFailure_( + 'Expected Element but got %s: %s.', [goog.typeOf(value), value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Element} */ (value); +}; + + +/** + * Checks if the value is an instance of the user-defined type if + * goog.asserts.ENABLE_ASSERTS is true. + * + * The compiler may tighten the type returned by this function. + * + * @param {?} value The value to check. + * @param {function(new: T, ...)} type A user-defined constructor. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} When the value is not an instance of + * type. + * @return {T} + * @template T + */ +goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) { + goog.asserts.doAssertFailure_( + 'Expected instanceof %s but got %s.', + [goog.asserts.getType_(type), goog.asserts.getType_(value)], + opt_message, Array.prototype.slice.call(arguments, 3)); + } + return value; +}; + + +/** + * Checks whether the value is a finite number, if goog.asserts.ENABLE_ASSERTS + * is true. + * + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} When the value is not a number, or is + * a non-finite number such as NaN, Infinity or -Infinity. + * @return {number} The value initially passed in. + */ +goog.asserts.assertFinite = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && + (typeof value != 'number' || !isFinite(value))) { + goog.asserts.doAssertFailure_( + 'Expected %s to be a finite number but it is not.', [value], + opt_message, Array.prototype.slice.call(arguments, 2)); + } + return /** @type {number} */ (value); +}; + +/** + * Checks that no enumerable keys are present in Object.prototype. Such keys + * would break most code that use {@code for (var ... in ...)} loops. + */ +goog.asserts.assertObjectPrototypeIsIntact = function() { + for (var key in Object.prototype) { + goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); + } +}; + + +/** + * Returns the type of a value. If a constructor is passed, and a suitable + * string cannot be found, 'unknown type name' will be returned. + * @param {*} value A constructor, object, or primitive. + * @return {string} The best display name for the value, or 'unknown type name'. + * @private + */ +goog.asserts.getType_ = function(value) { + if (value instanceof Function) { + return value.displayName || value.name || 'unknown type name'; + } else if (value instanceof Object) { + return value.constructor.displayName || value.constructor.name || + Object.prototype.toString.call(value); + } else { + return value === null ? 'null' : typeof value; + } +};
diff --git a/third_party/ink/closure/base.js b/third_party/ink/closure/base.js new file mode 100644 index 0000000..4d46cd7 --- /dev/null +++ b/third_party/ink/closure/base.js
@@ -0,0 +1,2962 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bootstrap for the Google JS Library (Closure). + * + * In uncompiled mode base.js will attempt to load Closure's deps file, unless + * the global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects + * to include their own deps file(s) from different locations. + * + * Avoid including base.js more than once. This is strictly discouraged and not + * supported. goog.require(...) won't work properly in that case. + * + * @provideGoog + */ + + +/** + * @define {boolean} Overridden to true by the compiler. + */ +var COMPILED = false; + + +/** + * Base namespace for the Closure library. Checks to see goog is already + * defined in the current scope before assigning to prevent clobbering if + * base.js is loaded more than once. + * + * @const + */ +var goog = goog || {}; + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * A hook for overriding the define values in uncompiled mode. + * + * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before + * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES}, + * {@code goog.define} will use the value instead of the default value. This + * allows flags to be overwritten without compilation (this is normally + * accomplished with the compiler's "define" flag). + * + * Example: + * <pre> + * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false}; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_UNCOMPILED_DEFINES; + + +/** + * A hook for overriding the define values in uncompiled or compiled mode, + * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In + * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence. + * + * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or + * string literals or the compiler will emit an error. + * + * While any @define value may be set, only those set with goog.define will be + * effective for uncompiled code. + * + * Example: + * <pre> + * var CLOSURE_DEFINES = {'goog.DEBUG': false} ; + * </pre> + * + * @type {Object<string, (string|number|boolean)>|undefined} + */ +goog.global.CLOSURE_DEFINES; + + +/** + * Returns true if the specified value is not undefined. + * + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + // void 0 always evaluates to undefined and hence we do not need to depend on + // the definition of the global variable named 'undefined'. + return val !== void 0; +}; + +/** + * Returns true if the specified value is a string. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a string. + */ +goog.isString = function(val) { + return typeof val == 'string'; +}; + + +/** + * Returns true if the specified value is a boolean. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is boolean. + */ +goog.isBoolean = function(val) { + return typeof val == 'boolean'; +}; + + +/** + * Returns true if the specified value is a number. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a number. + */ +goog.isNumber = function(val) { + return typeof val == 'number'; +}; + + +/** + * Builds an object structure for the provided namespace path, ensuring that + * names that already exist are not overwritten. For example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {*=} opt_object the object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is `goog.global`. + * @private + */ +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || goog.global; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part] && cur[part] !== Object.prototype[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Defines a named value. In uncompiled mode, the value is retrieved from + * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + * has the property specified, and otherwise used the defined defaultValue. + * When compiled the default can be overridden using the compiler + * options or the value set in the CLOSURE_DEFINES object. + * + * @param {string} name The distinguished name to provide. + * @param {string|number|boolean} defaultValue + */ +goog.define = function(name, defaultValue) { + var value = defaultValue; + if (!COMPILED) { + if (goog.global.CLOSURE_UNCOMPILED_DEFINES && + // Anti DOM-clobbering runtime check (b/37736576). + /** @type {?} */ (goog.global.CLOSURE_UNCOMPILED_DEFINES).nodeType === + undefined && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) { + value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name]; + } else if ( + goog.global.CLOSURE_DEFINES && + // Anti DOM-clobbering runtime check (b/37736576). + /** @type {?} */ (goog.global.CLOSURE_DEFINES).nodeType === undefined && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_DEFINES, name)) { + value = goog.global.CLOSURE_DEFINES[name]; + } + } + goog.exportPath_(name, value); +}; + + +/** + * @define {boolean} DEBUG is provided as a convenience so that debugging code + * that should not be included in a production. It can be easily stripped + * by specifying --define goog.DEBUG=false to the Closure Compiler aka + * JSCompiler. For example, most toString() methods should be declared inside an + * "if (goog.DEBUG)" conditional because they are generally used for debugging + * purposes and it is difficult for the JSCompiler to statically determine + * whether they are used. + */ +goog.define('goog.DEBUG', true); + + +/** + * @define {string} LOCALE defines the locale being used for compilation. It is + * used to select locale specific data to be compiled in js binary. BUILD rule + * can specify this value by "--define goog.LOCALE=<locale_name>" as a compiler + * option. + * + * Take into account that the locale code format is important. You should use + * the canonical Unicode format with hyphen as a delimiter. Language must be + * lowercase, Language Script - Capitalized, Region - UPPERCASE. + * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN. + * + * See more info about locale codes here: + * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers + * + * For language codes you should use values defined by ISO 693-1. See it here + * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from + * this rule: the Hebrew language. For legacy reasons the old code (iw) should + * be used instead of the new code (he). + * + * MOE:begin_intracomment_strip + * See http://g3doc/i18n/identifiers/g3doc/synonyms. + * MOE:end_intracomment_strip + */ +goog.define('goog.LOCALE', 'en'); // default to en + + +/** + * @define {boolean} Whether this code is running on trusted sites. + * + * On untrusted sites, several native functions can be defined or overridden by + * external libraries like Prototype, Datejs, and JQuery and setting this flag + * to false forces closure to use its own implementations when possible. + * + * If your JavaScript can be loaded by a third party site and you are wary about + * relying on non-standard implementations, specify + * "--define goog.TRUSTED_SITE=false" to the compiler. + */ +goog.define('goog.TRUSTED_SITE', true); + + +/** + * @define {boolean} Whether a project is expected to be running in strict mode. + * + * This define can be used to trigger alternate implementations compatible with + * running in EcmaScript Strict mode or warn about unavailable functionality. + * @see https://goo.gl/PudQ4y + * + */ +goog.define('goog.STRICT_MODE_COMPATIBLE', false); + + +/** + * @define {boolean} Whether code that calls {@link goog.setTestOnly} should + * be disallowed in the compilation unit. + */ +goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG); + + +/** + * @define {boolean} Whether to use a Chrome app CSP-compliant method for + * loading scripts via goog.require. @see appendScriptSrcNode_. + */ +goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false); + + +/** + * Defines a namespace in Closure. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * The presence of one or more goog.provide() calls in a file indicates + * that the file defines the given objects/namespaces. + * Provided symbols must not be null or undefined. + * + * In addition, goog.provide() creates the object stubs for a namespace + * (for example, goog.provide("goog.foo.bar") will create the object + * goog.foo.bar if it does not already exist). + * + * Build tools also scan for provide/require/module statements + * to discern dependencies, build dependency files (see deps.js), etc. + * + * @see goog.require + * @see goog.module + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + */ +goog.provide = function(name) { + if (goog.isInModuleLoader_()) { + throw new Error('goog.provide can not be used within a goog.module.'); + } + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw new Error('Namespace "' + name + '" already declared.'); + } + } + + goog.constructNamespace_(name); +}; + + +/** + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + * @param {Object=} opt_obj The object to embed in the namespace. + * @private + */ +goog.constructNamespace_ = function(name, opt_obj) { + if (!COMPILED) { + delete goog.implicitNamespaces_[name]; + + var namespace = name; + while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) { + if (goog.getObjectByName(namespace)) { + break; + } + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name, opt_obj); +}; + + +/** + * Module identifier validation regexp. + * Note: This is a conservative check, it is very possible to be more lenient, + * the primary exclusion here is "/" and "\" and a leading ".", these + * restrictions are intended to leave the door open for using goog.require + * with relative file paths rather than module identifiers. + * @private + */ +goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/; + + +/** + * Defines a module in Closure. + * + * Marks that this file must be loaded as a module and claims the namespace. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * goog.module() has three requirements: + * - goog.module may not be used in the same file as goog.provide. + * - goog.module must be the first statement in the file. + * - only one goog.module is allowed per file. + * + * When a goog.module annotated file is loaded, it is enclosed in + * a strict function closure. This means that: + * - any variables declared in a goog.module file are private to the file + * (not global), though the compiler is expected to inline the module. + * - The code must obey all the rules of "strict" JavaScript. + * - the file will be marked as "use strict" + * + * NOTE: unlike goog.provide, goog.module does not declare any symbols by + * itself. If declared symbols are desired, use + * goog.module.declareLegacyNamespace(). + * + * MOE:begin_intracomment_strip + * See the goog.module announcement at http://go/goog.module-announce + * MOE:end_intracomment_strip + * + * See the public goog.module proposal: http://goo.gl/Va1hin + * + * @param {string} name Namespace provided by this file in the form + * "goog.package.part", is expected but not required. + * @return {void} + */ +goog.module = function(name) { + if (!goog.isString(name) || !name || + name.search(goog.VALID_MODULE_RE_) == -1) { + throw new Error('Invalid module identifier'); + } + if (!goog.isInModuleLoader_()) { + throw new Error( + 'Module ' + name + ' has been loaded incorrectly. Note, ' + + 'modules cannot be loaded as normal scripts. They require some kind of ' + + 'pre-processing step. You\'re likely trying to load a module via a ' + + 'script tag or as a part of a concatenated bundle without rewriting the ' + + 'module. For more info see: ' + + 'https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.'); + } + if (goog.moduleLoaderState_.moduleName) { + throw new Error('goog.module may only be called once per module.'); + } + + // Store the module name for the loader. + goog.moduleLoaderState_.moduleName = name; + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw new Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + } +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * + * Note: This is not an alternative to goog.require, it does not + * indicate a hard dependency, instead it is used to indicate + * an optional dependency or to access the exports of a module + * that has already been loaded. + * @suppress {missingProvide} + */ +goog.module.get = function(name) { + return goog.module.getInternal_(name); +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * @private + */ +goog.module.getInternal_ = function(name) { + if (!COMPILED) { + if (name in goog.loadedModules_) { + return goog.loadedModules_[name]; + } else if (!goog.implicitNamespaces_[name]) { + var ns = goog.getObjectByName(name); + return ns != null ? ns : null; + } + } + return null; +}; + + +/** + * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}} + */ +goog.moduleLoaderState_ = null; + + +/** + * @private + * @return {boolean} Whether a goog.module is currently being initialized. + */ +goog.isInModuleLoader_ = function() { + return goog.moduleLoaderState_ != null; +}; + + +/** + * Provide the module's exports as a globally accessible object under the + * module's declared name. This is intended to ease migration to goog.module + * for files that have existing usages. + * @suppress {missingProvide} + */ +goog.module.declareLegacyNamespace = function() { + if (!COMPILED && !goog.isInModuleLoader_()) { + throw new Error( + 'goog.module.declareLegacyNamespace must be called from ' + + 'within a goog.module'); + } + if (!COMPILED && !goog.moduleLoaderState_.moduleName) { + throw new Error( + 'goog.module must be called prior to ' + + 'goog.module.declareLegacyNamespace.'); + } + goog.moduleLoaderState_.declareLegacyNamespace = true; +}; + + +/** + * Marks that the current file should only be used for testing, and never for + * live code in production. + * + * In the case of unit tests, the message may optionally be an exact namespace + * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + * provide (if not explicitly defined in the code). + * + * @param {string=} opt_message Optional message to add to the error that's + * raised when used in production code. + */ +goog.setTestOnly = function(opt_message) { + if (goog.DISALLOW_TEST_ONLY_CODE) { + opt_message = opt_message || ''; + throw new Error( + 'Importing test-only code into non-debug environment' + + (opt_message ? ': ' + opt_message : '.')); + } +}; + + +/** + * Forward declares a symbol. This is an indication to the compiler that the + * symbol may be used in the source yet is not required and may not be provided + * in compilation. + * + * The most common usage of forward declaration is code that takes a type as a + * function parameter but does not need to require it. By forward declaring + * instead of requiring, no hard dependency is made, and (if not required + * elsewhere) the namespace may never be required and thus, not be pulled + * into the JavaScript binary. If it is required elsewhere, it will be type + * checked as normal. + * + * Before using goog.forwardDeclare, please read the documentation at + * https://github.com/google/closure-compiler/wiki/Bad-Type-Annotation to + * understand the options and tradeoffs when working with forward declarations. + * + * @param {string} name The namespace to forward declare in the form of + * "goog.package.part". + */ +goog.forwardDeclare = function(name) {}; + + +/** + * Forward declare type information. Used to assign types to goog.global + * referenced object that would otherwise result in unknown type references + * and thus block property disambiguation. + */ +goog.forwardDeclare('Document'); +goog.forwardDeclare('HTMLScriptElement'); +goog.forwardDeclare('XMLHttpRequest'); + + +if (!COMPILED) { + /** + * Check if the given name has been goog.provided. This will return false for + * names that are available only as implicit namespaces. + * @param {string} name name of the object to look for. + * @return {boolean} Whether the name has been provided. + * @private + */ + goog.isProvided_ = function(name) { + return (name in goog.loadedModules_) || + (!goog.implicitNamespaces_[name] && + goog.isDefAndNotNull(goog.getObjectByName(name))); + }; + + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares that 'goog' and + * 'goog.events' must be namespaces. + * + * @type {!Object<string, (boolean|undefined)>} + * @private + */ + goog.implicitNamespaces_ = {'goog.module': true}; + + // NOTE: We add goog.module as an implicit namespace as goog.module is defined + // here and because the existing module package has not been moved yet out of + // the goog.module namespace. This satisifies both the debug loader and + // ahead-of-time dependency management. +} + + +/** + * Returns an object based on its fully qualified external name. The object + * is not found if null or undefined. If you are using a compilation pass that + * renames property names beware that using this function will not find renamed + * properties. + * + * @param {string} name The fully qualified name. + * @param {Object=} opt_obj The object within which to look; default is + * |goog.global|. + * @return {?} The value (object or primitive) or, if not found, null. + */ +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split('.'); + var cur = opt_obj || goog.global; + for (var i = 0; i < parts.length; i++) { + cur = cur[parts[i]]; + if (!goog.isDefAndNotNull(cur)) { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {!Object} obj The namespace to globalize. + * @param {Object=} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {!Array<string>} provides An array of strings with + * the names of the objects this file provides. + * @param {!Array<string>} requires An array of strings with + * the names of the objects this file requires. + * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating + * how the file must be loaded. The boolean 'true' is equivalent + * to {'module': 'goog'} for backwards-compatibility. Valid properties + * and values include {'module': 'goog'} and {'lang': 'es6'}. + */ +goog.addDependency = function(relPath, provides, requires, opt_loadFlags) { + if (goog.DEPENDENCIES_ENABLED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') { + opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {}; + } + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + deps.loadFlags[path] = opt_loadFlags; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + +// MOE:begin_strip +/** + * Whether goog.require should throw an exception if it fails. + * @type {boolean} + */ +goog.useStrictRequires = false; + + +// MOE:end_strip + + +// NOTE(nnaze): The debug DOM loader was included in base.js as an original way +// to do "debug-mode" development. The dependency system can sometimes be +// confusing, as can the debug DOM loader's asynchronous nature. +// +// With the DOM loader, a call to goog.require() is not blocking -- the script +// will not load until some point after the current script. If a namespace is +// needed at runtime, it needs to be defined in a previous script, or loaded via +// require() with its registered dependencies. +// +// User-defined namespaces may need their own deps file. For a reference on +// creating a deps file, see: +// MOE:begin_strip +// Internally: http://go/deps-files and http://go/be#js_deps +// MOE:end_strip +// Externally: https://developers.google.com/closure/library/docs/depswriter +// +// Because of legacy clients, the DOM loader can't be easily removed from +// base.js. Work was done to make it disableable or replaceable for +// different environments (DOM-less JavaScript interpreters like Rhino or V8, +// for example). See bootstrap/ for more information. + + +/** + * @define {boolean} Whether to enable the debug loader. + * + * If enabled, a call to goog.require() will attempt to load the namespace by + * appending a script tag to the DOM (if the namespace has been registered). + * + * If disabled, goog.require() will simply assert that the namespace has been + * provided (and depend on the fact that some outside tool correctly ordered + * the script). + */ +goog.define('goog.ENABLE_DEBUG_LOADER', true); + + +/** + * @param {string} msg + * @private + */ +goog.logToConsole_ = function(msg) { + if (goog.global.console) { + goog.global.console['error'](msg); + } +}; + + +/** + * Implements a system for the dynamic resolution of dependencies that works in + * parallel with the BUILD system. Note that all calls to goog.require will be + * stripped by the compiler. + * @see goog.provide + * @param {string} name Namespace to include (as was given in goog.provide()) in + * the form "goog.package.part". + * @return {?} If called within a goog.module file, the associated namespace or + * module otherwise null. + */ +goog.require = function(name) { + // If the object already exists we do not need to do anything. + if (!COMPILED) { + if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) { + goog.maybeProcessDeferredDep_(name); + } + + if (goog.isProvided_(name)) { + if (goog.isInModuleLoader_()) { + return goog.module.getInternal_(name); + } + } else if (goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if (path) { + goog.writeScripts_(path); + } else { + var errorMessage = 'goog.require could not find: ' + name; + goog.logToConsole_(errorMessage); + + // MOE:begin_strip + + // NOTE(nicksantos): We could always throw an error, but this would + // break legacy users that depended on this failing silently. Instead, + // the compiler should warn us when there are invalid goog.require + // calls. For now, we simply give clients a way to turn strict mode on. + if (goog.useStrictRequires) { + throw new Error(errorMessage); + } + + // In external Closure, always error. + // MOE:end_strip_and_replace throw new Error(errorMessage); + } + } + + return null; + } +}; + + +/** + * Path for included scripts. + * @type {string} + */ +goog.basePath = ''; + + +/** + * A hook for overriding the base path. + * @type {string|undefined} + */ +goog.global.CLOSURE_BASE_PATH; + + +/** + * Whether to attempt to load Closure's deps file. By default, when uncompiled, + * deps files will attempt to be loaded. + * @type {boolean|undefined} + */ +goog.global.CLOSURE_NO_DEPS; + + +/** + * A function to import a single script. This is meant to be overridden when + * Closure is being run in non-HTML contexts, such as web workers. It's defined + * in the global scope so that it can be set before base.js is loaded, which + * allows deps.js to be imported properly. + * + * The function is passed the script source, which is a relative URI. It should + * return true if the script was imported, false otherwise. + * @type {(function(string): boolean)|undefined} + */ +goog.global.CLOSURE_IMPORT_SCRIPT; + + +/** + * Null function used for default values of callbacks, etc. + * @return {void} Nothing. + */ +goog.nullFunction = function() {}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error will be thrown + * when bar() is invoked. + * + * @type {!Function} + * @throws {Error} when invoked to indicate the method should be overridden. + */ +goog.abstractMethod = function() { + throw new Error('unimplemented abstract method'); +}; + + +/** + * Adds a {@code getInstance} static method that always returns the same + * instance object. + * @param {!Function} ctor The constructor for the class to add the static + * method to. + */ +goog.addSingletonGetter = function(ctor) { + // instance_ is immediately set to prevent issues with sealed constructors + // such as are encountered when a constructor is returned as the export object + // of a goog.module in unoptimized code. + ctor.instance_ = undefined; + ctor.getInstance = function() { + if (ctor.instance_) { + return ctor.instance_; + } + if (goog.DEBUG) { + // NOTE: JSCompiler can't optimize away Array#push. + goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; + } + return ctor.instance_ = new ctor; + }; +}; + + +/** + * All singleton classes that have been instantiated, for testing. Don't read + * it directly, use the {@code goog.testing.singleton} module. The compiler + * removes this variable if unused. + * @type {!Array<!Function>} + * @private + */ +goog.instantiatedSingletons_ = []; + + +/** + * @define {boolean} Whether to load goog.modules using {@code eval} when using + * the debug loader. This provides a better debugging experience as the + * source is unmodified and can be edited using Chrome Workspaces or similar. + * However in some environments the use of {@code eval} is banned + * so we provide an alternative. + */ +goog.define('goog.LOAD_MODULE_USING_EVAL', true); + + +/** + * @define {boolean} Whether the exports of goog.modules should be sealed when + * possible. + */ +goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG); + + +/** + * The registry of initialized modules: + * the module identifier to module exports map. + * @private @const {!Object<string, ?>} + */ +goog.loadedModules_ = {}; + + +/** + * True if goog.dependencies_ is available. + * @const {boolean} + */ +goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; + + +/** + * @define {string} How to decide whether to transpile. Valid values + * are 'always', 'never', and 'detect'. The default ('detect') is to + * use feature detection to determine which language levels need + * transpilation. + */ +// NOTE(sdh): we could expand this to accept a language level to bypass +// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but +// would leave ES3 and ES5 files alone. +goog.define('goog.TRANSPILE', 'detect'); + + +/** + * @define {string} Path to the transpiler. Executing the script at this + * path (relative to base.js) should define a function $jscomp.transpile. + */ +goog.define('goog.TRANSPILER', 'transpile.js'); + + +if (goog.DEPENDENCIES_ENABLED) { + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts. + * @private + * @type {{ + * loadFlags: !Object<string, !Object<string, string>>, + * nameToPath: !Object<string, string>, + * requires: !Object<string, !Object<string, boolean>>, + * visited: !Object<string, boolean>, + * written: !Object<string, boolean>, + * deferred: !Object<string, string> + * }} + */ + goog.dependencies_ = { + loadFlags: {}, // 1 to 1 + + nameToPath: {}, // 1 to 1 + + requires: {}, // 1 to many + + // Used when resolving dependencies to prevent us from visiting file twice. + visited: {}, + + written: {}, // Used to keep track of script files we have written. + + deferred: {} // Used to track deferred module evaluations in old IEs + }; + + + /** + * Tries to detect whether is in the context of an HTML document. + * @return {boolean} True if it looks like HTML document. + * @private + */ + goog.inHtmlDocument_ = function() { + /** @type {Document} */ + var doc = goog.global.document; + return doc != null && 'write' in doc; // XULDocument misses write. + }; + + + /** + * Tries to detect the base path of base.js script that bootstraps Closure. + * @private + */ + goog.findBasePath_ = function() { + if (goog.isDef(goog.global.CLOSURE_BASE_PATH) && + // Anti DOM-clobbering runtime check (b/37736576). + goog.isString(goog.global.CLOSURE_BASE_PATH)) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return; + } else if (!goog.inHtmlDocument_()) { + return; + } + /** @type {Document} */ + var doc = goog.global.document; + // If we have a currentScript available, use it exclusively. + var currentScript = doc.currentScript; + if (currentScript) { + var scripts = [currentScript]; + } else { + var scripts = doc.getElementsByTagName('SCRIPT'); + } + // Search backwards since the current script is in almost all cases the one + // that has base.js. + for (var i = scripts.length - 1; i >= 0; --i) { + var script = /** @type {!HTMLScriptElement} */ (scripts[i]); + var src = script.src; + var qmark = src.lastIndexOf('?'); + var l = qmark == -1 ? src.length : qmark; + if (src.substr(l - 7, 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Imports a script if, and only if, that script hasn't already been imported. + * (Must be called at execution time) + * @param {string} src Script source. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @private + */ + goog.importScript_ = function(src, opt_sourceText) { + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + if (importScript(src, opt_sourceText)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** + * Whether the browser is IE9 or earlier, which needs special handling + * for deferred modules. + * @const @private {boolean} + */ + goog.IS_OLD_IE_ = + !!(!goog.global.atob && goog.global.document && goog.global.document.all); + + + /** + * Whether IE9 or earlier is waiting on a dependency. This ensures that + * deferred modules that have no non-deferred dependencies actually get + * loaded, since if we defer them and then never pull in a non-deferred + * script, then `goog.loadQueuedModules_` will never be called. Instead, + * if not waiting on anything we simply don't defer in the first place. + * @private {boolean} + */ + goog.oldIeWaiting_ = false; + + + /** + * Given a URL initiate retrieval and execution of a script that needs + * pre-processing. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether this is a goog.module. + * @param {boolean} needsTranspile Whether this source needs transpilation. + * @private + */ + goog.importProcessedScript_ = function(src, isModule, needsTranspile) { + // In an attempt to keep browsers from timing out loading scripts using + // synchronous XHRs, put each load in its own script block. + var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' + + needsTranspile + ');'; + + goog.importScript_('', bootstrap); + }; + + + /** @private {!Array<string>} */ + goog.queuedModules_ = []; + + + /** + * Return an appropriate module text. Suitable to insert into + * a script tag (that is unescaped). + * @param {string} srcUrl + * @param {string} scriptText + * @return {string} + * @private + */ + goog.wrapModule_ = function(srcUrl, scriptText) { + if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) { + return '' + + 'goog.loadModule(function(exports) {' + + '"use strict";' + scriptText + + '\n' + // terminate any trailing single line comment. + ';return exports' + + '});' + + '\n//# sourceURL=' + srcUrl + '\n'; + } else { + return '' + + 'goog.loadModule(' + + goog.global.JSON.stringify( + scriptText + '\n//# sourceURL=' + srcUrl + '\n') + + ');'; + } + }; + + // On IE9 and earlier, it is necessary to handle + // deferred module loads. In later browsers, the + // code to be evaluated is simply inserted as a script + // block in the correct order. To eval deferred + // code at the right time, we piggy back on goog.require to call + // goog.maybeProcessDeferredDep_. + // + // The goog.requires are used both to bootstrap + // the loading process (when no deps are available) and + // declare that they should be available. + // + // Here we eval the sources, if all the deps are available + // either already eval'd or goog.require'd. This will + // be the case when all the dependencies have already + // been loaded, and the dependent module is loaded. + // + // But this alone isn't sufficient because it is also + // necessary to handle the case where there is no root + // that is not deferred. For that there we register for an event + // and trigger goog.loadQueuedModules_ handle any remaining deferred + // evaluations. + + /** + * Handle any remaining deferred goog.module evals. + * @private + */ + goog.loadQueuedModules_ = function() { + var count = goog.queuedModules_.length; + if (count > 0) { + var queue = goog.queuedModules_; + goog.queuedModules_ = []; + for (var i = 0; i < count; i++) { + var path = queue[i]; + goog.maybeProcessDeferredPath_(path); + } + } + goog.oldIeWaiting_ = false; + }; + + + /** + * Eval the named module if its dependencies are + * available. + * @param {string} name The module to load. + * @private + */ + goog.maybeProcessDeferredDep_ = function(name) { + if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) { + var path = goog.getPathFromDeps_(name); + goog.maybeProcessDeferredPath_(goog.basePath + path); + } + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose evaluation has been deferred. + * @private + */ + goog.isDeferredModule_ = function(name) { + var path = goog.getPathFromDeps_(name); + var loadFlags = path && goog.dependencies_.loadFlags[path] || {}; + var languageLevel = loadFlags['lang'] || 'es3'; + if (path && (loadFlags['module'] == 'goog' || + goog.needsTranspile_(languageLevel))) { + var abspath = goog.basePath + path; + return (abspath) in goog.dependencies_.deferred; + } + return false; + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose declared dependencies have all been loaded + * (eval'd or a deferred module load) + * @private + */ + goog.allDepsAreAvailable_ = function(name) { + var path = goog.getPathFromDeps_(name); + if (path && (path in goog.dependencies_.requires)) { + for (var requireName in goog.dependencies_.requires[path]) { + if (!goog.isProvided_(requireName) && + !goog.isDeferredModule_(requireName)) { + return false; + } + } + } + return true; + }; + + + /** + * @param {string} abspath + * @private + */ + goog.maybeProcessDeferredPath_ = function(abspath) { + if (abspath in goog.dependencies_.deferred) { + var src = goog.dependencies_.deferred[abspath]; + delete goog.dependencies_.deferred[abspath]; + goog.globalEval(src); + } + }; + + + /** + * Load a goog.module from the provided URL. This is not a general purpose + * code loader and does not support late loading code, that is it should only + * be used during page load. This method exists to support unit tests and + * "debug" loaders that would otherwise have inserted script tags. Under the + * hood this needs to use a synchronous XHR and is not recommeneded for + * production code. + * + * The module's goog.requires must have already been satisified; an exception + * will be thrown if this is not the case. This assumption is that no + * "deps.js" file exists, so there is no way to discover and locate the + * module-to-be-loaded's dependencies and no attempt is made to do so. + * + * There should only be one attempt to load a module. If + * "goog.loadModuleFromUrl" is called for an already loaded module, an + * exception will be throw. + * + * @param {string} url The URL from which to attempt to load the goog.module. + */ + goog.loadModuleFromUrl = function(url) { + // Because this executes synchronously, we don't need to do any additional + // bookkeeping. When "goog.loadModule" the namespace will be marked as + // having been provided which is sufficient. + goog.retrieveAndExec_(url, true, false); + }; + + + /** + * Writes a new script pointing to {@code src} directly into the DOM. + * + * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for + * the fallback mechanism. + * + * @param {string} src The script URL. + * @private + */ + goog.writeScriptSrcNode_ = function(src) { + goog.global.document.write( + '<script type="text/javascript" src="' + src + '"></' + + 'script>'); + }; + + + /** + * Appends a new script node to the DOM using a CSP-compliant mechanism. This + * method exists as a fallback for document.write (which is not allowed in a + * strict CSP context, e.g., Chrome apps). + * + * NOTE: This method is not analogous to using document.write to insert a + * <script> tag; specifically, the user agent will execute a script added by + * document.write immediately after the current script block finishes + * executing, whereas the DOM-appended script node will not be executed until + * the entire document is parsed and executed. That is to say, this script is + * added to the end of the script execution queue. + * + * The page must not attempt to call goog.required entities until after the + * document has loaded, e.g., in or after the window.onload callback. + * + * @param {string} src The script URL. + * @private + */ + goog.appendScriptSrcNode_ = function(src) { + /** @type {Document} */ + var doc = goog.global.document; + var scriptEl = + /** @type {HTMLScriptElement} */ (doc.createElement('script')); + scriptEl.type = 'text/javascript'; + scriptEl.src = src; + scriptEl.defer = false; + scriptEl.async = false; + doc.head.appendChild(scriptEl); + }; + + + /** + * The default implementation of the import function. Writes a script tag to + * import the script. + * + * @param {string} src The script url. + * @param {string=} opt_sourceText The optionally source text to evaluate + * @return {boolean} True if the script was imported, false otherwise. + * @private + */ + goog.writeScriptTag_ = function(src, opt_sourceText) { + if (goog.inHtmlDocument_()) { + /** @type {!HTMLDocument} */ + var doc = goog.global.document; + + // If the user tries to require a new symbol after document load, + // something has gone terribly wrong. Doing a document.write would + // wipe out the page. This does not apply to the CSP-compliant method + // of writing script tags. + if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING && + doc.readyState == 'complete') { + // Certain test frameworks load base.js multiple times, which tries + // to write deps.js each time. If that happens, just fail silently. + // These frameworks wipe the page between each load of base.js, so this + // is OK. + var isDeps = /\bdeps.js$/.test(src); + if (isDeps) { + return false; + } else { + throw new Error('Cannot write "' + src + '" after document load'); + } + } + + if (opt_sourceText === undefined) { + if (!goog.IS_OLD_IE_) { + if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) { + goog.appendScriptSrcNode_(src); + } else { + goog.writeScriptSrcNode_(src); + } + } else { + goog.oldIeWaiting_ = true; + var state = ' onreadystatechange=\'goog.onScriptLoad_(this, ' + + ++goog.lastNonModuleScriptIndex_ + ')\' '; + doc.write( + '<script type="text/javascript" src="' + src + '"' + state + + '></' + + 'script>'); + } + } else { + doc.write( + '<script type="text/javascript">' + + goog.protectScriptTag_(opt_sourceText) + '</' + + 'script>'); + } + return true; + } else { + return false; + } + }; + + /** + * Rewrites closing script tags in input to avoid ending an enclosing script + * tag. + * + * @param {string} str + * @return {string} + * @private + */ + goog.protectScriptTag_ = function(str) { + return str.replace(/<\/(SCRIPT)/ig, '\\x3c/$1'); + }; + + /** + * Determines whether the given language needs to be transpiled. + * @param {string} lang + * @return {boolean} + * @private + */ + goog.needsTranspile_ = function(lang) { + if (goog.TRANSPILE == 'always') { + return true; + } else if (goog.TRANSPILE == 'never') { + return false; + } else if (!goog.requiresTranspilation_) { + goog.requiresTranspilation_ = goog.createRequiresTranspilation_(); + } + if (lang in goog.requiresTranspilation_) { + return goog.requiresTranspilation_[lang]; + } else { + throw new Error('Unknown language mode: ' + lang); + } + }; + + /** @private {?Object<string, boolean>} */ + goog.requiresTranspilation_ = null; + + + /** @private {number} */ + goog.lastNonModuleScriptIndex_ = 0; + + + /** + * A readystatechange handler for legacy IE + * @param {?} script + * @param {number} scriptIndex + * @return {boolean} + * @private + */ + goog.onScriptLoad_ = function(script, scriptIndex) { + // for now load the modules when we reach the last script, + // later allow more inter-mingling. + if (script.readyState == 'complete' && + goog.lastNonModuleScriptIndex_ == scriptIndex) { + goog.loadQueuedModules_(); + } + return true; + }; + + /** + * Resolves dependencies based on the dependencies added using addDependency + * and calls importScript_ in the correct order. + * @param {string} pathToLoad The path from which to start discovering + * dependencies. + * @private + */ + goog.writeScripts_ = function(pathToLoad) { + /** @type {!Array<string>} The scripts we need to write this time. */ + var scripts = []; + var seenScript = {}; + var deps = goog.dependencies_; + + /** @param {string} path */ + function visitNode(path) { + if (path in deps.written) { + return; + } + + // We have already visited this one. We can get here if we have cyclic + // dependencies. + if (path in deps.visited) { + return; + } + + deps.visited[path] = true; + + if (path in deps.requires) { + for (var requireName in deps.requires[path]) { + // If the required name is defined, we assume that it was already + // bootstrapped by other means. + if (!goog.isProvided_(requireName)) { + if (requireName in deps.nameToPath) { + visitNode(deps.nameToPath[requireName]); + } else { + throw new Error('Undefined nameToPath for ' + requireName); + } + } + } + } + + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + } + + visitNode(pathToLoad); + + // record that we are going to load all these scripts. + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + goog.dependencies_.written[path] = true; + } + + // If a module is loaded synchronously then we need to + // clear the current inModuleLoader value, and restore it when we are + // done loading the current "requires". + var moduleState = goog.moduleLoaderState_; + goog.moduleLoaderState_ = null; + + for (var i = 0; i < scripts.length; i++) { + var path = scripts[i]; + if (path) { + var loadFlags = deps.loadFlags[path] || {}; + var languageLevel = loadFlags['lang'] || 'es3'; + var needsTranspile = goog.needsTranspile_(languageLevel); + if (loadFlags['module'] == 'goog' || needsTranspile) { + goog.importProcessedScript_( + goog.basePath + path, loadFlags['module'] == 'goog', + needsTranspile); + } else { + goog.importScript_(goog.basePath + path); + } + } else { + goog.moduleLoaderState_ = moduleState; + throw new Error('Undefined script input'); + } + } + + // restore the current "module loading state" + goog.moduleLoaderState_ = moduleState; + }; + + + /** + * Looks at the dependency rules and tries to determine the script file that + * fulfills a particular rule. + * @param {string} rule In the form goog.namespace.Class or project.script. + * @return {?string} Url corresponding to the rule, or null. + * @private + */ + goog.getPathFromDeps_ = function(rule) { + if (rule in goog.dependencies_.nameToPath) { + return goog.dependencies_.nameToPath[rule]; + } else { + return null; + } + }; + + goog.findBasePath_(); + + // Allow projects to manage the deps files themselves. + if (!goog.global.CLOSURE_NO_DEPS) { + goog.importScript_(goog.basePath + 'deps.js'); + } +} + + +/** + * @package {?boolean} + * Visible for testing. + */ +goog.hasBadLetScoping = null; + + +/** + * @return {boolean} + * @package Visible for testing. + */ +goog.useSafari10Workaround = function() { + if (goog.hasBadLetScoping == null) { + var hasBadLetScoping; + try { + hasBadLetScoping = !eval( + '"use strict";' + + 'let x = 1; function f() { return typeof x; };' + + 'f() == "number";'); + } catch (e) { + // Assume that ES6 syntax isn't supported. + hasBadLetScoping = false; + } + goog.hasBadLetScoping = hasBadLetScoping; + } + return goog.hasBadLetScoping; +}; + + +/** + * @param {string} moduleDef + * @return {string} + * @package Visible for testing. + */ +goog.workaroundSafari10EvalBug = function(moduleDef) { + return '(function(){' + moduleDef + + '\n' + // Terminate any trailing single line comment. + ';' + // Terminate any trailing expression. + '})();\n'; +}; + + +/** + * @param {function(?):?|string} moduleDef The module definition. + */ +goog.loadModule = function(moduleDef) { + // NOTE: we allow function definitions to be either in the from + // of a string to eval (which keeps the original source intact) or + // in a eval forbidden environment (CSP) we allow a function definition + // which in its body must call {@code goog.module}, and return the exports + // of the module. + var previousState = goog.moduleLoaderState_; + try { + goog.moduleLoaderState_ = { + moduleName: undefined, + declareLegacyNamespace: false + }; + var exports; + if (goog.isFunction(moduleDef)) { + exports = moduleDef.call(undefined, {}); + } else if (goog.isString(moduleDef)) { + if (goog.useSafari10Workaround()) { + moduleDef = goog.workaroundSafari10EvalBug(moduleDef); + } + + exports = goog.loadModuleFromSource_.call(undefined, moduleDef); + } else { + throw new Error('Invalid module definition'); + } + + var moduleName = goog.moduleLoaderState_.moduleName; + if (!goog.isString(moduleName) || !moduleName) { + throw new Error('Invalid module name \"' + moduleName + '\"'); + } + + // Don't seal legacy namespaces as they may be uses as a parent of + // another namespace + if (goog.moduleLoaderState_.declareLegacyNamespace) { + goog.constructNamespace_(moduleName, exports); + } else if ( + goog.SEAL_MODULE_EXPORTS && Object.seal && typeof exports == 'object' && + exports != null) { + Object.seal(exports); + } + + goog.loadedModules_[moduleName] = exports; + } finally { + goog.moduleLoaderState_ = previousState; + } +}; + + +/** + * @private @const + */ +goog.loadModuleFromSource_ = /** @type {function(string):?} */ (function() { + // NOTE: we avoid declaring parameters or local variables here to avoid + // masking globals or leaking values into the module definition. + 'use strict'; + var exports = {}; + eval(arguments[0]); + return exports; +}); + + +/** + * Normalize a file path by removing redundant ".." and extraneous "." file + * path components. + * @param {string} path + * @return {string} + * @private + */ +goog.normalizePath_ = function(path) { + var components = path.split('/'); + var i = 0; + while (i < components.length) { + if (components[i] == '.') { + components.splice(i, 1); + } else if ( + i && components[i] == '..' && components[i - 1] && + components[i - 1] != '..') { + components.splice(--i, 2); + } else { + i++; + } + } + return components.join('/'); +}; + + +/** + * Provides a hook for loading a file when using Closure's goog.require() API + * with goog.modules. In particular this hook is provided to support Node.js. + * + * @type {(function(string):string)|undefined} + */ +goog.global.CLOSURE_LOAD_FILE_SYNC; + + +/** + * Loads file by synchronous XHR. Should not be used in production environments. + * @param {string} src Source URL. + * @return {?string} File contents, or null if load failed. + * @private + */ +goog.loadFileSync_ = function(src) { + if (goog.global.CLOSURE_LOAD_FILE_SYNC) { + return goog.global.CLOSURE_LOAD_FILE_SYNC(src); + } else { + try { + /** @type {XMLHttpRequest} */ + var xhr = new goog.global['XMLHttpRequest'](); + xhr.open('get', src, false); + xhr.send(); + // NOTE: Successful http: requests have a status of 200, but successful + // file: requests may have a status of zero. Any other status, or a + // thrown exception (particularly in case of file: requests) indicates + // some sort of error, which we treat as a missing or unavailable file. + return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null; + } catch (err) { + // No need to rethrow or log, since errors should show up on their own. + return null; + } + } +}; + + +/** + * Retrieve and execute a script that needs some sort of wrapping. + * @param {string} src Script source URL. + * @param {boolean} isModule Whether to load as a module. + * @param {boolean} needsTranspile Whether to transpile down to ES3. + * @private + */ +goog.retrieveAndExec_ = function(src, isModule, needsTranspile) { + if (!COMPILED) { + // The full but non-canonicalized URL for later use. + var originalPath = src; + // Canonicalize the path, removing any /./ or /../ since Chrome's debugging + // console doesn't auto-canonicalize XHR loads as it does <script> srcs. + src = goog.normalizePath_(src); + + var importScript = + goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + + var scriptText = goog.loadFileSync_(src); + if (scriptText == null) { + throw new Error('Load of "' + src + '" failed'); + } + + if (needsTranspile) { + scriptText = goog.transpile_.call(goog.global, scriptText, src); + } + + if (isModule) { + scriptText = goog.wrapModule_(src, scriptText); + } else { + scriptText += '\n//# sourceURL=' + src; + } + var isOldIE = goog.IS_OLD_IE_; + if (isOldIE && goog.oldIeWaiting_) { + goog.dependencies_.deferred[originalPath] = scriptText; + goog.queuedModules_.push(originalPath); + } else { + importScript(src, scriptText); + } + } +}; + + +/** + * Lazily retrieves the transpiler and applies it to the source. + * @param {string} code JS code. + * @param {string} path Path to the code. + * @return {string} The transpiled code. + * @private + */ +goog.transpile_ = function(code, path) { + var jscomp = goog.global['$jscomp']; + if (!jscomp) { + goog.global['$jscomp'] = jscomp = {}; + } + var transpile = jscomp.transpile; + if (!transpile) { + var transpilerPath = goog.basePath + goog.TRANSPILER; + var transpilerCode = goog.loadFileSync_(transpilerPath); + if (transpilerCode) { + // This must be executed synchronously, since by the time we know we + // need it, we're about to load and write the ES6 code synchronously, + // so a normal script-tag load will be too slow. + eval(transpilerCode + '\n//# sourceURL=' + transpilerPath); + // Even though the transpiler is optional, if $gwtExport is found, it's + // a sign the transpiler was loaded and the $jscomp.transpile *should* + // be there. + if (goog.global['$gwtExport'] && goog.global['$gwtExport']['$jscomp'] && + !goog.global['$gwtExport']['$jscomp']['transpile']) { + throw new Error( + 'The transpiler did not properly export the "transpile" ' + + 'method. $gwtExport: ' + JSON.stringify(goog.global['$gwtExport'])); + } + // transpile.js only exports a single $jscomp function, transpile. We + // grab just that and add it to the existing definition of $jscomp which + // contains the polyfills. + goog.global['$jscomp'].transpile = + goog.global['$gwtExport']['$jscomp']['transpile']; + jscomp = goog.global['$jscomp']; + transpile = jscomp.transpile; + } + } + if (!transpile) { + // The transpiler is an optional component. If it's not available then + // replace it with a pass-through function that simply logs. + var suffix = ' requires transpilation but no transpiler was found.'; + // MOE:begin_strip + suffix += // Provide a more appropriate message internally. + ' Please add "//javascript/closure:transpiler" as a data ' + + 'dependency to ensure it is included.'; + // MOE:end_strip + transpile = jscomp.transpile = function(code, path) { + // TODO(sdh): figure out some way to get this error to show up + // in test results, noting that the failure may occur in many + // different ways, including in loadModule() before the test + // runner even comes up. + goog.logToConsole_(path + suffix); + return code; + }; + } + // Note: any transpilation errors/warnings will be logged to the console. + return transpile(code, path); +}; + + +//============================================================================== +// Language Enhancements +//============================================================================== + + +/** + * This is a "fixed" version of the typeof operator. It differs from the typeof + * operator in such a way that null returns 'null' and arrays return 'array'. + * @param {?} value The value to get the type of. + * @return {string} The name of the type. + */ +goog.typeOf = function(value) { + var s = typeof value; + if (s == 'object') { + if (value) { + // Check these first, so we can avoid calling Object.prototype.toString if + // possible. + // + // IE improperly marshals typeof across execution contexts, but a + // cross-context object will still return false for "instanceof Object". + if (value instanceof Array) { + return 'array'; + } else if (value instanceof Object) { + return s; + } + + // HACK: In order to use an Object prototype method on the arbitrary + // value, the compiler requires the value be cast to type Object, + // even though the ECMA spec explicitly allows it. + var className = Object.prototype.toString.call( + /** @type {!Object} */ (value)); + // In Firefox 3.6, attempting to access iframe window objects' length + // property throws an NS_ERROR_FAILURE, so we need to special-case it + // here. + if (className == '[object Window]') { + return 'object'; + } + + // We cannot always use constructor == Array or instanceof Array because + // different frames have different Array objects. In IE6, if the iframe + // where the array was created is destroyed, the array loses its + // prototype. Then dereferencing val.splice here throws an exception, so + // we can't use goog.isFunction. Calling typeof directly returns 'unknown' + // so that will work. In this case, this function will return false and + // most array functions will still work because the array is still + // array-like (supports length and []) even though it has lost its + // prototype. + // Mark Miller noticed that Object.prototype.toString + // allows access to the unforgeable [[Class]] property. + // 15.2.4.2 Object.prototype.toString ( ) + // When the toString method is called, the following steps are taken: + // 1. Get the [[Class]] property of this object. + // 2. Compute a string value by concatenating the three strings + // "[object ", Result(1), and "]". + // 3. Return Result(2). + // and this behavior survives the destruction of the execution context. + if ((className == '[object Array]' || + // In IE all non value types are wrapped as objects across window + // boundaries (not iframe though) so we have to do object detection + // for this edge case. + typeof value.length == 'number' && + typeof value.splice != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('splice') + + )) { + return 'array'; + } + // HACK: There is still an array case that fails. + // function ArrayImpostor() {} + // ArrayImpostor.prototype = []; + // var impostor = new ArrayImpostor; + // this can be fixed by getting rid of the fast path + // (value instanceof Array) and solely relying on + // (value && Object.prototype.toString.vall(value) === '[object Array]') + // but that would require many more function calls and is not warranted + // unless closure code is receiving objects from untrusted sources. + + // IE in cross-window calls does not correctly marshal the function type + // (it appears just as an object) so we cannot use just typeof val == + // 'function'. However, if the object has a call property, it is a + // function. + if ((className == '[object Function]' || + typeof value.call != 'undefined' && + typeof value.propertyIsEnumerable != 'undefined' && + !value.propertyIsEnumerable('call'))) { + return 'function'; + } + + } else { + return 'null'; + } + + } else if (s == 'function' && typeof value.call == 'undefined') { + // In Safari typeof nodeList returns 'function', and on Firefox typeof + // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We + // would like to return object for those and we can detect an invalid + // function by making sure that the function object has a call method. + return 'object'; + } + return s; +}; + + +/** + * Returns true if the specified value is null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is null. + */ +goog.isNull = function(val) { + return val === null; +}; + + +/** + * Returns true if the specified value is defined and not null. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined and not null. + */ +goog.isDefAndNotNull = function(val) { + // Note that undefined == null. + return val != null; +}; + + +/** + * Returns true if the specified value is an array. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArray = function(val) { + return goog.typeOf(val) == 'array'; +}; + + +/** + * Returns true if the object looks like an array. To qualify as array like + * the value needs to be either a NodeList or an object with a Number length + * property. As a special case, a function value is not array like, because its + * length property is fixed to correspond to the number of expected arguments. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + // We do not use goog.isObject here in order to exclude function values. + return type == 'array' || type == 'object' && typeof val.length == 'number'; +}; + + +/** + * Returns true if the object looks like a Date. To qualify as Date-like the + * value needs to be an object and have a getFullYear() function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a like a Date. + */ +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == 'function'; +}; + + +/** + * Returns true if the specified value is a function. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is a function. + */ +goog.isFunction = function(val) { + return goog.typeOf(val) == 'function'; +}; + + +/** + * Returns true if the specified value is an object. This includes arrays and + * functions. + * @param {?} val Variable to test. + * @return {boolean} Whether variable is an object. + */ +goog.isObject = function(val) { + var type = typeof val; + return type == 'object' && val != null || type == 'function'; + // return Object(val) === val also works, but is slower, especially if val is + // not an object. +}; + + +/** + * Gets a unique ID for an object. This mutates the object so that further calls + * with the same object as a parameter returns the same value. The unique ID is + * guaranteed to be unique across the current session amongst objects that are + * passed into {@code getUid}. There is no guarantee that the ID is unique or + * consistent across sessions. It is unsafe to generate unique ID for function + * prototypes. + * + * @param {Object} obj The object to get the unique ID for. + * @return {number} The unique ID for the object. + */ +goog.getUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In Opera window.hasOwnProperty exists but always returns false so we avoid + // using it. As a consequence the unique ID generated for BaseClass.prototype + // and SubClass.prototype will be the same. + return obj[goog.UID_PROPERTY_] || + (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); +}; + + +/** + * Whether the given object is already assigned a unique ID. + * + * This does not modify the object. + * + * @param {!Object} obj The object to check. + * @return {boolean} Whether there is an assigned unique id for the object. + */ +goog.hasUid = function(obj) { + return !!obj[goog.UID_PROPERTY_]; +}; + + +/** + * Removes the unique ID from an object. This is useful if the object was + * previously mutated using {@code goog.getUid} in which case the mutation is + * undone. + * @param {Object} obj The object to remove the unique ID field from. + */ +goog.removeUid = function(obj) { + // TODO(arv): Make the type stricter, do not accept null. + + // In IE, DOM nodes are not instances of Object and throw an exception if we + // try to delete. Instead we try to use removeAttribute. + if (obj !== null && 'removeAttribute' in obj) { + obj.removeAttribute(goog.UID_PROPERTY_); + } + + try { + delete obj[goog.UID_PROPERTY_]; + } catch (ex) { + } +}; + + +/** + * Name for unique ID property. Initialized in a way to help avoid collisions + * with other closure JavaScript on the same page. + * @type {string} + * @private + */ +goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0); + + +/** + * Counter for UID. + * @type {number} + * @private + */ +goog.uidCounter_ = 0; + + +/** + * Adds a hash code field to an object. The hash code is unique for the + * given object. + * @param {Object} obj The object to get the hash code for. + * @return {number} The hash code for the object. + * @deprecated Use goog.getUid instead. + */ +goog.getHashCode = goog.getUid; + + +/** + * Removes the hash code field from an object. + * @param {Object} obj The object to remove the field from. + * @deprecated Use goog.removeUid instead. + */ +goog.removeHashCode = goog.removeUid; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.cloneObject</code> does not detect reference loops. Objects that + * refer to themselves will cause infinite recursion. + * + * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies + * UIDs created by <code>getUid</code> into cloned results. + * + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. + * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods. + */ +goog.cloneObject = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.cloneObject(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * A native implementation of goog.bind. + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function goog.bind() was + * invoked as a method of. + * @template T + * @private + */ +goog.bindNative_ = function(fn, selfObj, var_args) { + return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments)); +}; + + +/** + * A pure-JS implementation of goog.bind. + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function goog.bind() was + * invoked as a method of. + * @template T + * @private + */ +goog.bindJs_ = function(fn, selfObj, var_args) { + if (!fn) { + throw new Error(); + } + + if (arguments.length > 2) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + // Prepend the bound arguments to the current arguments. + var newArgs = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(newArgs, boundArgs); + return fn.apply(selfObj, newArgs); + }; + + } else { + return function() { + return fn.apply(selfObj, arguments); + }; + } +}; + + +/** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of this 'pre-specified'. + * + * Remaining arguments specified at call-time are appended to the pre-specified + * ones. + * + * Also see: {@link #partial}. + * + * Usage: + * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2'); + * barMethBound('arg3', 'arg4');</pre> + * + * @param {?function(this:T, ...)} fn A function to partially apply. + * @param {T} selfObj Specifies the object which this should point to when the + * function is run. + * @param {...*} var_args Additional arguments that are partially applied to the + * function. + * @return {!Function} A partially-applied form of the function goog.bind() was + * invoked as a method of. + * @template T + * @suppress {deprecated} See above. + */ +goog.bind = function(fn, selfObj, var_args) { + // TODO(nicksantos): narrow the type signature. + if (Function.prototype.bind && + // NOTE(nicksantos): Somebody pulled base.js into the default Chrome + // extension environment. This means that for Chrome extensions, they get + // the implementation of Function.prototype.bind that calls goog.bind + // instead of the native one. Even worse, we don't want to introduce a + // circular dependency between goog.bind and Function.prototype.bind, so + // we have to hack this to make sure it works correctly. + Function.prototype.bind.toString().indexOf('native code') != -1) { + goog.bind = goog.bindNative_; + } else { + goog.bind = goog.bindJs_; + } + return goog.bind.apply(null, arguments); +}; + + +/** + * Like goog.bind(), except that a 'this object' is not required. Useful when + * the target function is already bound. + * + * Usage: + * var g = goog.partial(f, arg1, arg2); + * g(arg3, arg4); + * + * @param {Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn. + * @return {!Function} A partially-applied form of the function goog.partial() + * was invoked as a method of. + */ +goog.partial = function(fn, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + // Clone the array (with slice()) and append additional arguments + // to the existing arguments. + var newArgs = args.slice(); + newArgs.push.apply(newArgs, arguments); + return fn.apply(this, newArgs); + }; +}; + + +/** + * Copies all the members of a source object to a target object. This method + * does not work on all browsers for all objects that contain keys such as + * toString or hasOwnProperty. Use goog.object.extend for this purpose. + * @param {Object} target Target. + * @param {Object} source Source. + */ +goog.mixin = function(target, source) { + for (var x in source) { + target[x] = source[x]; + } + + // For IE7 or lower, the for-in-loop does not contain any properties that are + // not enumerable on the prototype object (for example, isPrototypeOf from + // Object.prototype) but also it will not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). +}; + + +/** + * @return {number} An integer value representing the number of milliseconds + * between midnight, January 1, 1970 and the current time. + */ +goog.now = (goog.TRUSTED_SITE && Date.now) || (function() { + // Unary plus operator converts its operand to a number which in + // the case of + // a date is done by calling getTime(). + return +new Date(); + }); + + +/** + * Evals JavaScript in the global scope. In IE this uses execScript, other + * browsers use goog.global.eval. If goog.global.eval does not evaluate in the + * global scope (for example, in Safari), appends a script tag instead. + * Throws an exception if neither execScript or eval is defined. + * @param {string} script JavaScript string. + */ +goog.globalEval = function(script) { + if (goog.global.execScript) { + goog.global.execScript(script, 'JavaScript'); + } else if (goog.global.eval) { + // Test to see if eval works + if (goog.evalWorksForGlobals_ == null) { + goog.global.eval('var _evalTest_ = 1;'); + if (typeof goog.global['_evalTest_'] != 'undefined') { + try { + delete goog.global['_evalTest_']; + } catch (ignore) { + // Microsoft edge fails the deletion above in strict mode. + } + goog.evalWorksForGlobals_ = true; + } else { + goog.evalWorksForGlobals_ = false; + } + } + + if (goog.evalWorksForGlobals_) { + goog.global.eval(script); + } else { + /** @type {Document} */ + var doc = goog.global.document; + var scriptElt = + /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT')); + scriptElt.type = 'text/javascript'; + scriptElt.defer = false; + // Note(pupius): can't use .innerHTML since "t('<test>')" will fail and + // .text doesn't work in Safari 2. Therefore we append a text node. + scriptElt.appendChild(doc.createTextNode(script)); + doc.body.appendChild(scriptElt); + doc.body.removeChild(scriptElt); + } + } else { + throw new Error('goog.globalEval not available'); + } +}; + + +/** + * Indicates whether or not we can call 'eval' directly to eval code in the + * global scope. Set to a Boolean by the first call to goog.globalEval (which + * empirically tests whether eval works for globals). @see goog.globalEval + * @type {?boolean} + * @private + */ +goog.evalWorksForGlobals_ = null; + + +/** + * Optional map of CSS class names to obfuscated names used with + * goog.getCssName(). + * @private {!Object<string, string>|undefined} + * @see goog.setCssNameMapping + */ +goog.cssNameMapping_; + + +/** + * Optional obfuscation style for CSS class names. Should be set to either + * 'BY_WHOLE' or 'BY_PART' if defined. + * @type {string|undefined} + * @private + * @see goog.setCssNameMapping + */ +goog.cssNameMappingStyle_; + + + +/** + * A hook for modifying the default behavior goog.getCssName. The function + * if present, will recieve the standard output of the goog.getCssName as + * its input. + * + * @type {(function(string):string)|undefined} + */ +goog.global.CLOSURE_CSS_NAME_MAP_FN; + + +/** + * Handles strings that are intended to be used as CSS class names. + * + * This function works in tandem with @see goog.setCssNameMapping. + * + * Without any mapping set, the arguments are simple joined with a hyphen and + * passed through unaltered. + * + * When there is a mapping, there are two possible styles in which these + * mappings are used. In the BY_PART style, each part (i.e. in between hyphens) + * of the passed in css name is rewritten according to the map. In the BY_WHOLE + * style, the full css name is looked up in the map directly. If a rewrite is + * not specified by the map, the compiler will output a warning. + * + * When the mapping is passed to the compiler, it will replace calls to + * goog.getCssName with the strings from the mapping, e.g. + * var x = goog.getCssName('foo'); + * var y = goog.getCssName(this.baseClass, 'active'); + * becomes: + * var x = 'foo'; + * var y = this.baseClass + '-active'; + * + * If one argument is passed it will be processed, if two are passed only the + * modifier will be processed, as it is assumed the first argument was generated + * as a result of calling goog.getCssName. + * + * @param {string} className The class name. + * @param {string=} opt_modifier A modifier to be appended to the class name. + * @return {string} The class name or the concatenation of the class name and + * the modifier. + */ +goog.getCssName = function(className, opt_modifier) { + // String() is used for compatibility with compiled soy where the passed + // className can be non-string objects. + if (String(className).charAt(0) == '.') { + throw new Error( + 'className passed in goog.getCssName must not start with ".".' + + ' You passed: ' + className); + } + + var getMapping = function(cssName) { + return goog.cssNameMapping_[cssName] || cssName; + }; + + var renameByParts = function(cssName) { + // Remap all the parts individually. + var parts = cssName.split('-'); + var mapped = []; + for (var i = 0; i < parts.length; i++) { + mapped.push(getMapping(parts[i])); + } + return mapped.join('-'); + }; + + var rename; + if (goog.cssNameMapping_) { + rename = + goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts; + } else { + rename = function(a) { + return a; + }; + } + + var result = + opt_modifier ? className + '-' + rename(opt_modifier) : rename(className); + + // The special CLOSURE_CSS_NAME_MAP_FN allows users to specify further + // processing of the class name. + if (goog.global.CLOSURE_CSS_NAME_MAP_FN) { + return goog.global.CLOSURE_CSS_NAME_MAP_FN(result); + } + + return result; +}; + + +/** + * Sets the map to check when returning a value from goog.getCssName(). Example: + * <pre> + * goog.setCssNameMapping({ + * "goog": "a", + * "disabled": "b", + * }); + * + * var x = goog.getCssName('goog'); + * // The following evaluates to: "a a-b". + * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled') + * </pre> + * When declared as a map of string literals to string literals, the JSCompiler + * will replace all calls to goog.getCssName() using the supplied map if the + * --process_closure_primitives flag is set. + * + * @param {!Object} mapping A map of strings to strings where keys are possible + * arguments to goog.getCssName() and values are the corresponding values + * that should be returned. + * @param {string=} opt_style The style of css name mapping. There are two valid + * options: 'BY_PART', and 'BY_WHOLE'. + * @see goog.getCssName for a description. + */ +goog.setCssNameMapping = function(mapping, opt_style) { + goog.cssNameMapping_ = mapping; + goog.cssNameMappingStyle_ = opt_style; +}; + + +/** + * To use CSS renaming in compiled mode, one of the input files should have a + * call to goog.setCssNameMapping() with an object literal that the JSCompiler + * can extract and use to replace all calls to goog.getCssName(). In uncompiled + * mode, JavaScript code should be loaded before this base.js file that declares + * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is + * to ensure that the mapping is loaded before any calls to goog.getCssName() + * are made in uncompiled mode. + * + * A hook for overriding the CSS name mapping. + * @type {!Object<string, string>|undefined} + */ +goog.global.CLOSURE_CSS_NAME_MAPPING; + + +if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) { + // This does not call goog.setCssNameMapping() because the JSCompiler + // requires that goog.setCssNameMapping() be called with an object literal. + goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING; +} + + +/** + * Gets a localized message. + * + * This function is a compiler primitive. If you give the compiler a localized + * message bundle, it will replace the string at compile-time with a localized + * version, and expand goog.getMsg call to a concatenated string. + * + * Messages must be initialized in the form: + * <code> + * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'}); + * </code> + * + * This function produces a string which should be treated as plain text. Use + * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to + * produce SafeHtml. + * + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object<string, string>=} opt_values Maps place holder name to value. + * @return {string} message with placeholders filled. + */ +goog.getMsg = function(str, opt_values) { + if (opt_values) { + str = str.replace(/\{\$([^}]+)}/g, function(match, key) { + return (opt_values != null && key in opt_values) ? opt_values[key] : + match; + }); + } + return str; +}; + + +/** + * Gets a localized message. If the message does not have a translation, gives a + * fallback message. + * + * This is useful when introducing a new message that has not yet been + * translated into all languages. + * + * This function is a compiler primitive. Must be used in the form: + * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code> + * where MSG_A and MSG_B were initialized with goog.getMsg. + * + * @param {string} a The preferred message. + * @param {string} b The fallback message. + * @return {string} The best translated message. + */ +goog.getMsgWithFallback = function(a, b) { + return a; +}; + + +/** + * Exposes an unobfuscated global namespace path for the given object. + * Note that fields of the exported object *will* be obfuscated, unless they are + * exported in turn via this function or goog.exportProperty. + * + * Also handy for making public items that are defined in anonymous closures. + * + * ex. goog.exportSymbol('public.path.Foo', Foo); + * + * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction); + * public.path.Foo.staticFunction(); + * + * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', + * Foo.prototype.myMethod); + * new public.path.Foo().myMethod(); + * + * @param {string} publicPath Unobfuscated name to export. + * @param {*} object Object the name should point to. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is goog.global. + */ +goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) { + goog.exportPath_(publicPath, object, opt_objectToExportTo); +}; + + +/** + * Exports a property unobfuscated into the object's namespace. + * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); + * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod); + * @param {Object} object Object whose static property is being exported. + * @param {string} publicName Unobfuscated name to export. + * @param {*} symbol Object the name should point to. + */ +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol; +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * <pre> + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { }; + * + * function ChildClass(a, b, c) { + * ChildClass.base(this, 'constructor', a, b); + * } + * goog.inherits(ChildClass, ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // This works. + * </pre> + * + * @param {!Function} childCtor Child class. + * @param {!Function} parentCtor Parent class. + */ +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; + + /** + * Calls superclass constructor/method. + * + * This function is only available if you use goog.inherits to + * express inheritance relationships between classes. + * + * NOTE: This is a replacement for goog.base and for superClass_ + * property defined in childCtor. + * + * @param {!Object} me Should always be "this". + * @param {string} methodName The method name to call. Calling + * superclass constructor can be done with the special string + * 'constructor'. + * @param {...*} var_args The arguments to pass to superclass + * method/constructor. + * @return {*} The return value of the superclass method/constructor. + */ + childCtor.base = function(me, methodName, var_args) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + return parentCtor.prototype[methodName].apply(me, args); + }; +}; + + +/** + * Call up to the superclass. + * + * If this is called from a constructor, then this calls the superclass + * constructor with arguments 1-N. + * + * If this is called from a prototype method, then you must pass the name of the + * method as the second argument to this function. If you do not, you will get a + * runtime error. This calls the superclass' method with arguments 2-N. + * + * This function only works if you use goog.inherits to express inheritance + * relationships between your classes. + * + * This function is a compiler primitive. At compile-time, the compiler will do + * macro expansion to remove a lot of the extra overhead that this function + * introduces. The compiler will also enforce a lot of the assumptions that this + * function makes, and treat it as a compiler error if you break them. + * + * @param {!Object} me Should always be "this". + * @param {*=} opt_methodName The method name if calling a super method. + * @param {...*} var_args The rest of the arguments. + * @return {*} The return value of the superclass method. + * @suppress {es5Strict} This method can not be used in strict mode, but + * all Closure Library consumers must depend on this file. + * @deprecated goog.base is not strict mode compatible. Prefer the static + * "base" method added to the constructor by goog.inherits + * or ES6 classes and the "super" keyword. + */ +goog.base = function(me, opt_methodName, var_args) { + var caller = arguments.callee.caller; + + if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) { + throw new Error( + 'arguments.caller not defined. goog.base() cannot be used ' + + 'with strict mode code. See ' + + 'http://www.ecma-international.org/ecma-262/5.1/#sec-C'); + } + + if (caller.superClass_) { + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var ctorArgs = new Array(arguments.length - 1); + for (var i = 1; i < arguments.length; i++) { + ctorArgs[i - 1] = arguments[i]; + } + // This is a constructor. Call the superclass constructor. + return caller.superClass_.constructor.apply(me, ctorArgs); + } + + // Copying using loop to avoid deop due to passing arguments object to + // function. This is faster in many JS engines as of late 2014. + var args = new Array(arguments.length - 2); + for (var i = 2; i < arguments.length; i++) { + args[i - 2] = arguments[i]; + } + var foundCaller = false; + for (var ctor = me.constructor; ctor; + ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if (ctor.prototype[opt_methodName] === caller) { + foundCaller = true; + } else if (foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args); + } + } + + // If we did not find the caller in the prototype chain, then one of two + // things happened: + // 1) The caller is an instance method. + // 2) This method was not called by the right caller. + if (me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args); + } else { + throw new Error( + 'goog.base called from a method of one name ' + + 'to a method of a different name'); + } +}; + + +/** + * Allow for aliasing within scope functions. This function exists for + * uncompiled code - in compiled code the calls will be inlined and the aliases + * applied. In uncompiled code the function is simply run since the aliases as + * written are valid JavaScript. + * + * MOE:begin_intracomment_strip + * See the goog.scope document at http://go/goog.scope + * MOE:end_intracomment_strip + * + * @param {function()} fn Function to call. This function can contain aliases + * to namespaces (e.g. "var dom = goog.dom") or classes + * (e.g. "var Timer = goog.Timer"). + */ +goog.scope = function(fn) { + if (goog.isInModuleLoader_()) { + throw new Error('goog.scope is not supported within a goog.module.'); + } + fn.call(goog.global); +}; + + +/* + * To support uncompiled, strict mode bundles that use eval to divide source + * like so: + * eval('someSource;//# sourceUrl sourcefile.js'); + * We need to export the globally defined symbols "goog" and "COMPILED". + * Exporting "goog" breaks the compiler optimizations, so we required that + * be defined externally. + * NOTE: We don't use goog.exportSymbol here because we don't want to trigger + * extern generation when that compiler option is enabled. + */ +if (!COMPILED) { + goog.global['COMPILED'] = COMPILED; +} + + +//============================================================================== +// goog.defineClass implementation +//============================================================================== + + +/** + * Creates a restricted form of a Closure "class": + * - from the compiler's perspective, the instance returned from the + * constructor is sealed (no new properties may be added). This enables + * better checks. + * - the compiler will rewrite this definition to a form that is optimal + * for type checking and optimization (initially this will be a more + * traditional form). + * + * @param {Function} superClass The superclass, Object or null. + * @param {goog.defineClass.ClassDescriptor} def + * An object literal describing + * the class. It may have the following properties: + * "constructor": the constructor function + * "statics": an object literal containing methods to add to the constructor + * as "static" methods or a function that will receive the constructor + * function as its only parameter to which static properties can + * be added. + * all other properties are added to the prototype. + * @return {!Function} The class constructor. + */ +goog.defineClass = function(superClass, def) { + // TODO(johnlenz): consider making the superClass an optional parameter. + var constructor = def.constructor; + var statics = def.statics; + // Wrap the constructor prior to setting up the prototype and static methods. + if (!constructor || constructor == Object.prototype.constructor) { + constructor = function() { + throw new Error( + 'cannot instantiate an interface (no constructor defined).'); + }; + } + + var cls = goog.defineClass.createSealingConstructor_(constructor, superClass); + if (superClass) { + goog.inherits(cls, superClass); + } + + // Remove all the properties that should not be copied to the prototype. + delete def.constructor; + delete def.statics; + + goog.defineClass.applyProperties_(cls.prototype, def); + if (statics != null) { + if (statics instanceof Function) { + statics(cls); + } else { + goog.defineClass.applyProperties_(cls, statics); + } + } + + return cls; +}; + + +/** + * @typedef {{ + * constructor: (!Function|undefined), + * statics: (Object|undefined|function(Function):void) + * }} + */ +goog.defineClass.ClassDescriptor; + + +/** + * @define {boolean} Whether the instances returned by goog.defineClass should + * be sealed when possible. + * + * When sealing is disabled the constructor function will not be wrapped by + * goog.defineClass, making it incompatible with ES6 class methods. + */ +goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG); + + +/** + * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is + * defined, this function will wrap the constructor in a function that seals the + * results of the provided constructor function. + * + * @param {!Function} ctr The constructor whose results maybe be sealed. + * @param {Function} superClass The superclass constructor. + * @return {!Function} The replacement constructor. + * @private + */ +goog.defineClass.createSealingConstructor_ = function(ctr, superClass) { + if (!goog.defineClass.SEAL_CLASS_INSTANCES) { + // Do now wrap the constructor when sealing is disabled. Angular code + // depends on this for injection to work properly. + return ctr; + } + + // Compute whether the constructor is sealable at definition time, rather + // than when the instance is being constructed. + var superclassSealable = !goog.defineClass.isUnsealable_(superClass); + + /** + * @this {Object} + * @return {?} + */ + var wrappedCtr = function() { + // Don't seal an instance of a subclass when it calls the constructor of + // its super class as there is most likely still setup to do. + var instance = ctr.apply(this, arguments) || this; + instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_]; + + if (this.constructor === wrappedCtr && superclassSealable && + Object.seal instanceof Function) { + Object.seal(instance); + } + return instance; + }; + + return wrappedCtr; +}; + + +/** + * @param {Function} ctr The constructor to test. + * @return {boolean} Whether the constructor has been tagged as unsealable + * using goog.tagUnsealableClass. + * @private + */ +goog.defineClass.isUnsealable_ = function(ctr) { + return ctr && ctr.prototype && + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]; +}; + + +// TODO(johnlenz): share these values with the goog.object +/** + * The names of the fields that are defined on Object.prototype. + * @type {!Array<string>} + * @private + * @const + */ +goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + + +// TODO(johnlenz): share this function with the goog.object +/** + * @param {!Object} target The object to add properties to. + * @param {!Object} source The object to copy properties from. + * @private + */ +goog.defineClass.applyProperties_ = function(target, source) { + // TODO(johnlenz): update this to support ES5 getters/setters + + var key; + for (key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) { + key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } +}; + + +/** + * Sealing classes breaks the older idiom of assigning properties on the + * prototype rather than in the constructor. As such, goog.defineClass + * must not seal subclasses of these old-style classes until they are fixed. + * Until then, this marks a class as "broken", instructing defineClass + * not to seal subclasses. + * @param {!Function} ctr The legacy constructor to tag as unsealable. + */ +goog.tagUnsealableClass = function(ctr) { + if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) { + ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true; + } +}; + + +/** + * Name for unsealable tag property. + * @const @private {string} + */ +goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable'; + + +/** + * Returns a newly created map from language mode string to a boolean + * indicating whether transpilation should be done for that mode. + * + * Guaranteed invariant: + * For any two modes, l1 and l2 where l2 is a newer mode than l1, + * `map[l1] == true` implies that `map[l2] == true`. + * @private + * @return {!Object<string, boolean>} + */ +goog.createRequiresTranspilation_ = function() { + var /** !Object<string, boolean> */ requiresTranspilation = {'es3': false}; + var transpilationRequiredForAllLaterModes = false; + + /** + * Adds an entry to requiresTranspliation for the given language mode. + * + * IMPORTANT: Calls must be made in order from oldest to newest language + * mode. + * @param {string} modeName + * @param {function(): boolean} isSupported Returns true if the JS engine + * supports the given mode. + */ + function addNewerLanguageTranspilationCheck(modeName, isSupported) { + if (transpilationRequiredForAllLaterModes) { + requiresTranspilation[modeName] = true; + } else if (isSupported()) { + requiresTranspilation[modeName] = false; + } else { + requiresTranspilation[modeName] = true; + transpilationRequiredForAllLaterModes = true; + } + } + + /** + * Does the given code evaluate without syntax errors and return a truthy + * result? + */ + function /** boolean */ evalCheck(/** string */ code) { + try { + return !!eval(code); + } catch (ignored) { + return false; + } + } + + var userAgent = goog.global.navigator && goog.global.navigator.userAgent ? + goog.global.navigator.userAgent : + ''; + + // Identify ES3-only browsers by their incorrect treatment of commas. + addNewerLanguageTranspilationCheck('es5', function() { + return evalCheck('[1,].length==1'); + }); + addNewerLanguageTranspilationCheck('es6', function() { + // Edge has a non-deterministic (i.e., not reproducible) bug with ES6: + // https://github.com/Microsoft/ChakraCore/issues/1496. + // MOE:begin_strip + // TODO(joeltine): Our internal web-testing version of Edge will need to be + // updated before we can remove this check. See http://b/34945376. + // MOE:end_strip + var re = /Edge\/(\d+)(\.\d)*/i; + var edgeUserAgent = userAgent.match(re); + if (edgeUserAgent && Number(edgeUserAgent[1]) < 15) { + return false; + } + // Test es6: [FF50 (?), Edge 14 (?), Chrome 50] + // (a) default params (specifically shadowing locals), + // (b) destructuring, (c) block-scoped functions, + // (d) for-of (const), (e) new.target/Reflect.construct + var es6fullTest = + 'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' + + 'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' + + 'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' + + 'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' + + '==3}'; + + return evalCheck('(()=>{"use strict";' + es6fullTest + '})()'); + }); + // TODO(joeltine): Remove es6-impl references for b/31340605. + // Consider es6-impl (widely-implemented es6 features) to be supported + // whenever es6 is supported. Technically es6-impl is a lower level of + // support than es6, but we don't have tests specifically for it. + addNewerLanguageTranspilationCheck('es6-impl', function() { + return true; + }); + // ** and **= are the only new features in 'es7' + addNewerLanguageTranspilationCheck('es7', function() { + return evalCheck('2 ** 2 == 4'); + }); + // async functions are the only new features in 'es8' + addNewerLanguageTranspilationCheck('es8', function() { + return evalCheck('async () => 1, true'); + }); + return requiresTranspilation; +};
diff --git a/third_party/ink/closure/crypt/base64.js b/third_party/ink/closure/crypt/base64.js new file mode 100644 index 0000000..026aa956 --- /dev/null +++ b/third_party/ink/closure/crypt/base64.js
@@ -0,0 +1,370 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base64 en/decoding. Not much to say here except that we + * work with decoded values in arrays of bytes. By "byte" I mean a number + * in [0, 255]. + * + * @author doughtie@google.com (Gavin Doughtie) + * @author fschneider@google.com (Fritz Schneider) + */ + +goog.provide('goog.crypt.base64'); + +goog.require('goog.asserts'); +goog.require('goog.crypt'); +goog.require('goog.string'); +goog.require('goog.userAgent'); +goog.require('goog.userAgent.product'); + +// Static lookup maps, lazily populated by init_() + + +/** + * Maps bytes to characters. + * @type {Object} + * @private + */ +goog.crypt.base64.byteToCharMap_ = null; + + +/** + * Maps characters to bytes. Used for normal and websafe characters. + * @type {Object} + * @private + */ +goog.crypt.base64.charToByteMap_ = null; + + +/** + * Maps bytes to websafe characters. + * @type {Object} + * @private + */ +goog.crypt.base64.byteToCharMapWebSafe_ = null; + + +/** + * Our default alphabet, shared between + * ENCODED_VALS and ENCODED_VALS_WEBSAFE + * @type {string} + */ +goog.crypt.base64.ENCODED_VALS_BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + 'abcdefghijklmnopqrstuvwxyz' + + '0123456789'; + + +/** + * Our default alphabet. Value 64 (=) is special; it means "nothing." + * @type {string} + */ +goog.crypt.base64.ENCODED_VALS = goog.crypt.base64.ENCODED_VALS_BASE + '+/='; + + +/** + * Our websafe alphabet. + * @type {string} + */ +goog.crypt.base64.ENCODED_VALS_WEBSAFE = + goog.crypt.base64.ENCODED_VALS_BASE + '-_.'; + + +/** + * White list of implementations with known-good native atob and btoa functions. + * Listing these explicitly (via the ASSUME_* wrappers) benefits dead-code + * removal in per-browser compilations. + * @private {boolean} + */ +goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ = goog.userAgent.GECKO || + (goog.userAgent.WEBKIT && !goog.userAgent.product.SAFARI) || + goog.userAgent.OPERA; + + +/** + * Does this browser have a working btoa function? + * @private {boolean} + */ +goog.crypt.base64.HAS_NATIVE_ENCODE_ = + goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ || + typeof(goog.global.btoa) == 'function'; + + +/** + * Does this browser have a working atob function? + * We blacklist known-bad implementations: + * - IE (10+) added atob() but it does not tolerate whitespace on the input. + * @private {boolean} + */ +goog.crypt.base64.HAS_NATIVE_DECODE_ = + goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ || + (!goog.userAgent.product.SAFARI && !goog.userAgent.IE && + typeof(goog.global.atob) == 'function'); + + +/** + * Base64-encode an array of bytes. + * + * @param {Array<number>|Uint8Array} input An array of bytes (numbers with + * value in [0, 255]) to encode. + * @param {boolean=} opt_webSafe True indicates we should use the alternative + * alphabet, which does not require escaping for use in URLs. + * @return {string} The base64 encoded string. + */ +goog.crypt.base64.encodeByteArray = function(input, opt_webSafe) { + // Assert avoids runtime dependency on goog.isArrayLike, which helps reduce + // size of jscompiler output, and which yields slight performance increase. + goog.asserts.assert( + goog.isArrayLike(input), 'encodeByteArray takes an array as a parameter'); + + goog.crypt.base64.init_(); + + var byteToCharMap = opt_webSafe ? goog.crypt.base64.byteToCharMapWebSafe_ : + goog.crypt.base64.byteToCharMap_; + + var output = []; + + for (var i = 0; i < input.length; i += 3) { + var byte1 = input[i]; + var haveByte2 = i + 1 < input.length; + var byte2 = haveByte2 ? input[i + 1] : 0; + var haveByte3 = i + 2 < input.length; + var byte3 = haveByte3 ? input[i + 2] : 0; + + var outByte1 = byte1 >> 2; + var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); + var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6); + var outByte4 = byte3 & 0x3F; + + if (!haveByte3) { + outByte4 = 64; + + if (!haveByte2) { + outByte3 = 64; + } + } + + output.push( + byteToCharMap[outByte1], byteToCharMap[outByte2], + byteToCharMap[outByte3], byteToCharMap[outByte4]); + } + + return output.join(''); +}; + + +/** + * Base64-encode a string. + * + * @param {string} input A string to encode. + * @param {boolean=} opt_webSafe True indicates we should use the alternative + * alphabet, which does not require escaping for use in URLs. + * @return {string} The base64 encoded string. + */ +goog.crypt.base64.encodeString = function(input, opt_webSafe) { + // Shortcut for browsers that implement + // a native base64 encoder in the form of "btoa/atob" + if (goog.crypt.base64.HAS_NATIVE_ENCODE_ && !opt_webSafe) { + return goog.global.btoa(input); + } + return goog.crypt.base64.encodeByteArray( + goog.crypt.stringToByteArray(input), opt_webSafe); +}; + + +/** + * Base64-decode a string. + * + * @param {string} input Input to decode. Any whitespace is ignored, and the + * input maybe encoded with either supported alphabet (or a mix thereof). + * @param {boolean=} opt_webSafe True indicates we should use the alternative + * alphabet, which does not require escaping for use in URLs. Note that + * passing false may also still allow webSafe input decoding, when the + * fallback decoder is used on browsers without native support. + * @return {string} string representing the decoded value. + */ +goog.crypt.base64.decodeString = function(input, opt_webSafe) { + // Shortcut for browsers that implement + // a native base64 encoder in the form of "btoa/atob" + if (goog.crypt.base64.HAS_NATIVE_DECODE_ && !opt_webSafe) { + return goog.global.atob(input); + } + var output = ''; + function pushByte(b) { output += String.fromCharCode(b); } + + goog.crypt.base64.decodeStringInternal_(input, pushByte); + + return output; +}; + + +/** + * Base64-decode a string to an Array of numbers. + * + * In base-64 decoding, groups of four characters are converted into three + * bytes. If the encoder did not apply padding, the input length may not + * be a multiple of 4. + * + * In this case, the last group will have fewer than 4 characters, and + * padding will be inferred. If the group has one or two characters, it decodes + * to one byte. If the group has three characters, it decodes to two bytes. + * + * @param {string} input Input to decode. Any whitespace is ignored, and the + * input maybe encoded with either supported alphabet (or a mix thereof). + * @param {boolean=} opt_ignored Unused parameter, retained for compatibility. + * @return {!Array<number>} bytes representing the decoded value. + */ +goog.crypt.base64.decodeStringToByteArray = function(input, opt_ignored) { + var output = []; + function pushByte(b) { output.push(b); } + + goog.crypt.base64.decodeStringInternal_(input, pushByte); + + return output; +}; + + +/** + * Base64-decode a string to a Uint8Array. + * + * Note that Uint8Array is not supported on older browsers, e.g. IE < 10. + * @see http://caniuse.com/uint8array + * + * In base-64 decoding, groups of four characters are converted into three + * bytes. If the encoder did not apply padding, the input length may not + * be a multiple of 4. + * + * In this case, the last group will have fewer than 4 characters, and + * padding will be inferred. If the group has one or two characters, it decodes + * to one byte. If the group has three characters, it decodes to two bytes. + * + * @param {string} input Input to decode. Any whitespace is ignored, and the + * input maybe encoded with either supported alphabet (or a mix thereof). + * @return {!Uint8Array} bytes representing the decoded value. + */ +goog.crypt.base64.decodeStringToUint8Array = function(input) { + goog.asserts.assert( + !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10'), + 'Browser does not support typed arrays'); + var len = input.length; + // Check if there are trailing '=' as padding in the b64 string. + var placeholders = 0; + if (input[len - 2] === '=') { + placeholders = 2; + } else if (input[len - 1] === '=') { + placeholders = 1; + } + var output = new Uint8Array(Math.ceil(len * 3 / 4) - placeholders); + var outLen = 0; + function pushByte(b) { + output[outLen++] = b; + } + + goog.crypt.base64.decodeStringInternal_(input, pushByte); + + return output.subarray(0, outLen); +}; + + +/** + * @param {string} input Input to decode. + * @param {function(number):void} pushByte result accumulator. + * @private + */ +goog.crypt.base64.decodeStringInternal_ = function(input, pushByte) { + goog.crypt.base64.init_(); + + var nextCharIndex = 0; + /** + * @param {number} default_val Used for end-of-input. + * @return {number} The next 6-bit value, or the default for end-of-input. + */ + function getByte(default_val) { + while (nextCharIndex < input.length) { + var ch = input.charAt(nextCharIndex++); + var b = goog.crypt.base64.charToByteMap_[ch]; + if (b != null) { + return b; // Common case: decoded the char. + } + if (!goog.string.isEmptyOrWhitespace(ch)) { + throw new Error('Unknown base64 encoding at char: ' + ch); + } + // We encountered whitespace: loop around to the next input char. + } + return default_val; // No more input remaining. + } + + while (true) { + var byte1 = getByte(-1); + var byte2 = getByte(0); + var byte3 = getByte(64); + var byte4 = getByte(64); + + // The common case is that all four bytes are present, so if we have byte4 + // we can skip over the truncated input special case handling. + if (byte4 === 64) { + if (byte1 === -1) { + return; // Terminal case: no input left to decode. + } + // Here we know an intermediate number of bytes are missing. + // The defaults for byte2, byte3 and byte4 apply the inferred padding + // rules per the public API documentation. i.e: 1 byte + // missing should yield 2 bytes of output, but 2 or 3 missing bytes yield + // a single byte of output. (Recall that 64 corresponds the padding char). + } + + var outByte1 = (byte1 << 2) | (byte2 >> 4); + pushByte(outByte1); + + if (byte3 != 64) { + var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2); + pushByte(outByte2); + + if (byte4 != 64) { + var outByte3 = ((byte3 << 6) & 0xC0) | byte4; + pushByte(outByte3); + } + } + } +}; + + +/** + * Lazy static initialization function. Called before + * accessing any of the static map variables. + * @private + */ +goog.crypt.base64.init_ = function() { + if (!goog.crypt.base64.byteToCharMap_) { + goog.crypt.base64.byteToCharMap_ = {}; + goog.crypt.base64.charToByteMap_ = {}; + goog.crypt.base64.byteToCharMapWebSafe_ = {}; + + // We want quick mappings back and forth, so we precompute two maps. + for (var i = 0; i < goog.crypt.base64.ENCODED_VALS.length; i++) { + goog.crypt.base64.byteToCharMap_[i] = + goog.crypt.base64.ENCODED_VALS.charAt(i); + goog.crypt.base64.charToByteMap_[goog.crypt.base64.byteToCharMap_[i]] = i; + goog.crypt.base64.byteToCharMapWebSafe_[i] = + goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i); + + // Be forgiving when decoding and correctly decode both encodings. + if (i >= goog.crypt.base64.ENCODED_VALS_BASE.length) { + goog.crypt.base64 + .charToByteMap_[goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i)] = + i; + } + } + } +};
diff --git a/third_party/ink/closure/crypt/crypt.js b/third_party/ink/closure/crypt/crypt.js new file mode 100644 index 0000000..a0e4f02 --- /dev/null +++ b/third_party/ink/closure/crypt/crypt.js
@@ -0,0 +1,196 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Namespace with crypto related helper functions. + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.crypt'); + +goog.require('goog.array'); +goog.require('goog.asserts'); + + +/** + * Turns a string into an array of bytes; a "byte" being a JS number in the + * range 0-255. Multi-byte characters are written as little-endian. + * @param {string} str String value to arrify. + * @return {!Array<number>} Array of numbers corresponding to the + * UCS character codes of each character in str. + */ +goog.crypt.stringToByteArray = function(str) { + var output = [], p = 0; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + // NOTE: c <= 0xffff since JavaScript strings are UTF-16. + if (c > 0xff) { + output[p++] = c & 0xff; + c >>= 8; + } + output[p++] = c; + } + return output; +}; + + +/** + * Turns an array of numbers into the string given by the concatenation of the + * characters to which the numbers correspond. + * @param {!Uint8Array|!Array<number>} bytes Array of numbers representing + * characters. + * @return {string} Stringification of the array. + */ +goog.crypt.byteArrayToString = function(bytes) { + var CHUNK_SIZE = 8192; + + // Special-case the simple case for speed's sake. + if (bytes.length <= CHUNK_SIZE) { + return String.fromCharCode.apply(null, bytes); + } + + // The remaining logic splits conversion by chunks since + // Function#apply() has a maximum parameter count. + // See discussion: http://goo.gl/LrWmZ9 + + var str = ''; + for (var i = 0; i < bytes.length; i += CHUNK_SIZE) { + var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE); + str += String.fromCharCode.apply(null, chunk); + } + return str; +}; + + +/** + * Turns an array of numbers into the hex string given by the concatenation of + * the hex values to which the numbers correspond. + * @param {Uint8Array|Array<number>} array Array of numbers representing + * characters. + * @return {string} Hex string. + */ +goog.crypt.byteArrayToHex = function(array) { + return goog.array + .map( + array, + function(numByte) { + var hexByte = numByte.toString(16); + return hexByte.length > 1 ? hexByte : '0' + hexByte; + }) + .join(''); +}; + + +/** + * Converts a hex string into an integer array. + * @param {string} hexString Hex string of 16-bit integers (two characters + * per integer). + * @return {!Array<number>} Array of {0,255} integers for the given string. + */ +goog.crypt.hexToByteArray = function(hexString) { + goog.asserts.assert( + hexString.length % 2 == 0, 'Key string length must be multiple of 2'); + var arr = []; + for (var i = 0; i < hexString.length; i += 2) { + arr.push(parseInt(hexString.substring(i, i + 2), 16)); + } + return arr; +}; + + +/** + * Converts a JS string to a UTF-8 "byte" array. + * @param {string} str 16-bit unicode string. + * @return {!Array<number>} UTF-8 byte array. + */ +goog.crypt.stringToUtf8ByteArray = function(str) { + // TODO(pupius): Use native implementations if/when available + var out = [], p = 0; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + if (c < 128) { + out[p++] = c; + } else if (c < 2048) { + out[p++] = (c >> 6) | 192; + out[p++] = (c & 63) | 128; + } else if ( + ((c & 0xFC00) == 0xD800) && (i + 1) < str.length && + ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) { + // Surrogate Pair + c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); + out[p++] = (c >> 18) | 240; + out[p++] = ((c >> 12) & 63) | 128; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } else { + out[p++] = (c >> 12) | 224; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; + } + } + return out; +}; + + +/** + * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode. + * @param {Uint8Array|Array<number>} bytes UTF-8 byte array. + * @return {string} 16-bit Unicode string. + */ +goog.crypt.utf8ByteArrayToString = function(bytes) { + // TODO(pupius): Use native implementations if/when available + var out = [], pos = 0, c = 0; + while (pos < bytes.length) { + var c1 = bytes[pos++]; + if (c1 < 128) { + out[c++] = String.fromCharCode(c1); + } else if (c1 > 191 && c1 < 224) { + var c2 = bytes[pos++]; + out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); + } else if (c1 > 239 && c1 < 365) { + // Surrogate Pair + var c2 = bytes[pos++]; + var c3 = bytes[pos++]; + var c4 = bytes[pos++]; + var u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) - + 0x10000; + out[c++] = String.fromCharCode(0xD800 + (u >> 10)); + out[c++] = String.fromCharCode(0xDC00 + (u & 1023)); + } else { + var c2 = bytes[pos++]; + var c3 = bytes[pos++]; + out[c++] = + String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); + } + } + return out.join(''); +}; + + +/** + * XOR two byte arrays. + * @param {!Uint8Array|!Int8Array|!Array<number>} bytes1 Byte array 1. + * @param {!Uint8Array|!Int8Array|!Array<number>} bytes2 Byte array 2. + * @return {!Array<number>} Resulting XOR of the two byte arrays. + */ +goog.crypt.xorByteArray = function(bytes1, bytes2) { + goog.asserts.assert( + bytes1.length == bytes2.length, 'XOR array lengths must match'); + + var result = []; + for (var i = 0; i < bytes1.length; i++) { + result.push(bytes1[i] ^ bytes2[i]); + } + return result; +};
diff --git a/third_party/ink/closure/debug/debug.js b/third_party/ink/closure/debug/debug.js new file mode 100644 index 0000000..92d1945 --- /dev/null +++ b/third_party/ink/closure/debug/debug.js
@@ -0,0 +1,666 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Logging and debugging utilities. + * + * @author pupius@google.com (Daniel Pupius) + * @see ../demos/debug.html + */ + +goog.provide('goog.debug'); + +goog.require('goog.array'); +goog.require('goog.debug.errorcontext'); +goog.require('goog.userAgent'); + + +/** @define {boolean} Whether logging should be enabled. */ +goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG); + + +/** @define {boolean} Whether to force "sloppy" stack building. */ +goog.define('goog.debug.FORCE_SLOPPY_STACKS', false); + + +/** + * Catches onerror events fired by windows and similar objects. + * @param {function(Object)} logFunc The function to call with the error + * information. + * @param {boolean=} opt_cancel Whether to stop the error from reaching the + * browser. + * @param {Object=} opt_target Object that fires onerror events. + */ +goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) { + var target = opt_target || goog.global; + var oldErrorHandler = target.onerror; + var retVal = !!opt_cancel; + + // Chrome interprets onerror return value backwards (http://crbug.com/92062) + // until it was fixed in webkit revision r94061 (Webkit 535.3). This + // workaround still needs to be skipped in Safari after the webkit change + // gets pushed out in Safari. + // See https://bugs.webkit.org/show_bug.cgi?id=67119 + if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.3')) { + retVal = !retVal; + } + + /** + * New onerror handler for this target. This onerror handler follows the spec + * according to + * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors + * The spec was changed in August 2013 to support receiving column information + * and an error object for all scripts on the same origin or cross origin + * scripts with the proper headers. See + * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + * + * @param {string} message The error message. For cross-origin errors, this + * will be scrubbed to just "Script error.". For new browsers that have + * updated to follow the latest spec, errors that come from origins that + * have proper cross origin headers will not be scrubbed. + * @param {string} url The URL of the script that caused the error. The URL + * will be scrubbed to "" for cross origin scripts unless the script has + * proper cross origin headers and the browser has updated to the latest + * spec. + * @param {number} line The line number in the script that the error + * occurred on. + * @param {number=} opt_col The optional column number that the error + * occurred on. Only browsers that have updated to the latest spec will + * include this. + * @param {Error=} opt_error The optional actual error object for this + * error that should include the stack. Only browsers that have updated + * to the latest spec will inlude this parameter. + * @return {boolean} Whether to prevent the error from reaching the browser. + */ + target.onerror = function(message, url, line, opt_col, opt_error) { + if (oldErrorHandler) { + oldErrorHandler(message, url, line, opt_col, opt_error); + } + logFunc({ + message: message, + fileName: url, + line: line, + lineNumber: line, + col: opt_col, + error: opt_error + }); + return retVal; + }; +}; + + +/** + * Creates a string representing an object and all its properties. + * @param {Object|null|undefined} obj Object to expose. + * @param {boolean=} opt_showFn Show the functions as well as the properties, + * default is false. + * @return {string} The string representation of {@code obj}. + */ +goog.debug.expose = function(obj, opt_showFn) { + if (typeof obj == 'undefined') { + return 'undefined'; + } + if (obj == null) { + return 'NULL'; + } + var str = []; + + for (var x in obj) { + if (!opt_showFn && goog.isFunction(obj[x])) { + continue; + } + var s = x + ' = '; + + try { + s += obj[x]; + } catch (e) { + s += '*** ' + e + ' ***'; + } + str.push(s); + } + return str.join('\n'); +}; + + +/** + * Creates a string representing a given primitive or object, and for an + * object, all its properties and nested objects. NOTE: The output will include + * Uids on all objects that were exposed. Any added Uids will be removed before + * returning. + * @param {*} obj Object to expose. + * @param {boolean=} opt_showFn Also show properties that are functions (by + * default, functions are omitted). + * @return {string} A string representation of {@code obj}. + */ +goog.debug.deepExpose = function(obj, opt_showFn) { + var str = []; + + // Track any objects where deepExpose added a Uid, so they can be cleaned up + // before return. We do this globally, rather than only on ancestors so that + // if the same object appears in the output, you can see it. + var uidsToCleanup = []; + var ancestorUids = {}; + + var helper = function(obj, space) { + var nestspace = space + ' '; + + var indentMultiline = function(str) { + return str.replace(/\n/g, '\n' + space); + }; + + + try { + if (!goog.isDef(obj)) { + str.push('undefined'); + } else if (goog.isNull(obj)) { + str.push('NULL'); + } else if (goog.isString(obj)) { + str.push('"' + indentMultiline(obj) + '"'); + } else if (goog.isFunction(obj)) { + str.push(indentMultiline(String(obj))); + } else if (goog.isObject(obj)) { + // Add a Uid if needed. The struct calls implicitly adds them. + if (!goog.hasUid(obj)) { + uidsToCleanup.push(obj); + } + var uid = goog.getUid(obj); + if (ancestorUids[uid]) { + str.push('*** reference loop detected (id=' + uid + ') ***'); + } else { + ancestorUids[uid] = true; + str.push('{'); + for (var x in obj) { + if (!opt_showFn && goog.isFunction(obj[x])) { + continue; + } + str.push('\n'); + str.push(nestspace); + str.push(x + ' = '); + helper(obj[x], nestspace); + } + str.push('\n' + space + '}'); + delete ancestorUids[uid]; + } + } else { + str.push(obj); + } + } catch (e) { + str.push('*** ' + e + ' ***'); + } + }; + + helper(obj, ''); + + // Cleanup any Uids that were added by the deepExpose. + for (var i = 0; i < uidsToCleanup.length; i++) { + goog.removeUid(uidsToCleanup[i]); + } + + return str.join(''); +}; + + +/** + * Recursively outputs a nested array as a string. + * @param {Array<?>} arr The array. + * @return {string} String representing nested array. + */ +goog.debug.exposeArray = function(arr) { + var str = []; + for (var i = 0; i < arr.length; i++) { + if (goog.isArray(arr[i])) { + str.push(goog.debug.exposeArray(arr[i])); + } else { + str.push(arr[i]); + } + } + return '[ ' + str.join(', ') + ' ]'; +}; + + +/** + * Normalizes the error/exception object between browsers. + * @param {*} err Raw error object. + * @return {!{ + * message: (?|undefined), + * name: (?|undefined), + * lineNumber: (?|undefined), + * fileName: (?|undefined), + * stack: (?|undefined) + * }} Normalized error object. + */ +goog.debug.normalizeErrorObject = function(err) { + var href = goog.getObjectByName('window.location.href'); + if (goog.isString(err)) { + return { + 'message': err, + 'name': 'Unknown error', + 'lineNumber': 'Not available', + 'fileName': href, + 'stack': 'Not available' + }; + } + + var lineNumber, fileName; + var threwError = false; + + try { + lineNumber = err.lineNumber || err.line || 'Not available'; + } catch (e) { + // Firefox 2 sometimes throws an error when accessing 'lineNumber': + // Message: Permission denied to get property UnnamedClass.lineNumber + lineNumber = 'Not available'; + threwError = true; + } + + try { + fileName = err.fileName || err.filename || err.sourceURL || + // $googDebugFname may be set before a call to eval to set the filename + // that the eval is supposed to present. + goog.global['$googDebugFname'] || href; + } catch (e) { + // Firefox 2 may also throw an error when accessing 'filename'. + fileName = 'Not available'; + threwError = true; + } + + // The IE Error object contains only the name and the message. + // The Safari Error object uses the line and sourceURL fields. + if (threwError || !err.lineNumber || !err.fileName || !err.stack || + !err.message || !err.name) { + return { + 'message': err.message || 'Not available', + 'name': err.name || 'UnknownError', + 'lineNumber': lineNumber, + 'fileName': fileName, + 'stack': err.stack || 'Not available' + }; + } + + // Standards error object + // Typed !Object. Should be a subtype of the return type, but it's not. + return /** @type {?} */ (err); +}; + + +/** + * Converts an object to an Error using the object's toString if it's not + * already an Error, adds a stacktrace if there isn't one, and optionally adds + * an extra message. + * @param {*} err The original thrown error, object, or string. + * @param {string=} opt_message optional additional message to add to the + * error. + * @return {!Error} If err is an Error, it is enhanced and returned. Otherwise, + * it is converted to an Error which is enhanced and returned. + */ +goog.debug.enhanceError = function(err, opt_message) { + var error; + if (!(err instanceof Error)) { + error = Error(err); + if (Error.captureStackTrace) { + // Trim this function off the call stack, if we can. + Error.captureStackTrace(error, goog.debug.enhanceError); + } + } else { + error = err; + } + + if (!error.stack) { + error.stack = goog.debug.getStacktrace(goog.debug.enhanceError); + } + if (opt_message) { + // find the first unoccupied 'messageX' property + var x = 0; + while (error['message' + x]) { + ++x; + } + error['message' + x] = String(opt_message); + } + return error; +}; + + +/** + * Converts an object to an Error using the object's toString if it's not + * already an Error, adds a stacktrace if there isn't one, and optionally adds + * context to the Error, which is reported by the closure error reporter. + * @param {*} err The original thrown error, object, or string. + * @param {!Object<string, string>=} opt_context Key-value context to add to the + * Error. + * @return {!Error} If err is an Error, it is enhanced and returned. Otherwise, + * it is converted to an Error which is enhanced and returned. + */ +goog.debug.enhanceErrorWithContext = function(err, opt_context) { + var error = goog.debug.enhanceError(err); + if (opt_context) { + for (var key in opt_context) { + goog.debug.errorcontext.addErrorContext(error, key, opt_context[key]); + } + } + return error; +}; + + +/** + * Gets the current stack trace. Simple and iterative - doesn't worry about + * catching circular references or getting the args. + * @param {number=} opt_depth Optional maximum depth to trace back to. + * @return {string} A string with the function names of all functions in the + * stack, separated by \n. + * @suppress {es5Strict} + */ +goog.debug.getStacktraceSimple = function(opt_depth) { + if (!goog.debug.FORCE_SLOPPY_STACKS) { + var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple); + if (stack) { + return stack; + } + // NOTE: browsers that have strict mode support also have native "stack" + // properties. Fall-through for legacy browser support. + } + + var sb = []; + var fn = arguments.callee.caller; + var depth = 0; + + while (fn && (!opt_depth || depth < opt_depth)) { + sb.push(goog.debug.getFunctionName(fn)); + sb.push('()\n'); + + try { + fn = fn.caller; + } catch (e) { + sb.push('[exception trying to get caller]\n'); + break; + } + depth++; + if (depth >= goog.debug.MAX_STACK_DEPTH) { + sb.push('[...long stack...]'); + break; + } + } + if (opt_depth && depth >= opt_depth) { + sb.push('[...reached max depth limit...]'); + } else { + sb.push('[end]'); + } + + return sb.join(''); +}; + + +/** + * Max length of stack to try and output + * @type {number} + */ +goog.debug.MAX_STACK_DEPTH = 50; + + +/** + * @param {Function} fn The function to start getting the trace from. + * @return {?string} + * @private + */ +goog.debug.getNativeStackTrace_ = function(fn) { + var tempErr = new Error(); + if (Error.captureStackTrace) { + Error.captureStackTrace(tempErr, fn); + return String(tempErr.stack); + } else { + // IE10, only adds stack traces when an exception is thrown. + try { + throw tempErr; + } catch (e) { + tempErr = e; + } + var stack = tempErr.stack; + if (stack) { + return String(stack); + } + } + return null; +}; + + +/** + * Gets the current stack trace, either starting from the caller or starting + * from a specified function that's currently on the call stack. + * @param {?Function=} fn If provided, when collecting the stack trace all + * frames above the topmost call to this function, including that call, + * will be left out of the stack trace. + * @return {string} Stack trace. + * @suppress {es5Strict} + */ +goog.debug.getStacktrace = function(fn) { + var stack; + if (!goog.debug.FORCE_SLOPPY_STACKS) { + // Try to get the stack trace from the environment if it is available. + var contextFn = fn || goog.debug.getStacktrace; + stack = goog.debug.getNativeStackTrace_(contextFn); + } + if (!stack) { + // NOTE: browsers that have strict mode support also have native "stack" + // properties. This function will throw in strict mode. + stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []); + } + return stack; +}; + + +/** + * Private helper for getStacktrace(). + * @param {?Function} fn If provided, when collecting the stack trace all + * frames above the topmost call to this function, including that call, + * will be left out of the stack trace. + * @param {Array<!Function>} visited List of functions visited so far. + * @return {string} Stack trace starting from function fn. + * @suppress {es5Strict} + * @private + */ +goog.debug.getStacktraceHelper_ = function(fn, visited) { + var sb = []; + + // Circular reference, certain functions like bind seem to cause a recursive + // loop so we need to catch circular references + if (goog.array.contains(visited, fn)) { + sb.push('[...circular reference...]'); + + // Traverse the call stack until function not found or max depth is reached + } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) { + sb.push(goog.debug.getFunctionName(fn) + '('); + var args = fn.arguments; + // Args may be null for some special functions such as host objects or eval. + for (var i = 0; args && i < args.length; i++) { + if (i > 0) { + sb.push(', '); + } + var argDesc; + var arg = args[i]; + switch (typeof arg) { + case 'object': + argDesc = arg ? 'object' : 'null'; + break; + + case 'string': + argDesc = arg; + break; + + case 'number': + argDesc = String(arg); + break; + + case 'boolean': + argDesc = arg ? 'true' : 'false'; + break; + + case 'function': + argDesc = goog.debug.getFunctionName(arg); + argDesc = argDesc ? argDesc : '[fn]'; + break; + + case 'undefined': + default: + argDesc = typeof arg; + break; + } + + if (argDesc.length > 40) { + argDesc = argDesc.substr(0, 40) + '...'; + } + sb.push(argDesc); + } + visited.push(fn); + sb.push(')\n'); + + try { + sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited)); + } catch (e) { + sb.push('[exception trying to get caller]\n'); + } + + } else if (fn) { + sb.push('[...long stack...]'); + } else { + sb.push('[end]'); + } + return sb.join(''); +}; + + +/** + * Set a custom function name resolver. + * @param {function(Function): string} resolver Resolves functions to their + * names. + */ +goog.debug.setFunctionResolver = function(resolver) { + goog.debug.fnNameResolver_ = resolver; +}; + + +/** + * Gets a function name + * @param {Function} fn Function to get name of. + * @return {string} Function's name. + */ +goog.debug.getFunctionName = function(fn) { + if (goog.debug.fnNameCache_[fn]) { + return goog.debug.fnNameCache_[fn]; + } + if (goog.debug.fnNameResolver_) { + var name = goog.debug.fnNameResolver_(fn); + if (name) { + goog.debug.fnNameCache_[fn] = name; + return name; + } + } + + // Heuristically determine function name based on code. + var functionSource = String(fn); + if (!goog.debug.fnNameCache_[functionSource]) { + var matches = /function ([^\(]+)/.exec(functionSource); + if (matches) { + var method = matches[1]; + goog.debug.fnNameCache_[functionSource] = method; + } else { + goog.debug.fnNameCache_[functionSource] = '[Anonymous]'; + } + } + + return goog.debug.fnNameCache_[functionSource]; +}; + + +/** + * Makes whitespace visible by replacing it with printable characters. + * This is useful in finding diffrences between the expected and the actual + * output strings of a testcase. + * @param {string} string whose whitespace needs to be made visible. + * @return {string} string whose whitespace is made visible. + */ +goog.debug.makeWhitespaceVisible = function(string) { + return string.replace(/ /g, '[_]') + .replace(/\f/g, '[f]') + .replace(/\n/g, '[n]\n') + .replace(/\r/g, '[r]') + .replace(/\t/g, '[t]'); +}; + + +/** + * Returns the type of a value. If a constructor is passed, and a suitable + * string cannot be found, 'unknown type name' will be returned. + * + * <p>Forked rather than moved from {@link goog.asserts.getType_} + * to avoid adding a dependency to goog.asserts. + * @param {*} value A constructor, object, or primitive. + * @return {string} The best display name for the value, or 'unknown type name'. + */ +goog.debug.runtimeType = function(value) { + if (value instanceof Function) { + return value.displayName || value.name || 'unknown type name'; + } else if (value instanceof Object) { + return value.constructor.displayName || value.constructor.name || + Object.prototype.toString.call(value); + } else { + return value === null ? 'null' : typeof value; + } +}; + + +/** + * Hash map for storing function names that have already been looked up. + * @type {Object} + * @private + */ +goog.debug.fnNameCache_ = {}; + + +/** + * Resolves functions to their names. Resolved function names will be cached. + * @type {function(Function):string} + * @private + */ +goog.debug.fnNameResolver_; + + +/** + * Private internal function to support goog.debug.freeze. + * @param {T} arg + * @return {T} + * @template T + * @private + */ +goog.debug.freezeInternal_ = goog.DEBUG && Object.freeze || function(arg) { + return arg; +}; + + +/** + * Freezes the given object, but only in debug mode (and in browsers that + * support it). Note that this is a shallow freeze, so for deeply nested + * objects it must be called at every level to ensure deep immutability. + * @param {T} arg + * @return {T} + * @template T + */ +goog.debug.freeze = function(arg) { + // NOTE: this compiles to nothing, but hides the possible side effect of + // freezeInternal_ from the compiler so that the entire call can be + // removed if the result is not used. + return { + valueOf: function() { + return goog.debug.freezeInternal_(arg); + } + }.valueOf(); +};
diff --git a/third_party/ink/closure/debug/entrypointregistry.js b/third_party/ink/closure/debug/entrypointregistry.js new file mode 100644 index 0000000..336e1468 --- /dev/null +++ b/third_party/ink/closure/debug/entrypointregistry.js
@@ -0,0 +1,159 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A global registry for entry points into a program, + * so that they can be instrumented. Each module should register their + * entry points with this registry. Designed to be compiled out + * if no instrumentation is requested. + * + * Entry points may be registered before or after a call to + * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered + * later, the existing monitor will instrument the new entry point. + * + * @author nicksantos@google.com (Nick Santos) + */ + +goog.provide('goog.debug.EntryPointMonitor'); +goog.provide('goog.debug.entryPointRegistry'); + +goog.require('goog.asserts'); + + + +/** + * @interface + */ +goog.debug.EntryPointMonitor = function() {}; + + +/** + * Instruments a function. + * + * @param {!Function} fn A function to instrument. + * @return {!Function} The instrumented function. + */ +goog.debug.EntryPointMonitor.prototype.wrap; + + +/** + * Try to remove an instrumentation wrapper created by this monitor. + * If the function passed to unwrap is not a wrapper created by this + * monitor, then we will do nothing. + * + * Notice that some wrappers may not be unwrappable. For example, if other + * monitors have applied their own wrappers, then it will be impossible to + * unwrap them because their wrappers will have captured our wrapper. + * + * So it is important that entry points are unwrapped in the reverse + * order that they were wrapped. + * + * @param {!Function} fn A function to unwrap. + * @return {!Function} The unwrapped function, or {@code fn} if it was not + * a wrapped function created by this monitor. + */ +goog.debug.EntryPointMonitor.prototype.unwrap; + + +/** + * An array of entry point callbacks. + * @type {!Array<function(!Function)>} + * @private + */ +goog.debug.entryPointRegistry.refList_ = []; + + +/** + * Monitors that should wrap all the entry points. + * @type {!Array<!goog.debug.EntryPointMonitor>} + * @private + */ +goog.debug.entryPointRegistry.monitors_ = []; + + +/** + * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + * Checking this allows the compiler to optimize out the registrations. + * @type {boolean} + * @private + */ +goog.debug.entryPointRegistry.monitorsMayExist_ = false; + + +/** + * Register an entry point with this module. + * + * The entry point will be instrumented when a monitor is passed to + * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the + * entry point is instrumented immediately. + * + * @param {function(!Function)} callback A callback function which is called + * with a transforming function to instrument the entry point. The callback + * is responsible for wrapping the relevant entry point with the + * transforming function. + */ +goog.debug.entryPointRegistry.register = function(callback) { + // Don't use push(), so that this can be compiled out. + goog.debug.entryPointRegistry + .refList_[goog.debug.entryPointRegistry.refList_.length] = callback; + // If no one calls monitorAll, this can be compiled out. + if (goog.debug.entryPointRegistry.monitorsMayExist_) { + var monitors = goog.debug.entryPointRegistry.monitors_; + for (var i = 0; i < monitors.length; i++) { + callback(goog.bind(monitors[i].wrap, monitors[i])); + } + } +}; + + +/** + * Configures a monitor to wrap all entry points. + * + * Entry points that have already been registered are immediately wrapped by + * the monitor. When an entry point is registered in the future, it will also + * be wrapped by the monitor when it is registered. + * + * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor. + */ +goog.debug.entryPointRegistry.monitorAll = function(monitor) { + goog.debug.entryPointRegistry.monitorsMayExist_ = true; + var transformer = goog.bind(monitor.wrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + goog.debug.entryPointRegistry.monitors_.push(monitor); +}; + + +/** + * Try to unmonitor all the entry points that have already been registered. If + * an entry point is registered in the future, it will not be wrapped by the + * monitor when it is registered. Note that this may fail if the entry points + * have additional wrapping. + * + * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap + * the entry points. + * @throws {Error} If the monitor is not the most recently configured monitor. + */ +goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) { + var monitors = goog.debug.entryPointRegistry.monitors_; + goog.asserts.assert( + monitor == monitors[monitors.length - 1], + 'Only the most recent monitor can be unwrapped.'); + var transformer = goog.bind(monitor.unwrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + monitors.length--; +};
diff --git a/third_party/ink/closure/debug/error.js b/third_party/ink/closure/debug/error.js new file mode 100644 index 0000000..2099b56 --- /dev/null +++ b/third_party/ink/closure/debug/error.js
@@ -0,0 +1,66 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a base class for custom Error objects such that the + * stack is correctly maintained. + * + * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is + * sufficient. + * + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.debug.Error'); + + + +/** + * Base class for custom error objects. + * @param {*=} opt_msg The message associated with the error. + * @constructor + * @extends {Error} + */ +goog.debug.Error = function(opt_msg) { + + // Attempt to ensure there is a stack trace. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, goog.debug.Error); + } else { + var stack = new Error().stack; + if (stack) { + /** @override */ + this.stack = stack; + } + } + + if (opt_msg) { + /** @override */ + this.message = String(opt_msg); + } + + /** + * Whether to report this error to the server. Setting this to false will + * cause the error reporter to not report the error back to the server, + * which can be useful if the client knows that the error has already been + * logged on the server. + * @type {boolean} + */ + this.reportErrorToServer = true; +}; +goog.inherits(goog.debug.Error, Error); + + +/** @override */ +goog.debug.Error.prototype.name = 'CustomError';
diff --git a/third_party/ink/closure/debug/errorcontext.js b/third_party/ink/closure/debug/errorcontext.js new file mode 100644 index 0000000..683454a3 --- /dev/null +++ b/third_party/ink/closure/debug/errorcontext.js
@@ -0,0 +1,49 @@ +// Copyright 2017 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides methods dealing with context on error objects. + */ + +goog.provide('goog.debug.errorcontext'); + + +/** + * Adds key-value context to the error. + * @param {!Error} err The error to add context to. + * @param {string} contextKey Key for the context to be added. + * @param {string} contextValue Value for the context to be added. + */ +goog.debug.errorcontext.addErrorContext = function( + err, contextKey, contextValue) { + if (!err[goog.debug.errorcontext.CONTEXT_KEY_]) { + err[goog.debug.errorcontext.CONTEXT_KEY_] = {}; + } + err[goog.debug.errorcontext.CONTEXT_KEY_][contextKey] = contextValue; +}; + + +/** + * @param {!Error} err The error to get context from. + * @return {!Object<string, string>} The context of the provided error. + */ +goog.debug.errorcontext.getErrorContext = function(err) { + return err[goog.debug.errorcontext.CONTEXT_KEY_] || {}; +}; + + +// TODO(aaronsn): convert this to a Symbol once goog.debug.ErrorReporter is +// able to use ES6. +/** @private @const {string} */ +goog.debug.errorcontext.CONTEXT_KEY_ = '__closure__error__context__984382';
diff --git a/third_party/ink/closure/disposable/disposable.js b/third_party/ink/closure/disposable/disposable.js new file mode 100644 index 0000000..b3e8138 --- /dev/null +++ b/third_party/ink/closure/disposable/disposable.js
@@ -0,0 +1,305 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Implements the disposable interface. The dispose method is used + * to clean up references and resources. + * @author arv@google.com (Erik Arvidsson) + */ + + +goog.provide('goog.Disposable'); +goog.provide('goog.dispose'); +goog.provide('goog.disposeAll'); + +goog.require('goog.disposable.IDisposable'); + + + +/** + * Class that provides the basic implementation for disposable objects. If your + * class holds one or more references to COM objects, DOM nodes, or other + * disposable objects, it should extend this class or implement the disposable + * interface (defined in goog.disposable.IDisposable). + * @constructor + * @implements {goog.disposable.IDisposable} + */ +goog.Disposable = function() { + /** + * If monitoring the goog.Disposable instances is enabled, stores the creation + * stack trace of the Disposable instance. + * @type {string|undefined} + */ + this.creationStack; + + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + if (goog.Disposable.INCLUDE_STACK_ON_CREATION) { + this.creationStack = new Error().stack; + } + goog.Disposable.instances_[goog.getUid(this)] = this; + } + // Support sealing + this.disposed_ = this.disposed_; + this.onDisposeCallbacks_ = this.onDisposeCallbacks_; +}; + + +/** + * @enum {number} Different monitoring modes for Disposable. + */ +goog.Disposable.MonitoringMode = { + /** + * No monitoring. + */ + OFF: 0, + /** + * Creating and disposing the goog.Disposable instances is monitored. All + * disposable objects need to call the {@code goog.Disposable} base + * constructor. The PERMANENT mode must be switched on before creating any + * goog.Disposable instances. + */ + PERMANENT: 1, + /** + * INTERACTIVE mode can be switched on and off on the fly without producing + * errors. It also doesn't warn if the disposable objects don't call the + * {@code goog.Disposable} base constructor. + */ + INTERACTIVE: 2 +}; + + +/** + * @define {number} The monitoring mode of the goog.Disposable + * instances. Default is OFF. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.define('goog.Disposable.MONITORING_MODE', 0); + + +/** + * @define {boolean} Whether to attach creation stack to each created disposable + * instance; This is only relevant for when MonitoringMode != OFF. + */ +goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true); + + +/** + * Maps the unique ID of every undisposed {@code goog.Disposable} object to + * the object itself. + * @type {!Object<number, !goog.Disposable>} + * @private + */ +goog.Disposable.instances_ = {}; + + +/** + * @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that + * haven't been disposed of. + */ +goog.Disposable.getUndisposedObjects = function() { + var ret = []; + for (var id in goog.Disposable.instances_) { + if (goog.Disposable.instances_.hasOwnProperty(id)) { + ret.push(goog.Disposable.instances_[Number(id)]); + } + } + return ret; +}; + + +/** + * Clears the registry of undisposed objects but doesn't dispose of them. + */ +goog.Disposable.clearUndisposedObjects = function() { + goog.Disposable.instances_ = {}; +}; + + +/** + * Whether the object has been disposed of. + * @type {boolean} + * @private + */ +goog.Disposable.prototype.disposed_ = false; + + +/** + * Callbacks to invoke when this object is disposed. + * @type {Array<!Function>} + * @private + */ +goog.Disposable.prototype.onDisposeCallbacks_; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @override + */ +goog.Disposable.prototype.isDisposed = function() { + return this.disposed_; +}; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @deprecated Use {@link #isDisposed} instead. + */ +goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed; + + +/** + * Disposes of the object. If the object hasn't already been disposed of, calls + * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should + * override {@link #disposeInternal} in order to delete references to COM + * objects, DOM nodes, and other disposable objects. Reentrant. + * + * @return {void} Nothing. + * @override + */ +goog.Disposable.prototype.dispose = function() { + if (!this.disposed_) { + // Set disposed_ to true first, in case during the chain of disposal this + // gets disposed recursively. + this.disposed_ = true; + this.disposeInternal(); + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + var uid = goog.getUid(this); + if (goog.Disposable.MONITORING_MODE == + goog.Disposable.MonitoringMode.PERMANENT && + !goog.Disposable.instances_.hasOwnProperty(uid)) { + throw new Error( + this + ' did not call the goog.Disposable base ' + + 'constructor or was disposed of after a clearUndisposedObjects ' + + 'call'); + } + delete goog.Disposable.instances_[uid]; + } + } +}; + + +/** + * Associates a disposable object with this object so that they will be disposed + * together. + * @param {goog.disposable.IDisposable} disposable that will be disposed when + * this object is disposed. + */ +goog.Disposable.prototype.registerDisposable = function(disposable) { + this.addOnDisposeCallback(goog.partial(goog.dispose, disposable)); +}; + + +/** + * Invokes a callback function when this object is disposed. Callbacks are + * invoked in the order in which they were added. If a callback is added to + * an already disposed Disposable, it will be called immediately. + * @param {function(this:T):?} callback The callback function. + * @param {T=} opt_scope An optional scope to call the callback in. + * @template T + */ +goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { + if (this.disposed_) { + goog.isDef(opt_scope) ? callback.call(opt_scope) : callback(); + return; + } + if (!this.onDisposeCallbacks_) { + this.onDisposeCallbacks_ = []; + } + + this.onDisposeCallbacks_.push( + goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback); +}; + + +/** + * Deletes or nulls out any references to COM objects, DOM nodes, or other + * disposable objects. Classes that extend {@code goog.Disposable} should + * override this method. + * Not reentrant. To avoid calling it twice, it must only be called from the + * subclass' {@code disposeInternal} method. Everywhere else the public + * {@code dispose} method must be used. + * For example: + * <pre> + * mypackage.MyClass = function() { + * mypackage.MyClass.base(this, 'constructor'); + * // Constructor logic specific to MyClass. + * ... + * }; + * goog.inherits(mypackage.MyClass, goog.Disposable); + * + * mypackage.MyClass.prototype.disposeInternal = function() { + * // Dispose logic specific to MyClass. + * ... + * // Call superclass's disposeInternal at the end of the subclass's, like + * // in C++, to avoid hard-to-catch issues. + * mypackage.MyClass.base(this, 'disposeInternal'); + * }; + * </pre> + * @protected + */ +goog.Disposable.prototype.disposeInternal = function() { + if (this.onDisposeCallbacks_) { + while (this.onDisposeCallbacks_.length) { + this.onDisposeCallbacks_.shift()(); + } + } +}; + + +/** + * Returns True if we can verify the object is disposed. + * Calls {@code isDisposed} on the argument if it supports it. If obj + * is not an object with an isDisposed() method, return false. + * @param {*} obj The object to investigate. + * @return {boolean} True if we can verify the object is disposed. + */ +goog.Disposable.isDisposed = function(obj) { + if (obj && typeof obj.isDisposed == 'function') { + return obj.isDisposed(); + } + return false; +}; + + +/** + * Calls {@code dispose} on the argument if it supports it. If obj is not an + * object with a dispose() method, this is a no-op. + * @param {*} obj The object to dispose of. + */ +goog.dispose = function(obj) { + if (obj && typeof obj.dispose == 'function') { + obj.dispose(); + } +}; + + +/** + * Calls {@code dispose} on each member of the list that supports it. (If the + * member is an ArrayLike, then {@code goog.disposeAll()} will be called + * recursively on each of its members.) If the member is not an object with a + * {@code dispose()} method, then it is ignored. + * @param {...*} var_args The list. + */ +goog.disposeAll = function(var_args) { + for (var i = 0, len = arguments.length; i < len; ++i) { + var disposable = arguments[i]; + if (goog.isArrayLike(disposable)) { + goog.disposeAll.apply(null, disposable); + } else { + goog.dispose(disposable); + } + } +};
diff --git a/third_party/ink/closure/disposable/idisposable.js b/third_party/ink/closure/disposable/idisposable.js new file mode 100644 index 0000000..b539eb6 --- /dev/null +++ b/third_party/ink/closure/disposable/idisposable.js
@@ -0,0 +1,45 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the disposable interface. A disposable object + * has a dispose method to to clean up references and resources. + * @author nnaze@google.com (Nathan Naze) + */ + + +goog.provide('goog.disposable.IDisposable'); + + + +/** + * Interface for a disposable object. If a instance requires cleanup + * (references COM objects, DOM nodes, or other disposable objects), it should + * implement this interface (it may subclass goog.Disposable). + * @record + */ +goog.disposable.IDisposable = function() {}; + + +/** + * Disposes of the object and its resources. + * @return {void} Nothing. + */ +goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod; + + +/** + * @return {boolean} Whether the object has been disposed of. + */ +goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
diff --git a/third_party/ink/closure/dom/asserts.js b/third_party/ink/closure/dom/asserts.js new file mode 100644 index 0000000..e891440 --- /dev/null +++ b/third_party/ink/closure/dom/asserts.js
@@ -0,0 +1,299 @@ +// Copyright 2017 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.dom.asserts'); + +goog.require('goog.asserts'); + +/** + * @fileoverview Custom assertions to ensure that an element has the appropriate + * type. + * + * Using a goog.dom.safe wrapper on an object on the incorrect type (via an + * incorrect static type cast) can result in security bugs: For instance, + * g.d.s.setAnchorHref ensures that the URL assigned to the .href attribute + * satisfies the SafeUrl contract, i.e., is safe to dereference as a hyperlink. + * However, the value assigned to a HTMLLinkElement's .href property requires + * the stronger TrustedResourceUrl contract, since it can refer to a stylesheet. + * Thus, using g.d.s.setAnchorHref on an (incorrectly statically typed) object + * of type HTMLLinkElement can result in a security vulnerability. + * Assertions of the correct run-time type help prevent such incorrect use. + * + * In some cases, code using the DOM API is tested using mock objects (e.g., a + * plain object such as {'href': url} instead of an actual Location object). + * To allow such mocking, the assertions permit objects of types that are not + * relevant DOM API objects at all (for instance, not Element or Location). + * + * Note that instanceof checks don't work straightforwardly in older versions of + * IE, or across frames (see, + * http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object, + * http://stackoverflow.com/questions/26248599/instanceof-htmlelement-in-iframe-is-not-element-or-object). + * + * Hence, these assertions may pass vacuously in such scenarios. The resulting + * risk of security bugs is limited by the following factors: + * - A bug can only arise in scenarios involving incorrect static typing (the + * wrapper methods are statically typed to demand objects of the appropriate, + * precise type). + * - Typically, code is tested and exercised in multiple browsers. + */ + +/** + * Asserts that a given object is a Location. + * + * To permit this assertion to pass in the context of tests where DOM APIs might + * be mocked, also accepts any other type except for subtypes of {!Element}. + * This is to ensure that, for instance, HTMLLinkElement is not being used in + * place of a Location, since this could result in security bugs due to stronger + * contracts required for assignments to the href property of the latter. + * + * @param {?Object} o The object whose type to assert. + * @return {!Location} + */ +goog.dom.asserts.assertIsLocation = function(o) { + if (goog.asserts.ENABLE_ASSERTS) { + var win = goog.dom.asserts.getWindow_(o); + if (typeof win.Location != 'undefined' && + typeof win.Element != 'undefined') { + goog.asserts.assert( + o && (o instanceof win.Location || !(o instanceof win.Element)), + 'Argument is not a Location (or a non-Element mock); got: %s', + goog.dom.asserts.debugStringForType_(o)); + } + } + return /** @type {!Location} */ (o); +}; + + +/** + * Asserts that a given object is either the given subtype of Element + * or a non-Element, non-Location Mock. + * + * To permit this assertion to pass in the context of tests where DOM + * APIs might be mocked, also accepts any other type except for + * subtypes of {!Element}. This is to ensure that, for instance, + * HTMLScriptElement is not being used in place of a HTMLImageElement, + * since this could result in security bugs due to stronger contracts + * required for assignments to the src property of the latter. + * + * The DOM type is looked up in the window the object belongs to. In + * some contexts, this might not be possible (e.g. when running tests + * outside a browser, cross-domain lookup). In this case, the + * assertions are skipped. + * + * @param {?Object} o The object whose type to assert. + * @param {string} typename The name of the DOM type. + * @return {!Element} The object. + * @private + */ +// TODO(bangert): Make an analog of goog.dom.TagName to correctly handle casts? +goog.dom.asserts.assertIsElementType_ = function(o, typename) { + if (goog.asserts.ENABLE_ASSERTS) { + var win = goog.dom.asserts.getWindow_(o); + if (typeof win[typename] != 'undefined' && + typeof win.Location != 'undefined' && + typeof win.Element != 'undefined') { + goog.asserts.assert( + o && + (o instanceof win[typename] || + !((o instanceof win.Location) || (o instanceof win.Element))), + 'Argument is not a %s (or a non-Element, non-Location mock); got: %s', + typename, goog.dom.asserts.debugStringForType_(o)); + } + } + return /** @type {!Element} */ (o); +}; + +/** + * Asserts that a given object is a HTMLAnchorElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not of type Location nor a subtype + * of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLAnchorElement} + */ +goog.dom.asserts.assertIsHTMLAnchorElement = function(o) { + return /** @type {!HTMLAnchorElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLAnchorElement')); +}; + +/** + * Asserts that a given object is a HTMLButtonElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLButtonElement} + */ +goog.dom.asserts.assertIsHTMLButtonElement = function(o) { + return /** @type {!HTMLButtonElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLButtonElement')); +}; + +/** + * Asserts that a given object is a HTMLLinkElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLLinkElement} + */ +goog.dom.asserts.assertIsHTMLLinkElement = function(o) { + return /** @type {!HTMLLinkElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLLinkElement')); +}; + +/** + * Asserts that a given object is a HTMLImageElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLImageElement} + */ +goog.dom.asserts.assertIsHTMLImageElement = function(o) { + return /** @type {!HTMLImageElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLImageElement')); +}; + +/** + * Asserts that a given object is a HTMLInputElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLInputElement} + */ +goog.dom.asserts.assertIsHTMLInputElement = function(o) { + return /** @type {!HTMLInputElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLInputElement')); +}; + +/** + * Asserts that a given object is a HTMLEmbedElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLEmbedElement} + */ +goog.dom.asserts.assertIsHTMLEmbedElement = function(o) { + return /** @type {!HTMLEmbedElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLEmbedElement')); +}; + +/** + * Asserts that a given object is a HTMLFormElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLFormElement} + */ +goog.dom.asserts.assertIsHTMLFormElement = function(o) { + return /** @type {!HTMLFormElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLFormElement')); +}; + +/** + * Asserts that a given object is a HTMLFrameElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLFrameElement} + */ +goog.dom.asserts.assertIsHTMLFrameElement = function(o) { + return /** @type {!HTMLFrameElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLFrameElement')); +}; + +/** + * Asserts that a given object is a HTMLIFrameElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLIFrameElement} + */ +goog.dom.asserts.assertIsHTMLIFrameElement = function(o) { + return /** @type {!HTMLIFrameElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLIFrameElement')); +}; + +/** + * Asserts that a given object is a HTMLObjectElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLObjectElement} + */ +goog.dom.asserts.assertIsHTMLObjectElement = function(o) { + return /** @type {!HTMLObjectElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLObjectElement')); +}; + +/** + * Asserts that a given object is a HTMLScriptElement. + * + * To permit this assertion to pass in the context of tests where elements might + * be mocked, also accepts objects that are not a subtype of Element. + * + * @param {?Object} o The object whose type to assert. + * @return {!HTMLScriptElement} + */ +goog.dom.asserts.assertIsHTMLScriptElement = function(o) { + return /** @type {!HTMLScriptElement} */ ( + goog.dom.asserts.assertIsElementType_(o, 'HTMLScriptElement')); +}; + +/** + * Returns a string representation of a value's type. + * + * @param {*} value An object, or primitive. + * @return {string} The best display name for the value. + * @private + */ +goog.dom.asserts.debugStringForType_ = function(value) { + if (goog.isObject(value)) { + return value.constructor.displayName || value.constructor.name || + Object.prototype.toString.call(value); + } else { + return value === undefined ? 'undefined' : + value === null ? 'null' : typeof value; + } +}; + +/** + * Gets window of element. + * @param {?Object} o + * @return {!Window} + * @private + */ +goog.dom.asserts.getWindow_ = function(o) { + var doc = o && o.ownerDocument; + var win = doc && /** @type {?Window} */ (doc.defaultView || doc.parentWindow); + return win || /** @type {!Window} */ (goog.global); +};
diff --git a/third_party/ink/closure/dom/browserfeature.js b/third_party/ink/closure/dom/browserfeature.js new file mode 100644 index 0000000..2d418ea --- /dev/null +++ b/third_party/ink/closure/dom/browserfeature.js
@@ -0,0 +1,74 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Browser capability checks for the dom package. + * + * @author zhyder@google.com (Zohair Hyder) + */ + + +goog.provide('goog.dom.BrowserFeature'); + +goog.require('goog.userAgent'); + + +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.dom.BrowserFeature = { + /** + * Whether attributes 'name' and 'type' can be added to an element after it's + * created. False in Internet Explorer prior to version 9. + */ + CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: + !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9), + + /** + * Whether we can use element.children to access an element's Element + * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment + * nodes in the collection.) + */ + CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE || + goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) || + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'), + + /** + * Opera, Safari 3, and Internet Explorer 9 all support innerText but they + * include text nodes in script and style tags. Not document-mode-dependent. + */ + CAN_USE_INNER_TEXT: + (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')), + + /** + * MSIE, Opera, and Safari>=4 support element.parentElement to access an + * element's parent if it is an Element. + */ + CAN_USE_PARENT_ELEMENT_PROPERTY: + goog.userAgent.IE || goog.userAgent.OPERA || goog.userAgent.WEBKIT, + + /** + * Whether NoScope elements need a scoped element written before them in + * innerHTML. + * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1 + */ + INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE, + + /** + * Whether we use legacy IE range API. + */ + LEGACY_IE_RANGES: + goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) +};
diff --git a/third_party/ink/closure/dom/classlist.js b/third_party/ink/closure/dom/classlist.js new file mode 100644 index 0000000..0d7afbf --- /dev/null +++ b/third_party/ink/closure/dom/classlist.js
@@ -0,0 +1,276 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for detecting, adding and removing classes. Prefer + * this over goog.dom.classes for new code since it attempts to use classList + * (DOMTokenList: http://dom.spec.whatwg.org/#domtokenlist) which is faster + * and requires less code. + * + * Note: these utilities are meant to operate on HTMLElements + * and may have unexpected behavior on elements with differing interfaces + * (such as SVGElements). + */ + + +goog.provide('goog.dom.classlist'); + +goog.require('goog.array'); + + +/** + * Override this define at build-time if you know your target supports it. + * @define {boolean} Whether to use the classList property (DOMTokenList). + */ +goog.define('goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST', false); + + +/** + * Gets an array-like object of class names on an element. + * @param {Element} element DOM node to get the classes of. + * @return {!IArrayLike<?>} Class names on {@code element}. + */ +goog.dom.classlist.get = function(element) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList; + } + + var className = element.className; + // Some types of elements don't have a className in IE (e.g. iframes). + // Furthermore, in Firefox, className is not a string when the element is + // an SVG element. + return goog.isString(className) && className.match(/\S+/g) || []; +}; + + +/** + * Sets the entire class name of an element. + * @param {Element} element DOM node to set class of. + * @param {string} className Class name(s) to apply to element. + */ +goog.dom.classlist.set = function(element, className) { + element.className = className; +}; + + +/** + * Returns true if an element has a class. This method may throw a DOM + * exception for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to test. + * @param {string} className Class name to test for. + * @return {boolean} Whether element has the class. + */ +goog.dom.classlist.contains = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList.contains(className); + } + return goog.array.contains(goog.dom.classlist.get(element), className); +}; + + +/** + * Adds a class to an element. Does not add multiples of class names. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {Element} element DOM node to add class to. + * @param {string} className Class name to add. + */ +goog.dom.classlist.add = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.add(className); + return; + } + + if (!goog.dom.classlist.contains(element, className)) { + // Ensure we add a space if this is not the first class name added. + element.className += + element.className.length > 0 ? (' ' + className) : className; + } +}; + + +/** + * Convenience method to add a number of class names at once. + * @param {Element} element The element to which to add classes. + * @param {IArrayLike<string>} classesToAdd An array-like object + * containing a collection of class names to add to the element. + * This method may throw a DOM exception if classesToAdd contains invalid + * or empty class names. + */ +goog.dom.classlist.addAll = function(element, classesToAdd) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToAdd, function(className) { + goog.dom.classlist.add(element, className); + }); + return; + } + + var classMap = {}; + + // Get all current class names into a map. + goog.array.forEach(goog.dom.classlist.get(element), function(className) { + classMap[className] = true; + }); + + // Add new class names to the map. + goog.array.forEach( + classesToAdd, function(className) { classMap[className] = true; }); + + // Flatten the keys of the map into the className. + element.className = ''; + for (var className in classMap) { + element.className += + element.className.length > 0 ? (' ' + className) : className; + } +}; + + +/** + * Removes a class from an element. This method may throw a DOM exception + * for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to remove class from. + * @param {string} className Class name to remove. + */ +goog.dom.classlist.remove = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.remove(className); + return; + } + + if (goog.dom.classlist.contains(element, className)) { + // Filter out the class name. + element.className = goog.array + .filter( + goog.dom.classlist.get(element), + function(c) { return c != className; }) + .join(' '); + } +}; + + +/** + * Removes a set of classes from an element. Prefer this call to + * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove + * a large set of class names at once. + * @param {Element} element The element from which to remove classes. + * @param {IArrayLike<string>} classesToRemove An array-like object + * containing a collection of class names to remove from the element. + * This method may throw a DOM exception if classesToRemove contains invalid + * or empty class names. + */ +goog.dom.classlist.removeAll = function(element, classesToRemove) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToRemove, function(className) { + goog.dom.classlist.remove(element, className); + }); + return; + } + // Filter out those classes in classesToRemove. + element.className = + goog.array + .filter( + goog.dom.classlist.get(element), + function(className) { + // If this class is not one we are trying to remove, + // add it to the array of new class names. + return !goog.array.contains(classesToRemove, className); + }) + .join(' '); +}; + + +/** + * Adds or removes a class depending on the enabled argument. This method + * may throw a DOM exception for an invalid or empty class name if DOMTokenList + * is used. + * @param {Element} element DOM node to add or remove the class on. + * @param {string} className Class name to add or remove. + * @param {boolean} enabled Whether to add or remove the class (true adds, + * false removes). + */ +goog.dom.classlist.enable = function(element, className, enabled) { + if (enabled) { + goog.dom.classlist.add(element, className); + } else { + goog.dom.classlist.remove(element, className); + } +}; + + +/** + * Adds or removes a set of classes depending on the enabled argument. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {!Element} element DOM node to add or remove the class on. + * @param {?IArrayLike<string>} classesToEnable An array-like object + * containing a collection of class names to add or remove from the element. + * @param {boolean} enabled Whether to add or remove the classes (true adds, + * false removes). + */ +goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) { + var f = enabled ? goog.dom.classlist.addAll : goog.dom.classlist.removeAll; + f(element, classesToEnable); +}; + + +/** + * Switches a class on an element from one to another without disturbing other + * classes. If the fromClass isn't removed, the toClass won't be added. This + * method may throw a DOM exception if the class names are empty or invalid. + * @param {Element} element DOM node to swap classes on. + * @param {string} fromClass Class to remove. + * @param {string} toClass Class to add. + * @return {boolean} Whether classes were switched. + */ +goog.dom.classlist.swap = function(element, fromClass, toClass) { + if (goog.dom.classlist.contains(element, fromClass)) { + goog.dom.classlist.remove(element, fromClass); + goog.dom.classlist.add(element, toClass); + return true; + } + return false; +}; + + +/** + * Removes a class if an element has it, and adds it the element doesn't have + * it. Won't affect other classes on the node. This method may throw a DOM + * exception if the class name is empty or invalid. + * @param {Element} element DOM node to toggle class on. + * @param {string} className Class to toggle. + * @return {boolean} True if class was added, false if it was removed + * (in other words, whether element has the class after this function has + * been called). + */ +goog.dom.classlist.toggle = function(element, className) { + var add = !goog.dom.classlist.contains(element, className); + goog.dom.classlist.enable(element, className, add); + return add; +}; + + +/** + * Adds and removes a class of an element. Unlike + * {@link goog.dom.classlist.swap}, this method adds the classToAdd regardless + * of whether the classToRemove was present and had been removed. This method + * may throw a DOM exception if the class names are empty or invalid. + * + * @param {Element} element DOM node to swap classes on. + * @param {string} classToRemove Class to remove. + * @param {string} classToAdd Class to add. + */ +goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) { + goog.dom.classlist.remove(element, classToRemove); + goog.dom.classlist.add(element, classToAdd); +};
diff --git a/third_party/ink/closure/dom/dom.js b/third_party/ink/closure/dom/dom.js new file mode 100644 index 0000000..a330b4c --- /dev/null +++ b/third_party/ink/closure/dom/dom.js
@@ -0,0 +1,3234 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating the browser's Document Object Model + * Inspiration taken *heavily* from mochikit (http://mochikit.com/). + * + * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer + * to a different document object. This is useful if you are working with + * frames or multiple windows. + * + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + */ + + +// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem +// is that getTextContent should mimic the DOM3 textContent. We should add a +// getInnerText (or getText) which tries to return the visible text, innerText. + + +goog.provide('goog.dom'); +goog.provide('goog.dom.Appendable'); +goog.provide('goog.dom.DomHelper'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.BrowserFeature'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.safe'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.uncheckedconversions'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.Unicode'); +goog.require('goog.userAgent'); + + +/** + * @define {boolean} Whether we know at compile time that the browser is in + * quirks mode. + */ +goog.define('goog.dom.ASSUME_QUIRKS_MODE', false); + + +/** + * @define {boolean} Whether we know at compile time that the browser is in + * standards compliance mode. + */ +goog.define('goog.dom.ASSUME_STANDARDS_MODE', false); + + +/** + * Whether we know the compatibility mode at compile time. + * @type {boolean} + * @private + */ +goog.dom.COMPAT_MODE_KNOWN_ = + goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; + + +/** + * Gets the DomHelper object for the document where the element resides. + * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this + * element. + * @return {!goog.dom.DomHelper} The DomHelper. + */ +goog.dom.getDomHelper = function(opt_element) { + return opt_element ? + new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : + (goog.dom.defaultDomHelper_ || + (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper())); +}; + + +/** + * Cached default DOM helper. + * @type {!goog.dom.DomHelper|undefined} + * @private + */ +goog.dom.defaultDomHelper_; + + +/** + * Gets the document object being used by the dom library. + * @return {!Document} Document object. + */ +goog.dom.getDocument = function() { + return document; +}; + + +/** + * Gets an element from the current document by element id. + * + * If an Element is passed in, it is returned. + * + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + */ +goog.dom.getElement = function(element) { + return goog.dom.getElementHelper_(document, element); +}; + + +/** + * Gets an element by id from the given document (if present). + * If an element is given, it is returned. + * @param {!Document} doc + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The resulting element. + * @private + */ +goog.dom.getElementHelper_ = function(doc, element) { + return goog.isString(element) ? doc.getElementById(element) : element; +}; + + +/** + * Gets an element by id, asserting that the element is found. + * + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. + */ +goog.dom.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(document, id); +}; + + +/** + * Helper function for getRequiredElementHelper functions, both static and + * on DomHelper. Asserts the element with the given id exists. + * @param {!Document} doc + * @param {string} id + * @return {!Element} The element with the given ID, if it exists. + * @private + */ +goog.dom.getRequiredElementHelper_ = function(doc, id) { + // To prevent users passing in Elements as is permitted in getElement(). + goog.asserts.assertString(id); + var element = goog.dom.getElementHelper_(doc, id); + element = + goog.asserts.assertElement(element, 'No element found with id: ' + id); + return element; +}; + + +/** + * Alias for getElement. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.getElement} instead. + */ +goog.dom.$ = goog.dom.getElement; + + +/** + * Gets elements by tag name. + * @param {!goog.dom.TagName<T>} tagName + * @param {(!Document|!Element)=} opt_parent Parent element or document where to + * look for elements. Defaults to document. + * @return {!NodeList<R>} List of elements. The members of the list are + * {!Element} if tagName is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.getElementsByTagName = function(tagName, opt_parent) { + var parent = opt_parent || document; + return parent.getElementsByTagName(String(tagName)); +}; + + +/** + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. This function + * is a useful, if limited, way of collecting a list of DOM elements + * with certain characteristics. {@code goog.dom.query} offers a + * more powerful and general solution which allows matching on CSS3 + * selector expressions, but at increased cost in code size. If all you + * need is particular tags belonging to a single class, this function + * is fast and sleek. + * + * Note that tag names are case sensitive in the SVG namespace, and this + * function converts opt_tag to uppercase for comparisons. For queries in the + * SVG namespace you should use querySelector or querySelectorAll instead. + * https://bugzilla.mozilla.org/show_bug.cgi?id=963870 + * https://bugs.webkit.org/show_bug.cgi?id=83438 + * + * @see {goog.dom.query} + * + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {!IArrayLike<R>} Array-like list of elements (only a length property + * and numerical indices are guaranteed to exist). The members of the array + * are {!Element} if opt_tag is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_( + document, opt_tag, opt_class, opt_el); +}; + + +/** + * Gets the first element matching the tag and the class. + * + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {?R} Reference to a DOM node. The return type is {?Element} if + * tagName is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {?HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.getElementByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementByTagNameAndClass_( + document, opt_tag, opt_class, opt_el); +}; + + +/** + * Returns a static, array-like list of the elements with the provided + * className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {!IArrayLike<!Element>} The items found with the class name provided. + */ +goog.dom.getElementsByClass = function(className, opt_el) { + var parent = opt_el || document; + if (goog.dom.canUseQuerySelector_(parent)) { + return parent.querySelectorAll('.' + className); + } + return goog.dom.getElementsByTagNameAndClass_( + document, '*', className, opt_el); +}; + + +/** + * Returns the first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return {Element} The first item with the class name provided. + */ +goog.dom.getElementByClass = function(className, opt_el) { + var parent = opt_el || document; + var retVal = null; + if (parent.getElementsByClassName) { + retVal = parent.getElementsByClassName(className)[0]; + } else { + retVal = + goog.dom.getElementByTagNameAndClass_(document, '*', className, opt_el); + } + return retVal || null; +}; + + +/** + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {!Element|!Document=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. + */ +goog.dom.getRequiredElementByClass = function(className, opt_root) { + var retValue = goog.dom.getElementByClass(className, opt_root); + return goog.asserts.assert( + retValue, 'No element found with className: ' + className); +}; + + +/** + * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and + * fast W3C Selectors API. + * @param {!(Element|Document)} parent The parent document object. + * @return {boolean} whether or not we can use parent.querySelector* APIs. + * @private + */ +goog.dom.canUseQuerySelector_ = function(parent) { + return !!(parent.querySelectorAll && parent.querySelector); +}; + + +/** + * Helper for {@code getElementsByTagNameAndClass}. + * @param {!Document} doc The document to get the elements in. + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {!IArrayLike<R>} Array-like list of elements (only a length property + * and numerical indices are guaranteed to exist). The members of the array + * are {!Element} if opt_tag is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @private + */ +goog.dom.getElementsByTagNameAndClass_ = function( + doc, opt_tag, opt_class, opt_el) { + var parent = opt_el || doc; + var tagName = + (opt_tag && opt_tag != '*') ? String(opt_tag).toUpperCase() : ''; + + if (goog.dom.canUseQuerySelector_(parent) && (tagName || opt_class)) { + var query = tagName + (opt_class ? '.' + opt_class : ''); + return parent.querySelectorAll(query); + } + + // Use the native getElementsByClassName if available, under the assumption + // that even when the tag name is specified, there will be fewer elements to + // filter through when going by class than by tag name + if (opt_class && parent.getElementsByClassName) { + var els = parent.getElementsByClassName(opt_class); + + if (tagName) { + var arrayLike = {}; + var len = 0; + + // Filter for specific tags if requested. + for (var i = 0, el; el = els[i]; i++) { + if (tagName == el.nodeName) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + + return /** @type {!IArrayLike<!Element>} */ (arrayLike); + } else { + return els; + } + } + + var els = parent.getElementsByTagName(tagName || '*'); + + if (opt_class) { + var arrayLike = {}; + var len = 0; + for (var i = 0, el; el = els[i]; i++) { + var className = el.className; + // Check if className has a split function since SVG className does not. + if (typeof className.split == 'function' && + goog.array.contains(className.split(/\s+/), opt_class)) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + return /** @type {!IArrayLike<!Element>} */ (arrayLike); + } else { + return els; + } +}; + + +/** + * Helper for goog.dom.getElementByTagNameAndClass. + * + * @param {!Document} doc The document to get the elements in. + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {?R} Reference to a DOM node. The return type is {?Element} if + * tagName is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {?HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @private + */ +goog.dom.getElementByTagNameAndClass_ = function( + doc, opt_tag, opt_class, opt_el) { + var parent = opt_el || doc; + var tag = (opt_tag && opt_tag != '*') ? String(opt_tag).toUpperCase() : ''; + if (goog.dom.canUseQuerySelector_(parent) && (tag || opt_class)) { + return parent.querySelector(tag + (opt_class ? '.' + opt_class : '')); + } + var elements = + goog.dom.getElementsByTagNameAndClass_(doc, opt_tag, opt_class, opt_el); + return elements[0] || null; +}; + + + +/** + * Alias for {@code getElementsByTagNameAndClass}. + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return {!IArrayLike<R>} Array-like list of elements (only a length property + * and numerical indices are guaranteed to exist). The members of the array + * are {!Element} if opt_tag is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead. + */ +goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; + + +/** + * Sets multiple properties, and sometimes attributes, on an element. Note that + * properties are simply object properties on the element instance, while + * attributes are visible in the DOM. Many properties map to attributes with the + * same names, some with different names, and there are also unmappable cases. + * + * This method sets properties by default (which means that custom attributes + * are not supported). These are the exeptions (some of which is legacy): + * - "style": Even though this is an attribute name, it is translated to a + * property, "style.cssText". Note that this property sanitizes and formats + * its value, unlike the attribute. + * - "class": This is an attribute name, it is translated to the "className" + * property. + * - "for": This is an attribute name, it is translated to the "htmlFor" + * property. + * - Entries in {@see goog.dom.DIRECT_ATTRIBUTE_MAP_} are set as attributes, + * this is probably due to browser quirks. + * - "aria-*", "data-*": Always set as attributes, they have no property + * counterparts. + * + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. + * Property values can be strings or goog.string.TypedString values (such as + * goog.html.SafeUrl). + */ +goog.dom.setProperties = function(element, properties) { + goog.object.forEach(properties, function(val, key) { + if (val && val.implementsGoogStringTypedString) { + val = val.getTypedStringValue(); + } + if (key == 'style') { + element.style.cssText = val; + } else if (key == 'class') { + element.className = val; + } else if (key == 'for') { + element.htmlFor = val; + } else if (goog.dom.DIRECT_ATTRIBUTE_MAP_.hasOwnProperty(key)) { + element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val); + } else if ( + goog.string.startsWith(key, 'aria-') || + goog.string.startsWith(key, 'data-')) { + element.setAttribute(key, val); + } else { + element[key] = val; + } + }); +}; + + +/** + * Map of attributes that should be set using + * element.setAttribute(key, val) instead of element[key] = val. Used + * by goog.dom.setProperties. + * + * @private {!Object<string, string>} + * @const + */ +goog.dom.DIRECT_ATTRIBUTE_MAP_ = { + 'cellpadding': 'cellPadding', + 'cellspacing': 'cellSpacing', + 'colspan': 'colSpan', + 'frameborder': 'frameBorder', + 'height': 'height', + 'maxlength': 'maxLength', + 'nonce': 'nonce', + 'role': 'role', + 'rowspan': 'rowSpan', + 'type': 'type', + 'usemap': 'useMap', + 'valign': 'vAlign', + 'width': 'width' +}; + + +/** + * Gets the dimensions of the viewport. + * + * Gecko Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of document. + * + * Gecko Backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * IE6/7 Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Undefined. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Undefined. + * body.clientHeight Height of document element. + * + * IE5 + IE6/7 Backwards compatible mode: + * docEl.clientWidth 0. + * win.innerWidth Undefined. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight 0. + * win.innerHeight Undefined. + * body.clientHeight Height of viewport excluding scrollbar. + * + * Opera 9 Standards and backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * WebKit: + * Safari 2 + * docEl.clientHeight Same as scrollHeight. + * docEl.clientWidth Same as innerWidth. + * win.innerWidth Width of viewport excluding scrollbar. + * win.innerHeight Height of the viewport including scrollbar. + * frame.innerHeight Height of the viewport exluding scrollbar. + * + * Safari 3 (tested in 522) + * + * docEl.clientWidth Width of viewport excluding scrollbar. + * docEl.clientHeight Height of viewport excluding scrollbar in strict mode. + * body.clientHeight Height of viewport excluding scrollbar in quirks mode. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + */ +goog.dom.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument + return goog.dom.getViewportSize_(opt_window || window); +}; + + +/** + * Helper for {@code getViewportSize}. + * @param {Window} win The window to get the view port size for. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + * @private + */ +goog.dom.getViewportSize_ = function(win) { + var doc = win.document; + var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body; + return new goog.math.Size(el.clientWidth, el.clientHeight); +}; + + +/** + * Calculates the height of the document. + * + * @return {number} The height of the current document. + */ +goog.dom.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(window); +}; + +/** + * Calculates the height of the document of the given window. + * + * @param {!Window} win The window whose document height to retrieve. + * @return {number} The height of the document of the given window. + */ +goog.dom.getDocumentHeightForWindow = function(win) { + return goog.dom.getDocumentHeight_(win); +}; + +/** + * Calculates the height of the document of the given window. + * + * Function code copied from the opensocial gadget api: + * gadgets.window.adjustHeight(opt_height) + * + * @private + * @param {!Window} win The window whose document height to retrieve. + * @return {number} The height of the document of the given window. + */ +goog.dom.getDocumentHeight_ = function(win) { + // NOTE(eae): This method will return the window size rather than the document + // size in webkit quirks mode. + var doc = win.document; + var height = 0; + + if (doc) { + // Calculating inner content height is hard and different between + // browsers rendering in Strict vs. Quirks mode. We use a combination of + // three properties within document.body and document.documentElement: + // - scrollHeight + // - offsetHeight + // - clientHeight + // These values differ significantly between browsers and rendering modes. + // But there are patterns. It just takes a lot of time and persistence + // to figure out. + + var body = doc.body; + var docEl = /** @type {!HTMLElement} */ (doc.documentElement); + if (!(docEl && body)) { + return 0; + } + + // Get the height of the viewport + var vh = goog.dom.getViewportSize_(win).height; + if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { + // In Strict mode: + // The inner content height is contained in either: + // document.documentElement.scrollHeight + // document.documentElement.offsetHeight + // Based on studying the values output by different browsers, + // use the value that's NOT equal to the viewport height found above. + height = + docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight; + } else { + // In Quirks mode: + // documentElement.clientHeight is equal to documentElement.offsetHeight + // except in IE. In most browsers, document.documentElement can be used + // to calculate the inner content height. + // However, in other browsers (e.g. IE), document.body must be used + // instead. How do we know which one to use? + // If document.documentElement.clientHeight does NOT equal + // document.documentElement.offsetHeight, then use document.body. + var sh = docEl.scrollHeight; + var oh = docEl.offsetHeight; + if (docEl.clientHeight != oh) { + sh = body.scrollHeight; + oh = body.offsetHeight; + } + + // Detect whether the inner content height is bigger or smaller + // than the bounding box (viewport). If bigger, take the larger + // value. If smaller, take the smaller value. + if (sh > vh) { + // Content is larger + height = sh > oh ? sh : oh; + } else { + // Content is smaller + height = sh < oh ? sh : oh; + } + } + } + + return height; +}; + + +/** + * Gets the page scroll distance as a coordinate object. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @deprecated Use {@link goog.dom.getDocumentScroll} instead. + */ +goog.dom.getPageScroll = function(opt_window) { + var win = opt_window || goog.global || window; + return goog.dom.getDomHelper(win.document).getDocumentScroll(); +}; + + +/** + * Gets the document scroll distance as a coordinate object. + * + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + */ +goog.dom.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(document); +}; + + +/** + * Helper for {@code getDocumentScroll}. + * + * @param {!Document} doc The document to get the scroll for. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @private + */ +goog.dom.getDocumentScroll_ = function(doc) { + var el = goog.dom.getDocumentScrollElement_(doc); + var win = goog.dom.getWindow_(doc); + if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') && + win.pageYOffset != el.scrollTop) { + // The keyboard on IE10 touch devices shifts the page using the pageYOffset + // without modifying scrollTop. For this case, we want the body scroll + // offsets. + return new goog.math.Coordinate(el.scrollLeft, el.scrollTop); + } + return new goog.math.Coordinate( + win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop); +}; + + +/** + * Gets the document scroll element. + * @return {!Element} Scrolling element. + */ +goog.dom.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(document); +}; + + +/** + * Helper for {@code getDocumentScrollElement}. + * @param {!Document} doc The document to get the scroll element for. + * @return {!Element} Scrolling element. + * @private + */ +goog.dom.getDocumentScrollElement_ = function(doc) { + // Old WebKit needs body.scrollLeft in both quirks mode and strict mode. We + // also default to the documentElement if the document does not have a body + // (e.g. a SVG document). + // Uses http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement to + // avoid trying to guess about browser behavior from the UA string. + if (doc.scrollingElement) { + return doc.scrollingElement; + } + if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) { + return doc.documentElement; + } + return doc.body || doc.documentElement; +}; + + +/** + * Gets the window object associated with the given document. + * + * @param {Document=} opt_doc Document object to get window for. + * @return {!Window} The window associated with the given document. + */ +goog.dom.getWindow = function(opt_doc) { + // TODO(arv): This should not take an argument. + return opt_doc ? goog.dom.getWindow_(opt_doc) : window; +}; + + +/** + * Helper for {@code getWindow}. + * + * @param {!Document} doc Document object to get window for. + * @return {!Window} The window associated with the given document. + * @private + */ +goog.dom.getWindow_ = function(doc) { + return /** @type {!Window} */ (doc.parentWindow || doc.defaultView); +}; + + +/** + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom(goog.dom.TagName.DIV, null, createDom(goog.dom.TagName.P), + * createDom(goog.dom.TagName.P));</code> would return a div with two child + * paragraphs + * + * For passing properties, please see {@link goog.dom.setProperties} for more + * information. + * + * @param {string|!goog.dom.TagName<T>} tagName Tag to create. + * @param {?Object|?Array<string>|string=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or NodeList, + * its elements will be added as childNodes instead. + * @return {R} Reference to a DOM node. The return type is {!Element} if tagName + * is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.createDom = function(tagName, opt_attributes, var_args) { + return goog.dom.createDom_(document, arguments); +}; + + +/** + * Helper for {@code createDom}. + * @param {!Document} doc The document to create the DOM in. + * @param {!Arguments} args Argument object passed from the callers. See + * {@code goog.dom.createDom} for details. + * @return {!Element} Reference to a DOM node. + * @private + */ +goog.dom.createDom_ = function(doc, args) { + var tagName = String(args[0]); + var attributes = args[1]; + + // Internet Explorer is dumb: + // name: https://msdn.microsoft.com/en-us/library/ms534184(v=vs.85).aspx + // type: https://msdn.microsoft.com/en-us/library/ms534700(v=vs.85).aspx + // Also does not allow setting of 'type' attribute on 'input' or 'button'. + if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && + (attributes.name || attributes.type)) { + var tagNameArr = ['<', tagName]; + if (attributes.name) { + tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), '"'); + } + if (attributes.type) { + tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), '"'); + + // Clone attributes map to remove 'type' without mutating the input. + var clone = {}; + goog.object.extend(clone, attributes); + + // JSCompiler can't see how goog.object.extend added this property, + // because it was essentially added by reflection. + // So it needs to be quoted. + delete clone['type']; + + attributes = clone; + } + tagNameArr.push('>'); + tagName = tagNameArr.join(''); + } + + var element = doc.createElement(tagName); + + if (attributes) { + if (goog.isString(attributes)) { + element.className = attributes; + } else if (goog.isArray(attributes)) { + element.className = attributes.join(' '); + } else { + goog.dom.setProperties(element, attributes); + } + } + + if (args.length > 2) { + goog.dom.append_(doc, element, args, 2); + } + + return element; +}; + + +/** + * Appends a node with text or other nodes. + * @param {!Document} doc The document to create new nodes in. + * @param {!Node} parent The node to append nodes to. + * @param {!Arguments} args The values to add. See {@code goog.dom.append}. + * @param {number} startIndex The index of the array to start from. + * @private + */ +goog.dom.append_ = function(doc, parent, args, startIndex) { + function childHandler(child) { + // TODO(pupius): More coercion, ala MochiKit? + if (child) { + parent.appendChild( + goog.isString(child) ? doc.createTextNode(child) : child); + } + } + + for (var i = startIndex; i < args.length; i++) { + var arg = args[i]; + // TODO(attila): Fix isArrayLike to return false for a text node. + if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) { + // If the argument is a node list, not a real array, use a clone, + // because forEach can't be used to mutate a NodeList. + goog.array.forEach( + goog.dom.isNodeList(arg) ? goog.array.toArray(arg) : arg, + childHandler); + } else { + childHandler(arg); + } + } +}; + + +/** + * Alias for {@code createDom}. + * @param {string|!goog.dom.TagName<T>} tagName Tag to create. + * @param {?Object|?Array<string>|string=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array, its + * children will be added as childNodes instead. + * @return {R} Reference to a DOM node. The return type is {!Element} if tagName + * is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @deprecated Use {@link goog.dom.createDom} instead. + */ +goog.dom.$dom = goog.dom.createDom; + + +/** + * Creates a new element. + * @param {string|!goog.dom.TagName<T>} name Tag to create. + * @return {R} The new element. The return type is {!Element} if name is + * a string or a more specific type if it is a member of goog.dom.TagName + * (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.createElement = function(name) { + return goog.dom.createElement_(document, name); +}; + + +/** + * Creates a new element. + * @param {!Document} doc The document to create the element in. + * @param {string|!goog.dom.TagName<T>} name Tag to create. + * @return {R} The new element. The return type is {!Element} if name is + * a string or a more specific type if it is a member of goog.dom.TagName + * (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @private + */ +goog.dom.createElement_ = function(doc, name) { + return doc.createElement(String(name)); +}; + + +/** + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. + */ +goog.dom.createTextNode = function(content) { + return document.createTextNode(String(content)); +}; + + +/** + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!Element} The created table. + */ +goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { + // TODO(mlourenco): Return HTMLTableElement, also in prototype function. + // Callers need to be updated to e.g. not assign numbers to table.cellSpacing. + return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp); +}; + + +/** + * Create a table. + * @param {!Document} doc Document object to use to create the table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean} fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!HTMLTableElement} The created table. + * @private + */ +goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { + var table = goog.dom.createElement_(doc, goog.dom.TagName.TABLE); + var tbody = + table.appendChild(goog.dom.createElement_(doc, goog.dom.TagName.TBODY)); + for (var i = 0; i < rows; i++) { + var tr = goog.dom.createElement_(doc, goog.dom.TagName.TR); + for (var j = 0; j < columns; j++) { + var td = goog.dom.createElement_(doc, goog.dom.TagName.TD); + // IE <= 9 will create a text node if we set text content to the empty + // string, so we avoid doing it unless necessary. This ensures that the + // same DOM tree is returned on all browsers. + if (fillWithNbsp) { + goog.dom.setTextContent(td, goog.string.Unicode.NBSP); + } + tr.appendChild(td); + } + tbody.appendChild(tr); + } + return table; +}; + + + +/** + * Creates a new Node from constant strings of HTML markup. + * @param {...!goog.string.Const} var_args The HTML strings to concatenate then + * convert into a node. + * @return {!Node} + */ +goog.dom.constHtmlToNode = function(var_args) { + var stringArray = goog.array.map(arguments, goog.string.Const.unwrap); + var safeHtml = + goog.html.uncheckedconversions + .safeHtmlFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Constant HTML string, that gets turned into a ' + + 'Node later, so it will be automatically balanced.'), + stringArray.join('')); + return goog.dom.safeHtmlToNode(safeHtml); +}; + + +/** + * Converts HTML markup into a node. This is a safe version of + * {@code goog.dom.htmlToDocumentFragment} which is now deleted. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. + */ +goog.dom.safeHtmlToNode = function(html) { + return goog.dom.safeHtmlToNode_(document, html); +}; + + +/** + * Helper for {@code safeHtmlToNode}. + * @param {!Document} doc The document. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. + * @private + */ +goog.dom.safeHtmlToNode_ = function(doc, html) { + var tempDiv = goog.dom.createElement_(doc, goog.dom.TagName.DIV); + if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { + goog.dom.safe.setInnerHtml( + tempDiv, goog.html.SafeHtml.concat(goog.html.SafeHtml.BR, html)); + tempDiv.removeChild(tempDiv.firstChild); + } else { + goog.dom.safe.setInnerHtml(tempDiv, html); + } + return goog.dom.childrenToNode_(doc, tempDiv); +}; + + +/** + * Helper for {@code safeHtmlToNode_}. + * @param {!Document} doc The document. + * @param {!Node} tempDiv The input node. + * @return {!Node} The resulting node. + * @private + */ +goog.dom.childrenToNode_ = function(doc, tempDiv) { + if (tempDiv.childNodes.length == 1) { + return tempDiv.removeChild(tempDiv.firstChild); + } else { + var fragment = doc.createDocumentFragment(); + while (tempDiv.firstChild) { + fragment.appendChild(tempDiv.firstChild); + } + return fragment; + } +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. + */ +goog.dom.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(document); +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @param {!Document} doc The document to check. + * @return {boolean} True if in CSS1-compatible mode. + * @private + */ +goog.dom.isCss1CompatMode_ = function(doc) { + if (goog.dom.COMPAT_MODE_KNOWN_) { + return goog.dom.ASSUME_STANDARDS_MODE; + } + + return doc.compatMode == 'CSS1Compat'; +}; + + +/** + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * IE natively supports node.canHaveChildren but has inconsistent behavior. + * Prior to IE8 the base tag allows children and in IE9 all nodes return true + * for canHaveChildren. + * + * In practice all non-IE browsers allow you to add children to any node, but + * the behavior is inconsistent: + * + * <pre> + * var a = goog.dom.createElement(goog.dom.TagName.BR); + * a.appendChild(document.createTextNode('foo')); + * a.appendChild(document.createTextNode('bar')); + * console.log(a.childNodes.length); // 2 + * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar" + * </pre> + * + * For more information, see: + * http://dev.w3.org/html5/markup/syntax.html#syntax-elements + * + * TODO(pupius): Rename shouldAllowChildren() ? + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. + */ +goog.dom.canHaveChildren = function(node) { + if (node.nodeType != goog.dom.NodeType.ELEMENT) { + return false; + } + switch (/** @type {!Element} */ (node).tagName) { + case String(goog.dom.TagName.APPLET): + case String(goog.dom.TagName.AREA): + case String(goog.dom.TagName.BASE): + case String(goog.dom.TagName.BR): + case String(goog.dom.TagName.COL): + case String(goog.dom.TagName.COMMAND): + case String(goog.dom.TagName.EMBED): + case String(goog.dom.TagName.FRAME): + case String(goog.dom.TagName.HR): + case String(goog.dom.TagName.IMG): + case String(goog.dom.TagName.INPUT): + case String(goog.dom.TagName.IFRAME): + case String(goog.dom.TagName.ISINDEX): + case String(goog.dom.TagName.KEYGEN): + case String(goog.dom.TagName.LINK): + case String(goog.dom.TagName.NOFRAMES): + case String(goog.dom.TagName.NOSCRIPT): + case String(goog.dom.TagName.META): + case String(goog.dom.TagName.OBJECT): + case String(goog.dom.TagName.PARAM): + case String(goog.dom.TagName.SCRIPT): + case String(goog.dom.TagName.SOURCE): + case String(goog.dom.TagName.STYLE): + case String(goog.dom.TagName.TRACK): + case String(goog.dom.TagName.WBR): + return false; + } + return true; +}; + + +/** + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. + */ +goog.dom.appendChild = function(parent, child) { + parent.appendChild(child); +}; + + +/** + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. + */ +goog.dom.append = function(parent, var_args) { + goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1); +}; + + +/** + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. + */ +goog.dom.removeChildren = function(node) { + // Note: Iterations over live collections can be slow, this is the fastest + // we could find. The double parenthesis are used to prevent JsCompiler and + // strict warnings. + var child; + while ((child = node.firstChild)) { + node.removeChild(child); + } +}; + + +/** + * Inserts a new node before an existing reference node (i.e. as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. + */ +goog.dom.insertSiblingBefore = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode); + } +}; + + +/** + * Inserts a new node after an existing reference node (i.e. as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. + */ +goog.dom.insertSiblingAfter = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode.nextSibling); + } +}; + + +/** + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. + */ +goog.dom.insertChildAt = function(parent, child, index) { + // Note that if the second argument is null, insertBefore + // will append the child at the end of the list of children. + parent.insertBefore(child, parent.childNodes[index] || null); +}; + + +/** + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. + */ +goog.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; + + +/** + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. + */ +goog.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); + } +}; + + +/** + * Flattens an element. That is, removes it and replace it with its children. + * Does nothing if the element is not in the document. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children; or undefined, if the element was not in the document + * to begin with. + */ +goog.dom.flattenElement = function(element) { + var child, parent = element.parentNode; + if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { + // Use IE DOM method (supported by Opera too) if available + if (element.removeNode) { + return /** @type {Element} */ (element.removeNode(false)); + } else { + // Move all children of the original node up one level. + while ((child = element.firstChild)) { + parent.insertBefore(child, element); + } + + // Detach the original element. + return /** @type {Element} */ (goog.dom.removeNode(element)); + } + } +}; + + +/** + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array<!Element>|NodeList<!Element>)} An array or array-like list + * of just the element children of the given element. + */ +goog.dom.getChildren = function(element) { + // We check if the children attribute is supported for child elements + // since IE8 misuses the attribute by also including comments. + if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE && + element.children != undefined) { + return element.children; + } + // Fall back to manually filtering the element's child nodes. + return goog.array.filter(element.childNodes, function(node) { + return node.nodeType == goog.dom.NodeType.ELEMENT; + }); +}; + + +/** + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. + */ +goog.dom.getFirstElementChild = function(node) { + if (goog.isDef(node.firstElementChild)) { + return /** @type {!Element} */ (node).firstElementChild; + } + return goog.dom.getNextElementNode_(node.firstChild, true); +}; + + +/** + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. + */ +goog.dom.getLastElementChild = function(node) { + if (goog.isDef(node.lastElementChild)) { + return /** @type {!Element} */ (node).lastElementChild; + } + return goog.dom.getNextElementNode_(node.lastChild, false); +}; + + +/** + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. + */ +goog.dom.getNextElementSibling = function(node) { + if (goog.isDef(node.nextElementSibling)) { + return /** @type {!Element} */ (node).nextElementSibling; + } + return goog.dom.getNextElementNode_(node.nextSibling, true); +}; + + +/** + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. + */ +goog.dom.getPreviousElementSibling = function(node) { + if (goog.isDef(node.previousElementSibling)) { + return /** @type {!Element} */ (node).previousElementSibling; + } + return goog.dom.getNextElementNode_(node.previousSibling, false); +}; + + +/** + * Returns the first node that is an element in the specified direction, + * starting with {@code node}. + * @param {Node} node The node to get the next element from. + * @param {boolean} forward Whether to look forwards or backwards. + * @return {Element} The first element. + * @private + */ +goog.dom.getNextElementNode_ = function(node, forward) { + while (node && node.nodeType != goog.dom.NodeType.ELEMENT) { + node = forward ? node.nextSibling : node.previousSibling; + } + + return /** @type {Element} */ (node); +}; + + +/** + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. + */ +goog.dom.getNextNode = function(node) { + if (!node) { + return null; + } + + if (node.firstChild) { + return node.firstChild; + } + + while (node && !node.nextSibling) { + node = node.parentNode; + } + + return node ? node.nextSibling : null; +}; + + +/** + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. + */ +goog.dom.getPreviousNode = function(node) { + if (!node) { + return null; + } + + if (!node.previousSibling) { + return node.parentNode; + } + + node = node.previousSibling; + while (node && node.lastChild) { + node = node.lastChild; + } + + return node; +}; + + +/** + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. + */ +goog.dom.isNodeLike = function(obj) { + return goog.isObject(obj) && obj.nodeType > 0; +}; + + +/** + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. + */ +goog.dom.isElement = function(obj) { + return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT; +}; + + +/** + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. + */ +goog.dom.isWindow = function(obj) { + return goog.isObject(obj) && obj['window'] == obj; +}; + + +/** + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. + */ +goog.dom.getParentElement = function(element) { + var parent; + if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) { + var isIe9 = goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9') && + !goog.userAgent.isVersionOrHigher('10'); + // SVG elements in IE9 can't use the parentElement property. + // goog.global['SVGElement'] is not defined in IE9 quirks mode. + if (!(isIe9 && goog.global['SVGElement'] && + element instanceof goog.global['SVGElement'])) { + parent = element.parentElement; + if (parent) { + return parent; + } + } + } + parent = element.parentNode; + return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null; +}; + + +/** + * Whether a node contains another node. + * @param {?Node|undefined} parent The node that should contain the other node. + * @param {?Node|undefined} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. + */ +goog.dom.contains = function(parent, descendant) { + if (!parent || !descendant) { + return false; + } + // We use browser specific methods for this if available since it is faster + // that way. + + // IE DOM + if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { + return parent == descendant || parent.contains(descendant); + } + + // W3C DOM Level 3 + if (typeof parent.compareDocumentPosition != 'undefined') { + return parent == descendant || + Boolean(parent.compareDocumentPosition(descendant) & 16); + } + + // W3C DOM Level 1 + while (descendant && parent != descendant) { + descendant = descendant.parentNode; + } + return descendant == parent; +}; + + +/** + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. + * + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. + */ +goog.dom.compareNodeOrder = function(node1, node2) { + // Fall out quickly for equality. + if (node1 == node2) { + return 0; + } + + // Use compareDocumentPosition where available + if (node1.compareDocumentPosition) { + // 4 is the bitmask for FOLLOWS. + return node1.compareDocumentPosition(node2) & 2 ? 1 : -1; + } + + // Special case for document nodes on IE 7 and 8. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + if (node1.nodeType == goog.dom.NodeType.DOCUMENT) { + return -1; + } + if (node2.nodeType == goog.dom.NodeType.DOCUMENT) { + return 1; + } + } + + // Process in IE using sourceIndex - we check to see if the first node has + // a source index or if its parent has one. + if ('sourceIndex' in node1 || + (node1.parentNode && 'sourceIndex' in node1.parentNode)) { + var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT; + var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; + + if (isElement1 && isElement2) { + return node1.sourceIndex - node2.sourceIndex; + } else { + var parent1 = node1.parentNode; + var parent2 = node2.parentNode; + + if (parent1 == parent2) { + return goog.dom.compareSiblingOrder_(node1, node2); + } + + if (!isElement1 && goog.dom.contains(parent1, node2)) { + return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2); + } + + + if (!isElement2 && goog.dom.contains(parent2, node1)) { + return goog.dom.compareParentsDescendantNodeIe_(node2, node1); + } + + return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) - + (isElement2 ? node2.sourceIndex : parent2.sourceIndex); + } + } + + // For Safari, we compare ranges. + var doc = goog.dom.getOwnerDocument(node1); + + var range1, range2; + range1 = doc.createRange(); + range1.selectNode(node1); + range1.collapse(true); + + range2 = doc.createRange(); + range2.selectNode(node2); + range2.collapse(true); + + return range1.compareBoundaryPoints( + goog.global['Range'].START_TO_END, range2); +}; + + +/** + * Utility function to compare the position of two nodes, when + * {@code textNode}'s parent is an ancestor of {@code node}. If this entry + * condition is not met, this function will attempt to reference a null object. + * @param {!Node} textNode The textNode to compare. + * @param {Node} node The node to compare. + * @return {number} -1 if node is before textNode, +1 otherwise. + * @private + */ +goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { + var parent = textNode.parentNode; + if (parent == node) { + // If textNode is a child of node, then node comes first. + return -1; + } + var sibling = node; + while (sibling.parentNode != parent) { + sibling = sibling.parentNode; + } + return goog.dom.compareSiblingOrder_(sibling, textNode); +}; + + +/** + * Utility function to compare the position of two nodes known to be non-equal + * siblings. + * @param {Node} node1 The first node to compare. + * @param {!Node} node2 The second node to compare. + * @return {number} -1 if node1 is before node2, +1 otherwise. + * @private + */ +goog.dom.compareSiblingOrder_ = function(node1, node2) { + var s = node2; + while ((s = s.previousSibling)) { + if (s == node1) { + // We just found node1 before node2. + return -1; + } + } + + // Since we didn't find it, node1 must be after node2. + return 1; +}; + + +/** + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. + */ +goog.dom.findCommonAncestor = function(var_args) { + var i, count = arguments.length; + if (!count) { + return null; + } else if (count == 1) { + return arguments[0]; + } + + var paths = []; + var minLength = Infinity; + for (i = 0; i < count; i++) { + // Compute the list of ancestors. + var ancestors = []; + var node = arguments[i]; + while (node) { + ancestors.unshift(node); + node = node.parentNode; + } + + // Save the list for comparison. + paths.push(ancestors); + minLength = Math.min(minLength, ancestors.length); + } + var output = null; + for (i = 0; i < minLength; i++) { + var first = paths[0][i]; + for (var j = 1; j < count; j++) { + if (first != paths[j][i]) { + return output; + } + } + output = first; + } + return output; +}; + + +/** + * Returns the owner document for a node. + * @param {Node|Window} node The node to get the document for. + * @return {!Document} The document owning the node. + */ +goog.dom.getOwnerDocument = function(node) { + // TODO(nnaze): Update param signature to be non-nullable. + goog.asserts.assert(node, 'Node cannot be null or undefined.'); + return /** @type {!Document} */ ( + node.nodeType == goog.dom.NodeType.DOCUMENT ? node : node.ownerDocument || + node.document); +}; + + +/** + * Cross-browser function for getting the document element of a frame or iframe. + * @param {Element} frame Frame element. + * @return {!Document} The frame content document. + */ +goog.dom.getFrameContentDocument = function(frame) { + return frame.contentDocument || + /** @type {!HTMLFrameElement} */ (frame).contentWindow.document; +}; + + +/** + * Cross-browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame, or null if none + * exists. + */ +goog.dom.getFrameContentWindow = function(frame) { + try { + return frame.contentWindow || + (frame.contentDocument ? goog.dom.getWindow(frame.contentDocument) : + null); + } catch (e) { + // NOTE(jfedor): In IE8, checking the contentWindow or contentDocument + // properties will throw a "Unspecified Error" exception if the iframe is + // not inserted in the DOM. If we get this we can be sure that no window + // exists, so return null. + } + return null; +}; + + +/** + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. + */ +goog.dom.setTextContent = function(node, text) { + goog.asserts.assert( + node != null, + 'goog.dom.setTextContent expects a non-null value for node'); + + if ('textContent' in node) { + node.textContent = text; + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + /** @type {!Text} */ (node).data = String(text); + } else if ( + node.firstChild && node.firstChild.nodeType == goog.dom.NodeType.TEXT) { + // If the first child is a text node we just change its data and remove the + // rest of the children. + while (node.lastChild != node.firstChild) { + node.removeChild(node.lastChild); + } + /** @type {!Text} */ (node.firstChild).data = String(text); + } else { + goog.dom.removeChildren(node); + var doc = goog.dom.getOwnerDocument(node); + node.appendChild(doc.createTextNode(String(text))); + } +}; + + +/** + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. + */ +goog.dom.getOuterHtml = function(element) { + goog.asserts.assert( + element !== null, + 'goog.dom.getOuterHtml expects a non-null value for element'); + // IE, Opera and WebKit all have outerHTML. + if ('outerHTML' in element) { + return element.outerHTML; + } else { + var doc = goog.dom.getOwnerDocument(element); + var div = goog.dom.createElement_(doc, goog.dom.TagName.DIV); + div.appendChild(element.cloneNode(true)); + return div.innerHTML; + } +}; + + +/** + * Finds the first descendant node that matches the filter function, using + * a depth first search. This function offers the most general purpose way + * of finding a matching element. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + * @see goog.dom.query + * + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. + */ +goog.dom.findNode = function(root, p) { + var rv = []; + var found = goog.dom.findNodes_(root, p, rv, true); + return found ? rv[0] : undefined; +}; + + +/** + * Finds all the descendant nodes that match the filter function, using a + * a depth first search. This function offers the most general-purpose way + * of finding a set of matching elements. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {!Array<!Node>} The found nodes or an empty array if none are found. + */ +goog.dom.findNodes = function(root, p) { + var rv = []; + goog.dom.findNodes_(root, p, rv, false); + return rv; +}; + + +/** + * Finds the first or all the descendant nodes that match the filter function, + * using a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @param {!Array<!Node>} rv The found nodes are added to this array. + * @param {boolean} findOne If true we exit after the first found node. + * @return {boolean} Whether the search is complete or not. True in case findOne + * is true and the node is found. False otherwise. + * @private + */ +goog.dom.findNodes_ = function(root, p, rv, findOne) { + if (root != null) { + var child = root.firstChild; + while (child) { + if (p(child)) { + rv.push(child); + if (findOne) { + return true; + } + } + if (goog.dom.findNodes_(child, p, rv, findOne)) { + return true; + } + child = child.nextSibling; + } + } + return false; +}; + + +/** + * Map of tags whose content to ignore when calculating text length. + * @private {!Object<string, number>} + * @const + */ +goog.dom.TAGS_TO_IGNORE_ = { + 'SCRIPT': 1, + 'STYLE': 1, + 'HEAD': 1, + 'IFRAME': 1, + 'OBJECT': 1 +}; + + +/** + * Map of tags which have predefined values with regard to whitespace. + * @private {!Object<string, string>} + * @const + */ +goog.dom.PREDEFINED_TAG_VALUES_ = { + 'IMG': ' ', + 'BR': '\n' +}; + + +/** + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. + */ +goog.dom.isFocusableTabIndex = function(element) { + return goog.dom.hasSpecifiedTabIndex_(element) && + goog.dom.isTabIndexFocusable_(element); +}; + + +/** + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. + */ +goog.dom.setFocusableTabIndex = function(element, enable) { + if (enable) { + element.tabIndex = 0; + } else { + // Set tabIndex to -1 first, then remove it. This is a workaround for + // Safari (confirmed in version 4 on Windows). When removing the attribute + // without setting it to -1 first, the element remains keyboard focusable + // despite not having a tabIndex attribute anymore. + element.tabIndex = -1; + element.removeAttribute('tabIndex'); // Must be camelCase! + } +}; + + +/** + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. + */ +goog.dom.isFocusable = function(element) { + var focusable; + // Some elements can have unspecified tab index and still receive focus. + if (goog.dom.nativelySupportsFocus_(element)) { + // Make sure the element is not disabled ... + focusable = !element.disabled && + // ... and if a tab index is specified, it allows focus. + (!goog.dom.hasSpecifiedTabIndex_(element) || + goog.dom.isTabIndexFocusable_(element)); + } else { + focusable = goog.dom.isFocusableTabIndex(element); + } + + // IE requires elements to be visible in order to focus them. + return focusable && goog.userAgent.IE ? + goog.dom.hasNonZeroBoundingRect_(/** @type {!HTMLElement} */ (element)) : + focusable; +}; + + +/** + * Returns true if the element has a specified tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a specified tab index. + * @private + */ +goog.dom.hasSpecifiedTabIndex_ = function(element) { + // IE8 and below don't support hasAttribute(), instead check whether the + // 'tabindex' attributeNode is specified. Otherwise check hasAttribute(). + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')) { + var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase! + return goog.isDefAndNotNull(attrNode) && attrNode.specified; + } else { + return element.hasAttribute('tabindex'); + } +}; + + +/** + * Returns true if the element's tab index allows the element to be focused. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element's tab index allows focus. + * @private + */ +goog.dom.isTabIndexFocusable_ = function(element) { + var index = /** @type {!HTMLElement} */ (element).tabIndex; + // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534. + return goog.isNumber(index) && index >= 0 && index < 32768; +}; + + +/** + * Returns true if the element is focusable even when tabIndex is not set. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element natively supports focus. + * @private + */ +goog.dom.nativelySupportsFocus_ = function(element) { + return element.tagName == goog.dom.TagName.A || + element.tagName == goog.dom.TagName.INPUT || + element.tagName == goog.dom.TagName.TEXTAREA || + element.tagName == goog.dom.TagName.SELECT || + element.tagName == goog.dom.TagName.BUTTON; +}; + + +/** + * Returns true if the element has a bounding rectangle that would be visible + * (i.e. its width and height are greater than zero). + * @param {!HTMLElement} element Element to check. + * @return {boolean} Whether the element has a non-zero bounding rectangle. + * @private + */ +goog.dom.hasNonZeroBoundingRect_ = function(element) { + var rect; + if (!goog.isFunction(element['getBoundingClientRect']) || + // In IE, getBoundingClientRect throws on detached nodes. + (goog.userAgent.IE && element.parentElement == null)) { + rect = {'height': element.offsetHeight, 'width': element.offsetWidth}; + } else { + rect = element.getBoundingClientRect(); + } + return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0; +}; + + +/** + * Returns the text content of the current node, without markup and invisible + * symbols. New lines are stripped and whitespace is collapsed, + * such that each character would be visible. + * + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. + */ +goog.dom.getTextContent = function(node) { + var textContent; + // Note(arv): IE9, Opera, and Safari 3 support innerText but they include + // text nodes in script tags. So we revert to use a user agent test here. + if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && node !== null && + ('innerText' in node)) { + textContent = goog.string.canonicalizeNewlines(node.innerText); + // Unfortunately .innerText() returns text with ­ symbols + // We need to filter it out and then remove duplicate whitespaces + } else { + var buf = []; + goog.dom.getTextContent_(node, buf, true); + textContent = buf.join(''); + } + + // Strip ­ entities. goog.format.insertWordBreaks inserts them in Opera. + textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, ''); + // Strip ​ entities. goog.format.insertWordBreaks inserts them in IE8. + textContent = textContent.replace(/\u200B/g, ''); + + // Skip this replacement on old browsers with working innerText, which + // automatically turns into ' ' and / +/ into ' ' when reading + // innerText. + if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) { + textContent = textContent.replace(/ +/g, ' '); + } + if (textContent != ' ') { + textContent = textContent.replace(/^\s*/, ''); + } + + return textContent; +}; + + +/** + * Returns the text content of the current node, without markup. + * + * Unlike {@code getTextContent} this method does not collapse whitespaces + * or normalize lines breaks. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The raw text content. + */ +goog.dom.getRawTextContent = function(node) { + var buf = []; + goog.dom.getTextContent_(node, buf, false); + + return buf.join(''); +}; + + +/** + * Recursive support function for text content retrieval. + * + * @param {Node} node The node from which we are getting content. + * @param {Array<string>} buf string buffer. + * @param {boolean} normalizeWhitespace Whether to normalize whitespace. + * @private + */ +goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { + if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + if (normalizeWhitespace) { + buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + buf.push(node.nodeValue); + } + } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]); + } else { + var child = node.firstChild; + while (child) { + goog.dom.getTextContent_(child, buf, normalizeWhitespace); + child = child.nextSibling; + } + } +}; + + +/** + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. + */ +goog.dom.getNodeTextLength = function(node) { + return goog.dom.getTextContent(node).length; +}; + + +/** + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by goog.dom.getNodeTextLength. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent The node relative to which the offset will + * be calculated. Defaults to the node's owner document's body. + * @return {number} The text offset. + */ +goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { + var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; + var buf = []; + while (node && node != root) { + var cur = node; + while ((cur = cur.previousSibling)) { + buf.unshift(goog.dom.getTextContent(cur)); + } + node = node.parentNode; + } + // Trim left to deal with FF cases when there might be line breaks and empty + // nodes at the front of the text + return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length; +}; + + +/** + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. + */ +goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { + var stack = [parent], pos = 0, cur = null; + while (stack.length > 0 && pos < offset) { + cur = stack.pop(); + if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (cur.nodeType == goog.dom.NodeType.TEXT) { + var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' '); + pos += text.length; + } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length; + } else { + for (var i = cur.childNodes.length - 1; i >= 0; i--) { + stack.push(cur.childNodes[i]); + } + } + } + if (goog.isObject(opt_result)) { + opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; + opt_result.node = cur; + } + + return cur; +}; + + +/** + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. + */ +goog.dom.isNodeList = function(val) { + // TODO(attila): Now the isNodeList is part of goog.dom we can use + // goog.userAgent to make this simpler. + // A NodeList must have a length property of type 'number' on all platforms. + if (val && typeof val.length == 'number') { + // A NodeList is an object everywhere except Safari, where it's a function. + if (goog.isObject(val)) { + // A NodeList must have an item function (on non-IE platforms) or an item + // property of type 'string' (on IE). + return typeof val.item == 'function' || typeof val.item == 'string'; + } else if (goog.isFunction(val)) { + // On Safari, a NodeList is a function with an item property that is also + // a function. + return typeof val.item == 'function'; + } + } + + // Not a NodeList. + return false; +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName<T>|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {?R} The first ancestor that matches the passed criteria, or + * null if no match is found. The return type is {?Element} if opt_tag is + * not a member of goog.dom.TagName or a more specific type if it is (e.g. + * {?HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.getAncestorByTagNameAndClass = function( + element, opt_tag, opt_class, opt_maxSearchSteps) { + if (!opt_tag && !opt_class) { + return null; + } + var tagName = opt_tag ? String(opt_tag).toUpperCase() : null; + return /** @type {Element} */ (goog.dom.getAncestor(element, function(node) { + return (!tagName || node.nodeName == tagName) && + (!opt_class || + goog.isString(node.className) && + goog.array.contains(node.className.split(/\s+/), opt_class)); + }, true, opt_maxSearchSteps)); +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} className The class name to match. + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. + */ +goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) { + return goog.dom.getAncestorByTagNameAndClass( + element, null, className, opt_maxSearchSteps); +}; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. + */ +goog.dom.getAncestor = function( + element, matcher, opt_includeNode, opt_maxSearchSteps) { + if (element && !opt_includeNode) { + element = element.parentNode; + } + var steps = 0; + while (element && + (opt_maxSearchSteps == null || steps <= opt_maxSearchSteps)) { + goog.asserts.assert(element.name != 'parentNode'); + if (matcher(element)) { + return element; + } + element = element.parentNode; + steps++; + } + // Reached the root of the DOM without a match + return null; +}; + + +/** + * Determines the active element in the given document. + * @param {Document} doc The document to look in. + * @return {Element} The active element. + */ +goog.dom.getActiveElement = function(doc) { + try { + return doc && doc.activeElement; + } catch (e) { + // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE + // throws an exception. I'm not 100% sure why, but I suspect it chokes + // on document.activeElement if the activeElement has been recently + // removed from the DOM by a JS operation. + // + // We assume that an exception here simply means + // "there is no active element." + } + + return null; +}; + + +/** + * Gives the current devicePixelRatio. + * + * By default, this is the value of window.devicePixelRatio (which should be + * preferred if present). + * + * If window.devicePixelRatio is not present, the ratio is calculated with + * window.matchMedia, if present. Otherwise, gives 1.0. + * + * Some browsers (including Chrome) consider the browser zoom level in the pixel + * ratio, so the value may change across multiple calls. + * + * @return {number} The number of actual pixels per virtual pixel. + */ +goog.dom.getPixelRatio = function() { + var win = goog.dom.getWindow(); + if (goog.isDef(win.devicePixelRatio)) { + return win.devicePixelRatio; + } else if (win.matchMedia) { + // Should be for IE10 and FF6-17 (this basically clamps to lower) + // Note that the order of these statements is important + return goog.dom.matchesPixelRatio_(3) || goog.dom.matchesPixelRatio_(2) || + goog.dom.matchesPixelRatio_(1.5) || goog.dom.matchesPixelRatio_(1) || + .75; + } + return 1; +}; + + +/** + * Calculates a mediaQuery to check if the current device supports the + * given actual to virtual pixel ratio. + * @param {number} pixelRatio The ratio of actual pixels to virtual pixels. + * @return {number} pixelRatio if applicable, otherwise 0. + * @private + */ +goog.dom.matchesPixelRatio_ = function(pixelRatio) { + var win = goog.dom.getWindow(); + /** + * Due to the 1:96 fixed ratio of CSS in to CSS px, 1dppx is equivalent to + * 96dpi. + * @const {number} + */ + var dpiPerDppx = 96; + var query = + // FF16-17 + '(min-resolution: ' + pixelRatio + 'dppx),' + + // FF6-15 + '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' + + // IE10 (this works for the two browsers above too but I don't want to + // trust the 1:96 fixed ratio magic) + '(min-resolution: ' + (pixelRatio * dpiPerDppx) + 'dpi)'; + return win.matchMedia(query).matches ? pixelRatio : 0; +}; + + +/** + * Gets '2d' context of a canvas. Shortcut for canvas.getContext('2d') with a + * type information. + * @param {!HTMLCanvasElement} canvas + * @return {!CanvasRenderingContext2D} + */ +goog.dom.getCanvasContext2D = function(canvas) { + return /** @type {!CanvasRenderingContext2D} */ (canvas.getContext('2d')); +}; + + + +/** + * Create an instance of a DOM helper with a new document object. + * @param {Document=} opt_document Document object to associate with this + * DOM helper. + * @constructor + */ +goog.dom.DomHelper = function(opt_document) { + /** + * Reference to the document object to use + * @type {!Document} + * @private + */ + this.document_ = opt_document || goog.global.document || document; +}; + + +/** + * Gets the dom helper object for the document where the element resides. + * @param {Node=} opt_node If present, gets the DomHelper for this node. + * @return {!goog.dom.DomHelper} The DomHelper. + */ +goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper; + + +/** + * Sets the document object. + * @param {!Document} document Document object. + */ +goog.dom.DomHelper.prototype.setDocument = function(document) { + this.document_ = document; +}; + + +/** + * Gets the document object being used by the dom library. + * @return {!Document} Document object. + */ +goog.dom.DomHelper.prototype.getDocument = function() { + return this.document_; +}; + + +/** + * Alias for {@code getElementById}. If a DOM node is passed in then we just + * return that. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + */ +goog.dom.DomHelper.prototype.getElement = function(element) { + return goog.dom.getElementHelper_(this.document_, element); +}; + + +/** + * Gets an element by id, asserting that the element is found. + * + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. + */ +goog.dom.DomHelper.prototype.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(this.document_, id); +}; + + +/** + * Alias for {@code getElement}. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead. + */ +goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement; + + +/** + * Gets elements by tag name. + * @param {!goog.dom.TagName<T>} tagName + * @param {(!Document|!Element)=} opt_parent Parent element or document where to + * look for elements. Defaults to document of this DomHelper. + * @return {!NodeList<R>} List of elements. The members of the list are + * {!Element} if tagName is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.getElementsByTagName = + function(tagName, opt_parent) { + var parent = opt_parent || this.document_; + return parent.getElementsByTagName(String(tagName)); +}; + + +/** + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. The returned array is a live + * NodeList or a static list depending on the code path taken. + * + * @see goog.dom.query + * + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name or * for all + * tags. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {!IArrayLike<R>} Array-like list of elements (only a length property + * and numerical indices are guaranteed to exist). The members of the array + * are {!Element} if opt_tag is not a member of goog.dom.TagName or more + * specific types if it is (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function( + opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_( + this.document_, opt_tag, opt_class, opt_el); +}; + + +/** + * Gets the first element matching the tag and the class. + * + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return {?R} Reference to a DOM node. The return type is {?Element} if + * tagName is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {?HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.getElementByTagNameAndClass = function( + opt_tag, opt_class, opt_el) { + return goog.dom.getElementByTagNameAndClass_( + this.document_, opt_tag, opt_class, opt_el); +}; + + +/** + * Returns an array of all the elements with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return {!IArrayLike<!Element>} The items found with the class name provided. + */ +goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementsByClass(className, doc); +}; + + +/** + * Returns the first element we find matching the provided class name. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Element|Document)=} opt_el Optional element to look in. + * @return {Element} The first item found with the class name provided. + */ +goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementByClass(className, doc); +}; + + +/** + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(!Element|!Document)=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item found with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. + */ +goog.dom.DomHelper.prototype.getRequiredElementByClass = function( + className, opt_root) { + var root = opt_root || this.document_; + return goog.dom.getRequiredElementByClass(className, root); +}; + + +/** + * Alias for {@code getElementsByTagNameAndClass}. + * @deprecated Use DomHelper getElementsByTagNameAndClass. + * @see goog.dom.query + * + * @param {(string|?goog.dom.TagName<T>)=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return {!IArrayLike<R>} Array-like list of elements (only a length property + * and numerical indices are guaranteed to exist). The members of the array + * are {!Element} if opt_tag is a string or more specific types if it is + * a member of goog.dom.TagName (e.g. {!HTMLAnchorElement} for + * goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.$$ = + goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; + + +/** + * Sets a number of properties on a node. + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. + */ +goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties; + + +/** + * Gets the dimensions of the viewport. + * @param {Window=} opt_window Optional window element to test. Defaults to + * the window of the Dom Helper. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + */ +goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument. That breaks the rule of a + // a DomHelper representing a single frame/window/document. + return goog.dom.getViewportSize(opt_window || this.getWindow()); +}; + + +/** + * Calculates the height of the document. + * + * @return {number} The height of the document. + */ +goog.dom.DomHelper.prototype.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(this.getWindow()); +}; + + +/** + * Typedef for use with goog.dom.createDom and goog.dom.append. + * @typedef {Object|string|Array|NodeList} + */ +goog.dom.Appendable; + + +/** + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom(goog.dom.TagName.DIV, null, createDom(goog.dom.TagName.P), + * createDom(goog.dom.TagName.P));</code> would return a div with two child + * paragraphs + * + * An easy way to move all child nodes of an existing element to a new parent + * element is: + * <code>createDom(goog.dom.TagName.DIV, null, oldElement.childNodes);</code> + * which will remove all child nodes from the old element and add them as + * child nodes of the new DIV. + * + * @param {string|!goog.dom.TagName<T>} tagName Tag to create. + * @param {?Object|?Array<string>|string=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or + * NodeList, its elements will be added as childNodes instead. + * @return {R} Reference to a DOM node. The return type is {!Element} if tagName + * is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.createDom = function( + tagName, opt_attributes, var_args) { + return goog.dom.createDom_(this.document_, arguments); +}; + + +/** + * Alias for {@code createDom}. + * @param {string|!goog.dom.TagName<T>} tagName Tag to create. + * @param {?Object|?Array<string>|string=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for + * text nodes. If one of the var_args is an array, its children will be + * added as childNodes instead. + * @return {R} Reference to a DOM node. The return type is {!Element} if tagName + * is a string or a more specific type if it is a member of + * goog.dom.TagName (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead. + */ +goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom; + + +/** + * Creates a new element. + * @param {string|!goog.dom.TagName<T>} name Tag to create. + * @return {R} The new element. The return type is {!Element} if name is + * a string or a more specific type if it is a member of goog.dom.TagName + * (e.g. {!HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.createElement = function(name) { + return goog.dom.createElement_(this.document_, name); +}; + + +/** + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. + */ +goog.dom.DomHelper.prototype.createTextNode = function(content) { + return this.document_.createTextNode(String(content)); +}; + + +/** + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!HTMLElement} The created table. + */ +goog.dom.DomHelper.prototype.createTable = function( + rows, columns, opt_fillWithNbsp) { + return goog.dom.createTable_( + this.document_, rows, columns, !!opt_fillWithNbsp); +}; + + +/** + * Converts an HTML into a node or a document fragment. A single Node is used if + * {@code html} only generates a single node. If {@code html} generates multiple + * nodes then these are put inside a {@code DocumentFragment}. This is a safe + * version of {@code goog.dom.DomHelper#htmlToDocumentFragment} which is now + * deleted. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. + */ +goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) { + return goog.dom.safeHtmlToNode_(this.document_, html); +}; + + +/** + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. + */ +goog.dom.DomHelper.prototype.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(this.document_); +}; + + +/** + * Gets the window object associated with the document. + * @return {!Window} The window associated with the given document. + */ +goog.dom.DomHelper.prototype.getWindow = function() { + return goog.dom.getWindow_(this.document_); +}; + + +/** + * Gets the document scroll element. + * @return {!Element} Scrolling element. + */ +goog.dom.DomHelper.prototype.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(this.document_); +}; + + +/** + * Gets the document scroll distance as a coordinate object. + * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'. + */ +goog.dom.DomHelper.prototype.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(this.document_); +}; + + +/** + * Determines the active element in the given document. + * @param {Document=} opt_doc The document to look in. + * @return {Element} The active element. + */ +goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) { + return goog.dom.getActiveElement(opt_doc || this.document_); +}; + + +/** + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. + */ +goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild; + + +/** + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. + */ +goog.dom.DomHelper.prototype.append = goog.dom.append; + + +/** + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. + */ +goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren; + + +/** + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. + */ +goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren; + + +/** + * Inserts a new node before an existing reference node (i.e., as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. + */ +goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore; + + +/** + * Inserts a new node after an existing reference node (i.e., as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. + */ +goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter; + + +/** + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. + */ +goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt; + + +/** + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. + */ +goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode; + + +/** + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. + */ +goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode; + + +/** + * Flattens an element. That is, removes it and replace it with its children. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children, or undefined if the element was already not in the + * document. + */ +goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement; + + +/** + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array<!Element>|NodeList<!Element>)} An array or array-like list + * of just the element children of the given element. + */ +goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren; + + +/** + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getFirstElementChild = + goog.dom.getFirstElementChild; + + +/** + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild; + + +/** + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. + */ +goog.dom.DomHelper.prototype.getNextElementSibling = + goog.dom.getNextElementSibling; + + +/** + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. + */ +goog.dom.DomHelper.prototype.getPreviousElementSibling = + goog.dom.getPreviousElementSibling; + + +/** + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. + */ +goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode; + + +/** + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. + */ +goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode; + + +/** + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. + */ +goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike; + + +/** + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. + */ +goog.dom.DomHelper.prototype.isElement = goog.dom.isElement; + + +/** + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. + */ +goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow; + + +/** + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. + */ +goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement; + + +/** + * Whether a node contains another node. + * @param {Node} parent The node that should contain the other node. + * @param {Node} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. + */ +goog.dom.DomHelper.prototype.contains = goog.dom.contains; + + +/** + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. + * + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. + */ +goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder; + + +/** + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. + */ +goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor; + + +/** + * Returns the owner document for a node. + * @param {Node} node The node to get the document for. + * @return {!Document} The document owning the node. + */ +goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument; + + +/** + * Cross browser function for getting the document element of an iframe. + * @param {Element} iframe Iframe element. + * @return {!Document} The frame content document. + */ +goog.dom.DomHelper.prototype.getFrameContentDocument = + goog.dom.getFrameContentDocument; + + +/** + * Cross browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame. + */ +goog.dom.DomHelper.prototype.getFrameContentWindow = + goog.dom.getFrameContentWindow; + + +/** + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. + */ +goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent; + + +/** + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. + */ +goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml; + + +/** + * Finds the first descendant node that matches the filter function. This does + * a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. + */ +goog.dom.DomHelper.prototype.findNode = goog.dom.findNode; + + +/** + * Finds all the descendant nodes that matches the filter function. This does a + * depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Array<Node>} The found nodes or an empty array if none are found. + */ +goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes; + + +/** + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. + */ +goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex; + + +/** + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. + */ +goog.dom.DomHelper.prototype.setFocusableTabIndex = + goog.dom.setFocusableTabIndex; + + +/** + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. + */ +goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable; + + +/** + * Returns the text contents of the current node, without markup. New lines are + * stripped and whitespace is collapsed, such that each character would be + * visible. + * + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. + */ +goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent; + + +/** + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. + */ +goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength; + + +/** + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by + * {@code goog.dom.getNodeTextLength}. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent Defaults to the node's owner document's body. + * @return {number} The text offset. + */ +goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset; + + +/** + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. + */ +goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset; + + +/** + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. + */ +goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName<T>|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {?R} The first ancestor that matches the passed criteria, or + * null if no match is found. The return type is {?Element} if opt_tag is + * not a member of goog.dom.TagName or a more specific type if it is (e.g. + * {?HTMLAnchorElement} for goog.dom.TagName.A). + * @template T + * @template R := cond(isUnknown(T), 'Element', T) =: + */ +goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = + goog.dom.getAncestorByTagNameAndClass; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} class The class name to match. + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. + */ +goog.dom.DomHelper.prototype.getAncestorByClass = goog.dom.getAncestorByClass; + + +/** + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. + */ +goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor; + + +/** + * Gets '2d' context of a canvas. Shortcut for canvas.getContext('2d') with a + * type information. + * @param {!HTMLCanvasElement} canvas + * @return {!CanvasRenderingContext2D} + */ +goog.dom.DomHelper.prototype.getCanvasContext2D = goog.dom.getCanvasContext2D;
diff --git a/third_party/ink/closure/dom/htmlelement.js b/third_party/ink/closure/dom/htmlelement.js new file mode 100644 index 0000000..c48f753 --- /dev/null +++ b/third_party/ink/closure/dom/htmlelement.js
@@ -0,0 +1,29 @@ +// Copyright 2017 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.dom.HtmlElement'); + + + +/** + * This subclass of HTMLElement is used when only a HTMLElement is possible and + * not any of its subclasses. Normally, a type can refer to an instance of + * itself or an instance of any subtype. More concretely, if HTMLElement is used + * then the compiler must assume that it might still be e.g. HTMLScriptElement. + * With this, the type check knows that it couldn't be any special element. + * + * @constructor + * @extends {HTMLElement} + */ +goog.dom.HtmlElement = function() {};
diff --git a/third_party/ink/closure/dom/nodetype.js b/third_party/ink/closure/dom/nodetype.js new file mode 100644 index 0000000..cccb470 --- /dev/null +++ b/third_party/ink/closure/dom/nodetype.js
@@ -0,0 +1,48 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of goog.dom.NodeType. + */ + +goog.provide('goog.dom.NodeType'); + + +/** + * Constants for the nodeType attribute in the Node interface. + * + * These constants match those specified in the Node interface. These are + * usually present on the Node object in recent browsers, but not in older + * browsers (specifically, early IEs) and thus are given here. + * + * In some browsers (early IEs), these are not defined on the Node object, + * so they are provided here. + * + * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 + * @enum {number} + */ +goog.dom.NodeType = { + ELEMENT: 1, + ATTRIBUTE: 2, + TEXT: 3, + CDATA_SECTION: 4, + ENTITY_REFERENCE: 5, + ENTITY: 6, + PROCESSING_INSTRUCTION: 7, + COMMENT: 8, + DOCUMENT: 9, + DOCUMENT_TYPE: 10, + DOCUMENT_FRAGMENT: 11, + NOTATION: 12 +};
diff --git a/third_party/ink/closure/dom/safe.js b/third_party/ink/closure/dom/safe.js new file mode 100644 index 0000000..a869e1b --- /dev/null +++ b/third_party/ink/closure/dom/safe.js
@@ -0,0 +1,458 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Type-safe wrappers for unsafe DOM APIs. + * + * This file provides type-safe wrappers for DOM APIs that can result in + * cross-site scripting (XSS) vulnerabilities, if the API is supplied with + * untrusted (attacker-controlled) input. Instead of plain strings, the type + * safe wrappers consume values of types from the goog.html package whose + * contract promises that values are safe to use in the corresponding context. + * + * Hence, a program that exclusively uses the wrappers in this file (i.e., whose + * only reference to security-sensitive raw DOM APIs are in this file) is + * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo + * correctness of code that produces values of the respective goog.html types, + * and absent code that violates type safety). + * + * For example, assigning to an element's .innerHTML property a string that is + * derived (even partially) from untrusted input typically results in an XSS + * vulnerability. The type-safe wrapper goog.dom.safe.setInnerHtml consumes a + * value of type goog.html.SafeHtml, whose contract states that using its values + * in a HTML context will not result in XSS. Hence a program that is free of + * direct assignments to any element's innerHTML property (with the exception of + * the assignment to .innerHTML in this file) is guaranteed to be free of XSS + * due to assignment of untrusted strings to the innerHTML property. + */ + +goog.provide('goog.dom.safe'); +goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition'); + +goog.require('goog.asserts'); +goog.require('goog.dom.asserts'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.string'); +goog.require('goog.string.Const'); + + +/** @enum {string} */ +goog.dom.safe.InsertAdjacentHtmlPosition = { + AFTERBEGIN: 'afterbegin', + AFTEREND: 'afterend', + BEFOREBEGIN: 'beforebegin', + BEFOREEND: 'beforeend' +}; + + +/** + * Inserts known-safe HTML into a Node, at the specified position. + * @param {!Node} node The node on which to call insertAdjacentHTML. + * @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where + * to insert the HTML. + * @param {!goog.html.SafeHtml} html The known-safe HTML to insert. + */ +goog.dom.safe.insertAdjacentHtml = function(node, position, html) { + node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(html)); +}; + + +/** + * Tags not allowed in goog.dom.safe.setInnerHtml. + * @private @const {!Object<string, boolean>} + */ +goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_ = { + 'MATH': true, + 'SCRIPT': true, + 'STYLE': true, + 'SVG': true, + 'TEMPLATE': true +}; + + +/** + * Assigns known-safe HTML to an element's innerHTML property. + * @param {!Element} elem The element whose innerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * @throws {Error} If called with one of these tags: math, script, style, svg, + * template. + */ +goog.dom.safe.setInnerHtml = function(elem, html) { + if (goog.asserts.ENABLE_ASSERTS) { + var tagName = elem.tagName.toUpperCase(); + if (goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_[tagName]) { + throw new Error( + 'goog.dom.safe.setInnerHtml cannot be used to set content of ' + + elem.tagName + '.'); + } + } + elem.innerHTML = goog.html.SafeHtml.unwrap(html); +}; + + +/** + * Assigns known-safe HTML to an element's outerHTML property. + * @param {!Element} elem The element whose outerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.setOuterHtml = function(elem, html) { + elem.outerHTML = goog.html.SafeHtml.unwrap(html); +}; + + +/** + * Sets the given element's style property to the contents of the provided + * SafeStyle object. + * @param {!Element} elem + * @param {!goog.html.SafeStyle} style + */ +goog.dom.safe.setStyle = function(elem, style) { + elem.style.cssText = goog.html.SafeStyle.unwrap(style); +}; + + +/** + * Writes known-safe HTML to a document. + * @param {!Document} doc The document to be written to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.documentWrite = function(doc, html) { + doc.write(goog.html.SafeHtml.unwrap(html)); +}; + + +/** + * Safely assigns a URL to an anchor element's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * anchor's href property. If url is of type string however, it is first + * sanitized using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setAnchorHref(anchorEl, url); + * which is a safe alternative to + * anchorEl.href = url; + * The latter can result in XSS vulnerabilities if url is a + * user-/attacker-controlled value. + * + * @param {!HTMLAnchorElement} anchor The anchor element whose href property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setAnchorHref = function(anchor, url) { + goog.dom.asserts.assertIsHTMLAnchorElement(anchor); + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url); + } + anchor.href = goog.html.SafeUrl.unwrap(safeUrl); +}; + + +/** + * Safely assigns a URL to an image element's src property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * image's src property. If url is of type string however, it is first + * sanitized using goog.html.SafeUrl.sanitize. + * + * @param {!HTMLImageElement} imageElement The image element whose src property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setImageSrc = function(imageElement, url) { + goog.dom.asserts.assertIsHTMLImageElement(imageElement); + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url); + } + imageElement.src = goog.html.SafeUrl.unwrap(safeUrl); +}; + + +/** + * Safely assigns a URL to an embed element's src property. + * + * Example usage: + * goog.dom.safe.setEmbedSrc(embedEl, url); + * which is a safe alternative to + * embedEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLEmbedElement} embed The embed element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setEmbedSrc = function(embed, url) { + goog.dom.asserts.assertIsHTMLEmbedElement(embed); + embed.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to a frame element's src property. + * + * Example usage: + * goog.dom.safe.setFrameSrc(frameEl, url); + * which is a safe alternative to + * frameEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLFrameElement} frame The frame element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setFrameSrc = function(frame, url) { + goog.dom.asserts.assertIsHTMLFrameElement(frame); + frame.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to an iframe element's src property. + * + * Example usage: + * goog.dom.safe.setIframeSrc(iframeEl, url); + * which is a safe alternative to + * iframeEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLIFrameElement} iframe The iframe element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setIframeSrc = function(iframe, url) { + goog.dom.asserts.assertIsHTMLIFrameElement(iframe); + iframe.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns HTML to an iframe element's srcdoc property. + * + * Example usage: + * goog.dom.safe.setIframeSrcdoc(iframeEl, safeHtml); + * which is a safe alternative to + * iframeEl.srcdoc = html; + * The latter can result in loading untrusted code. + * + * @param {!HTMLIFrameElement} iframe The iframe element whose srcdoc property + * is to be assigned to. + * @param {!goog.html.SafeHtml} html The HTML to assign. + */ +goog.dom.safe.setIframeSrcdoc = function(iframe, html) { + goog.dom.asserts.assertIsHTMLIFrameElement(iframe); + iframe.srcdoc = goog.html.SafeHtml.unwrap(html); +}; + + +/** + * Safely sets a link element's href and rel properties. Whether or not + * the URL assigned to href has to be a goog.html.TrustedResourceUrl + * depends on the value of the rel property. If rel contains "stylesheet" + * then a TrustedResourceUrl is required. + * + * Example usage: + * goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet'); + * which is a safe alternative to + * linkEl.rel = 'stylesheet'; + * linkEl.href = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLLinkElement} link The link element whose href property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL + * to assign to the href property. Must be a TrustedResourceUrl if the + * value assigned to rel contains "stylesheet". A string value is + * sanitized with goog.html.SafeUrl.sanitize. + * @param {string} rel The value to assign to the rel property. + * @throws {Error} if rel contains "stylesheet" and url is not a + * TrustedResourceUrl + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) { + goog.dom.asserts.assertIsHTMLLinkElement(link); + link.rel = rel; + if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) { + goog.asserts.assert( + url instanceof goog.html.TrustedResourceUrl, + 'URL must be TrustedResourceUrl because "rel" contains "stylesheet"'); + link.href = goog.html.TrustedResourceUrl.unwrap(url); + } else if (url instanceof goog.html.TrustedResourceUrl) { + link.href = goog.html.TrustedResourceUrl.unwrap(url); + } else if (url instanceof goog.html.SafeUrl) { + link.href = goog.html.SafeUrl.unwrap(url); + } else { // string + // SafeUrl.sanitize must return legitimate SafeUrl when passed a string. + link.href = + goog.html.SafeUrl.sanitizeAssertUnchanged(url).getTypedStringValue(); + } +}; + + +/** + * Safely assigns a URL to an object element's data property. + * + * Example usage: + * goog.dom.safe.setObjectData(objectEl, url); + * which is a safe alternative to + * objectEl.data = url; + * The latter can result in loading untrusted code unless setit is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLObjectElement} object The object element whose data property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setObjectData = function(object, url) { + goog.dom.asserts.assertIsHTMLObjectElement(object); + object.data = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a URL to a script element's src property. + * + * Example usage: + * goog.dom.safe.setScriptSrc(scriptEl, url); + * which is a safe alternative to + * scriptEl.src = url; + * The latter can result in loading untrusted code unless it is ensured that + * the URL refers to a trustworthy resource. + * + * @param {!HTMLScriptElement} script The script element whose src property + * is to be assigned to. + * @param {!goog.html.TrustedResourceUrl} url The URL to assign. + */ +goog.dom.safe.setScriptSrc = function(script, url) { + goog.dom.asserts.assertIsHTMLScriptElement(script); + script.src = goog.html.TrustedResourceUrl.unwrap(url); +}; + + +/** + * Safely assigns a value to a script element's content. + * + * Example usage: + * goog.dom.safe.setScriptContent(scriptEl, content); + * which is a safe alternative to + * scriptEl.text = content; + * The latter can result in executing untrusted code unless it is ensured that + * the code is loaded from a trustworthy resource. + * + * @param {!HTMLScriptElement} script The script element whose content is being + * set. + * @param {!goog.html.SafeScript} content The content to assign. + */ +goog.dom.safe.setScriptContent = function(script, content) { + goog.dom.asserts.assertIsHTMLScriptElement(script); + script.text = goog.html.SafeScript.unwrap(content); +}; + + +/** + * Safely assigns a URL to a Location object's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * loc's href property. If url is of type string however, it is first sanitized + * using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setLocationHref(document.location, redirectUrl); + * which is a safe alternative to + * document.location.href = redirectUrl; + * The latter can result in XSS vulnerabilities if redirectUrl is a + * user-/attacker-controlled value. + * + * @param {!Location} loc The Location object whose href property is to be + * assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize + */ +goog.dom.safe.setLocationHref = function(loc, url) { + goog.dom.asserts.assertIsLocation(loc); + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url); + } + loc.href = goog.html.SafeUrl.unwrap(safeUrl); +}; + + +/** + * Safely opens a URL in a new window (via window.open). + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to + * window.open. If url is of type string however, it is first sanitized + * using goog.html.SafeUrl.sanitize. + * + * Note that this function does not prevent leakages via the referer that is + * sent by window.open. It is advised to only use this to open 1st party URLs. + * + * Example usage: + * goog.dom.safe.openInWindow(url); + * which is a safe alternative to + * window.open(url); + * The latter can result in XSS vulnerabilities if redirectUrl is a + * user-/attacker-controlled value. + * + * @param {string|!goog.html.SafeUrl} url The URL to open. + * @param {Window=} opt_openerWin Window of which to call the .open() method. + * Defaults to the global window. + * @param {!goog.string.Const=} opt_name Name of the window to open in. Can be + * _top, etc as allowed by window.open(). + * @param {string=} opt_specs Comma-separated list of specifications, same as + * in window.open(). + * @param {boolean=} opt_replace Whether to replace the current entry in browser + * history, same as in window.open(). + * @return {Window} Window the url was opened in. + */ +goog.dom.safe.openInWindow = function( + url, opt_openerWin, opt_name, opt_specs, opt_replace) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url); + } + var win = opt_openerWin || window; + return win.open( + goog.html.SafeUrl.unwrap(safeUrl), + // If opt_name is undefined, simply passing that in to open() causes IE to + // reuse the current window instead of opening a new one. Thus we pass '' + // in instead, which according to spec opens a new window. See + // https://html.spec.whatwg.org/multipage/browsers.html#dom-open . + opt_name ? goog.string.Const.unwrap(opt_name) : '', opt_specs, + opt_replace); +};
diff --git a/third_party/ink/closure/dom/tagname.js b/third_party/ink/closure/dom/tagname.js new file mode 100644 index 0000000..b3808ad1 --- /dev/null +++ b/third_party/ink/closure/dom/tagname.js
@@ -0,0 +1,562 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the goog.dom.TagName class. Its constants enumerate + * all HTML tag names specified in either the the W3C HTML 4.01 index of + * elements or the HTML5 draft specification. + * + * References: + * http://www.w3.org/TR/html401/index/elements.html + * http://dev.w3.org/html5/spec/section-index.html + */ +goog.provide('goog.dom.TagName'); + +goog.require('goog.dom.HtmlElement'); + + +/** + * A tag name with the type of the element stored in the generic. + * @param {string} tagName + * @constructor + * @template T + */ +goog.dom.TagName = function(tagName) { + /** @private {string} */ + this.tagName_ = tagName; +}; + + +/** + * Returns the tag name. + * @return {string} + * @override + */ +goog.dom.TagName.prototype.toString = function() { + return this.tagName_; +}; + + +// Closure Compiler unconditionally converts the following constants to their +// string value (goog.dom.TagName.A -> 'A'). These are the consequences: +// 1. Don't add any members or static members to goog.dom.TagName as they +// couldn't be accessed after this optimization. +// 2. Keep the constant name and its string value the same: +// goog.dom.TagName.X = new goog.dom.TagName('Y'); +// is converted to 'X', not 'Y'. + + +/** @type {!goog.dom.TagName<!HTMLAnchorElement>} */ +goog.dom.TagName.A = new goog.dom.TagName('A'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.ABBR = new goog.dom.TagName('ABBR'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.ACRONYM = new goog.dom.TagName('ACRONYM'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.ADDRESS = new goog.dom.TagName('ADDRESS'); + + +/** @type {!goog.dom.TagName<!HTMLAppletElement>} */ +goog.dom.TagName.APPLET = new goog.dom.TagName('APPLET'); + + +/** @type {!goog.dom.TagName<!HTMLAreaElement>} */ +goog.dom.TagName.AREA = new goog.dom.TagName('AREA'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.ARTICLE = new goog.dom.TagName('ARTICLE'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.ASIDE = new goog.dom.TagName('ASIDE'); + + +/** @type {!goog.dom.TagName<!HTMLAudioElement>} */ +goog.dom.TagName.AUDIO = new goog.dom.TagName('AUDIO'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.B = new goog.dom.TagName('B'); + + +/** @type {!goog.dom.TagName<!HTMLBaseElement>} */ +goog.dom.TagName.BASE = new goog.dom.TagName('BASE'); + + +/** @type {!goog.dom.TagName<!HTMLBaseFontElement>} */ +goog.dom.TagName.BASEFONT = new goog.dom.TagName('BASEFONT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.BDI = new goog.dom.TagName('BDI'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.BDO = new goog.dom.TagName('BDO'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.BIG = new goog.dom.TagName('BIG'); + + +/** @type {!goog.dom.TagName<!HTMLQuoteElement>} */ +goog.dom.TagName.BLOCKQUOTE = new goog.dom.TagName('BLOCKQUOTE'); + + +/** @type {!goog.dom.TagName<!HTMLBodyElement>} */ +goog.dom.TagName.BODY = new goog.dom.TagName('BODY'); + + +/** @type {!goog.dom.TagName<!HTMLBRElement>} */ +goog.dom.TagName.BR = new goog.dom.TagName('BR'); + + +/** @type {!goog.dom.TagName<!HTMLButtonElement>} */ +goog.dom.TagName.BUTTON = new goog.dom.TagName('BUTTON'); + + +/** @type {!goog.dom.TagName<!HTMLCanvasElement>} */ +goog.dom.TagName.CANVAS = new goog.dom.TagName('CANVAS'); + + +/** @type {!goog.dom.TagName<!HTMLTableCaptionElement>} */ +goog.dom.TagName.CAPTION = new goog.dom.TagName('CAPTION'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.CENTER = new goog.dom.TagName('CENTER'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.CITE = new goog.dom.TagName('CITE'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.CODE = new goog.dom.TagName('CODE'); + + +/** @type {!goog.dom.TagName<!HTMLTableColElement>} */ +goog.dom.TagName.COL = new goog.dom.TagName('COL'); + + +/** @type {!goog.dom.TagName<!HTMLTableColElement>} */ +goog.dom.TagName.COLGROUP = new goog.dom.TagName('COLGROUP'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.COMMAND = new goog.dom.TagName('COMMAND'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.DATA = new goog.dom.TagName('DATA'); + + +/** @type {!goog.dom.TagName<!HTMLDataListElement>} */ +goog.dom.TagName.DATALIST = new goog.dom.TagName('DATALIST'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.DD = new goog.dom.TagName('DD'); + + +/** @type {!goog.dom.TagName<!HTMLModElement>} */ +goog.dom.TagName.DEL = new goog.dom.TagName('DEL'); + + +/** @type {!goog.dom.TagName<!HTMLDetailsElement>} */ +goog.dom.TagName.DETAILS = new goog.dom.TagName('DETAILS'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.DFN = new goog.dom.TagName('DFN'); + + +/** @type {!goog.dom.TagName<!HTMLDialogElement>} */ +goog.dom.TagName.DIALOG = new goog.dom.TagName('DIALOG'); + + +/** @type {!goog.dom.TagName<!HTMLDirectoryElement>} */ +goog.dom.TagName.DIR = new goog.dom.TagName('DIR'); + + +/** @type {!goog.dom.TagName<!HTMLDivElement>} */ +goog.dom.TagName.DIV = new goog.dom.TagName('DIV'); + + +/** @type {!goog.dom.TagName<!HTMLDListElement>} */ +goog.dom.TagName.DL = new goog.dom.TagName('DL'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.DT = new goog.dom.TagName('DT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.EM = new goog.dom.TagName('EM'); + + +/** @type {!goog.dom.TagName<!HTMLEmbedElement>} */ +goog.dom.TagName.EMBED = new goog.dom.TagName('EMBED'); + + +/** @type {!goog.dom.TagName<!HTMLFieldSetElement>} */ +goog.dom.TagName.FIELDSET = new goog.dom.TagName('FIELDSET'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.FIGCAPTION = new goog.dom.TagName('FIGCAPTION'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.FIGURE = new goog.dom.TagName('FIGURE'); + + +/** @type {!goog.dom.TagName<!HTMLFontElement>} */ +goog.dom.TagName.FONT = new goog.dom.TagName('FONT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.FOOTER = new goog.dom.TagName('FOOTER'); + + +/** @type {!goog.dom.TagName<!HTMLFormElement>} */ +goog.dom.TagName.FORM = new goog.dom.TagName('FORM'); + + +/** @type {!goog.dom.TagName<!HTMLFrameElement>} */ +goog.dom.TagName.FRAME = new goog.dom.TagName('FRAME'); + + +/** @type {!goog.dom.TagName<!HTMLFrameSetElement>} */ +goog.dom.TagName.FRAMESET = new goog.dom.TagName('FRAMESET'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H1 = new goog.dom.TagName('H1'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H2 = new goog.dom.TagName('H2'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H3 = new goog.dom.TagName('H3'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H4 = new goog.dom.TagName('H4'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H5 = new goog.dom.TagName('H5'); + + +/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */ +goog.dom.TagName.H6 = new goog.dom.TagName('H6'); + + +/** @type {!goog.dom.TagName<!HTMLHeadElement>} */ +goog.dom.TagName.HEAD = new goog.dom.TagName('HEAD'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.HEADER = new goog.dom.TagName('HEADER'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.HGROUP = new goog.dom.TagName('HGROUP'); + + +/** @type {!goog.dom.TagName<!HTMLHRElement>} */ +goog.dom.TagName.HR = new goog.dom.TagName('HR'); + + +/** @type {!goog.dom.TagName<!HTMLHtmlElement>} */ +goog.dom.TagName.HTML = new goog.dom.TagName('HTML'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.I = new goog.dom.TagName('I'); + + +/** @type {!goog.dom.TagName<!HTMLIFrameElement>} */ +goog.dom.TagName.IFRAME = new goog.dom.TagName('IFRAME'); + + +/** @type {!goog.dom.TagName<!HTMLImageElement>} */ +goog.dom.TagName.IMG = new goog.dom.TagName('IMG'); + + +/** @type {!goog.dom.TagName<!HTMLInputElement>} */ +goog.dom.TagName.INPUT = new goog.dom.TagName('INPUT'); + + +/** @type {!goog.dom.TagName<!HTMLModElement>} */ +goog.dom.TagName.INS = new goog.dom.TagName('INS'); + + +/** @type {!goog.dom.TagName<!HTMLIsIndexElement>} */ +goog.dom.TagName.ISINDEX = new goog.dom.TagName('ISINDEX'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.KBD = new goog.dom.TagName('KBD'); + + +// HTMLKeygenElement is deprecated. +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.KEYGEN = new goog.dom.TagName('KEYGEN'); + + +/** @type {!goog.dom.TagName<!HTMLLabelElement>} */ +goog.dom.TagName.LABEL = new goog.dom.TagName('LABEL'); + + +/** @type {!goog.dom.TagName<!HTMLLegendElement>} */ +goog.dom.TagName.LEGEND = new goog.dom.TagName('LEGEND'); + + +/** @type {!goog.dom.TagName<!HTMLLIElement>} */ +goog.dom.TagName.LI = new goog.dom.TagName('LI'); + + +/** @type {!goog.dom.TagName<!HTMLLinkElement>} */ +goog.dom.TagName.LINK = new goog.dom.TagName('LINK'); + + +/** @type {!goog.dom.TagName<!HTMLMapElement>} */ +goog.dom.TagName.MAP = new goog.dom.TagName('MAP'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.MARK = new goog.dom.TagName('MARK'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.MATH = new goog.dom.TagName('MATH'); + + +/** @type {!goog.dom.TagName<!HTMLMenuElement>} */ +goog.dom.TagName.MENU = new goog.dom.TagName('MENU'); + + +/** @type {!goog.dom.TagName<!HTMLMetaElement>} */ +goog.dom.TagName.META = new goog.dom.TagName('META'); + + +/** @type {!goog.dom.TagName<!HTMLMeterElement>} */ +goog.dom.TagName.METER = new goog.dom.TagName('METER'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.NAV = new goog.dom.TagName('NAV'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.NOFRAMES = new goog.dom.TagName('NOFRAMES'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.NOSCRIPT = new goog.dom.TagName('NOSCRIPT'); + + +/** @type {!goog.dom.TagName<!HTMLObjectElement>} */ +goog.dom.TagName.OBJECT = new goog.dom.TagName('OBJECT'); + + +/** @type {!goog.dom.TagName<!HTMLOListElement>} */ +goog.dom.TagName.OL = new goog.dom.TagName('OL'); + + +/** @type {!goog.dom.TagName<!HTMLOptGroupElement>} */ +goog.dom.TagName.OPTGROUP = new goog.dom.TagName('OPTGROUP'); + + +/** @type {!goog.dom.TagName<!HTMLOptionElement>} */ +goog.dom.TagName.OPTION = new goog.dom.TagName('OPTION'); + + +/** @type {!goog.dom.TagName<!HTMLOutputElement>} */ +goog.dom.TagName.OUTPUT = new goog.dom.TagName('OUTPUT'); + + +/** @type {!goog.dom.TagName<!HTMLParagraphElement>} */ +goog.dom.TagName.P = new goog.dom.TagName('P'); + + +/** @type {!goog.dom.TagName<!HTMLParamElement>} */ +goog.dom.TagName.PARAM = new goog.dom.TagName('PARAM'); + + +/** @type {!goog.dom.TagName<!HTMLPreElement>} */ +goog.dom.TagName.PRE = new goog.dom.TagName('PRE'); + + +/** @type {!goog.dom.TagName<!HTMLProgressElement>} */ +goog.dom.TagName.PROGRESS = new goog.dom.TagName('PROGRESS'); + + +/** @type {!goog.dom.TagName<!HTMLQuoteElement>} */ +goog.dom.TagName.Q = new goog.dom.TagName('Q'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.RP = new goog.dom.TagName('RP'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.RT = new goog.dom.TagName('RT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.RUBY = new goog.dom.TagName('RUBY'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.S = new goog.dom.TagName('S'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SAMP = new goog.dom.TagName('SAMP'); + + +/** @type {!goog.dom.TagName<!HTMLScriptElement>} */ +goog.dom.TagName.SCRIPT = new goog.dom.TagName('SCRIPT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SECTION = new goog.dom.TagName('SECTION'); + + +/** @type {!goog.dom.TagName<!HTMLSelectElement>} */ +goog.dom.TagName.SELECT = new goog.dom.TagName('SELECT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SMALL = new goog.dom.TagName('SMALL'); + + +/** @type {!goog.dom.TagName<!HTMLSourceElement>} */ +goog.dom.TagName.SOURCE = new goog.dom.TagName('SOURCE'); + + +/** @type {!goog.dom.TagName<!HTMLSpanElement>} */ +goog.dom.TagName.SPAN = new goog.dom.TagName('SPAN'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.STRIKE = new goog.dom.TagName('STRIKE'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.STRONG = new goog.dom.TagName('STRONG'); + + +/** @type {!goog.dom.TagName<!HTMLStyleElement>} */ +goog.dom.TagName.STYLE = new goog.dom.TagName('STYLE'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SUB = new goog.dom.TagName('SUB'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SUMMARY = new goog.dom.TagName('SUMMARY'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SUP = new goog.dom.TagName('SUP'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.SVG = new goog.dom.TagName('SVG'); + + +/** @type {!goog.dom.TagName<!HTMLTableElement>} */ +goog.dom.TagName.TABLE = new goog.dom.TagName('TABLE'); + + +/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */ +goog.dom.TagName.TBODY = new goog.dom.TagName('TBODY'); + + +/** @type {!goog.dom.TagName<!HTMLTableCellElement>} */ +goog.dom.TagName.TD = new goog.dom.TagName('TD'); + + +/** @type {!goog.dom.TagName<!HTMLTemplateElement>} */ +goog.dom.TagName.TEMPLATE = new goog.dom.TagName('TEMPLATE'); + + +/** @type {!goog.dom.TagName<!HTMLTextAreaElement>} */ +goog.dom.TagName.TEXTAREA = new goog.dom.TagName('TEXTAREA'); + + +/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */ +goog.dom.TagName.TFOOT = new goog.dom.TagName('TFOOT'); + + +/** @type {!goog.dom.TagName<!HTMLTableCellElement>} */ +goog.dom.TagName.TH = new goog.dom.TagName('TH'); + + +/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */ +goog.dom.TagName.THEAD = new goog.dom.TagName('THEAD'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.TIME = new goog.dom.TagName('TIME'); + + +/** @type {!goog.dom.TagName<!HTMLTitleElement>} */ +goog.dom.TagName.TITLE = new goog.dom.TagName('TITLE'); + + +/** @type {!goog.dom.TagName<!HTMLTableRowElement>} */ +goog.dom.TagName.TR = new goog.dom.TagName('TR'); + + +/** @type {!goog.dom.TagName<!HTMLTrackElement>} */ +goog.dom.TagName.TRACK = new goog.dom.TagName('TRACK'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.TT = new goog.dom.TagName('TT'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.U = new goog.dom.TagName('U'); + + +/** @type {!goog.dom.TagName<!HTMLUListElement>} */ +goog.dom.TagName.UL = new goog.dom.TagName('UL'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.VAR = new goog.dom.TagName('VAR'); + + +/** @type {!goog.dom.TagName<!HTMLVideoElement>} */ +goog.dom.TagName.VIDEO = new goog.dom.TagName('VIDEO'); + + +/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */ +goog.dom.TagName.WBR = new goog.dom.TagName('WBR');
diff --git a/third_party/ink/closure/dom/tags.js b/third_party/ink/closure/dom/tags.js new file mode 100644 index 0000000..7c12938 --- /dev/null +++ b/third_party/ink/closure/dom/tags.js
@@ -0,0 +1,41 @@ +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for HTML element tag names. + */ +goog.provide('goog.dom.tags'); + +goog.require('goog.object'); + + +/** + * The void elements specified by + * http://www.w3.org/TR/html-markup/syntax.html#void-elements. + * @const @private {!Object<string, boolean>} + */ +goog.dom.tags.VOID_TAGS_ = goog.object.createSet( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', + 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'); + + +/** + * Checks whether the tag is void (with no contents allowed and no legal end + * tag), for example 'br'. + * @param {string} tagName The tag name in lower case. + * @return {boolean} + */ +goog.dom.tags.isVoidTag = function(tagName) { + return goog.dom.tags.VOID_TAGS_[tagName] === true; +};
diff --git a/third_party/ink/closure/dom/vendor.js b/third_party/ink/closure/dom/vendor.js new file mode 100644 index 0000000..28b173d --- /dev/null +++ b/third_party/ink/closure/dom/vendor.js
@@ -0,0 +1,97 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Vendor prefix getters. + */ + +goog.provide('goog.dom.vendor'); + +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Returns the JS vendor prefix used in CSS properties. Different vendors + * use different methods of changing the case of the property names. + * + * @return {?string} The JS vendor prefix or null if there is none. + */ +goog.dom.vendor.getVendorJsPrefix = function() { + if (goog.userAgent.WEBKIT) { + return 'Webkit'; + } else if (goog.userAgent.GECKO) { + return 'Moz'; + } else if (goog.userAgent.IE) { + return 'ms'; + } else if (goog.userAgent.OPERA) { + return 'O'; + } + + return null; +}; + + +/** + * Returns the vendor prefix used in CSS properties. + * + * @return {?string} The vendor prefix or null if there is none. + */ +goog.dom.vendor.getVendorPrefix = function() { + if (goog.userAgent.WEBKIT) { + return '-webkit'; + } else if (goog.userAgent.GECKO) { + return '-moz'; + } else if (goog.userAgent.IE) { + return '-ms'; + } else if (goog.userAgent.OPERA) { + return '-o'; + } + + return null; +}; + + +/** + * @param {string} propertyName A property name. + * @param {!Object=} opt_object If provided, we verify if the property exists in + * the object. + * @return {?string} A vendor prefixed property name, or null if it does not + * exist. + */ +goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) { + // We first check for a non-prefixed property, if available. + if (opt_object && propertyName in opt_object) { + return propertyName; + } + var prefix = goog.dom.vendor.getVendorJsPrefix(); + if (prefix) { + prefix = prefix.toLowerCase(); + var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName); + return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ? + prefixedPropertyName : + null; + } + return null; +}; + + +/** + * @param {string} eventType An event type. + * @return {string} A lower-cased vendor prefixed event type. + */ +goog.dom.vendor.getPrefixedEventType = function(eventType) { + var prefix = goog.dom.vendor.getVendorJsPrefix() || ''; + return (prefix + eventType).toLowerCase(); +};
diff --git a/third_party/ink/closure/events/browserevent.js b/third_party/ink/closure/events/browserevent.js new file mode 100644 index 0000000..3c557ca --- /dev/null +++ b/third_party/ink/closure/events/browserevent.js
@@ -0,0 +1,470 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A patched, standardized event object for browser events. + * + * <pre> + * The patched event object contains the following members: + * - type {string} Event type, e.g. 'click' + * - target {Object} The element that actually triggered the event + * - currentTarget {Object} The element the listener is attached to + * - relatedTarget {Object} For mouseover and mouseout, the previous object + * - offsetX {number} X-coordinate relative to target + * - offsetY {number} Y-coordinate relative to target + * - clientX {number} X-coordinate relative to viewport + * - clientY {number} Y-coordinate relative to viewport + * - screenX {number} X-coordinate relative to the edge of the screen + * - screenY {number} Y-coordinate relative to the edge of the screen + * - button {number} Mouse button. Use isButton() to test. + * - keyCode {number} Key-code + * - ctrlKey {boolean} Was ctrl key depressed + * - altKey {boolean} Was alt key depressed + * - shiftKey {boolean} Was shift key depressed + * - metaKey {boolean} Was meta key depressed + * - pointerId {number} Pointer ID + * - pointerType {string} Pointer type, e.g. 'mouse', 'pen', or 'touch' + * - defaultPrevented {boolean} Whether the default action has been prevented + * - state {Object} History state object + * + * NOTE: The keyCode member contains the raw browser keyCode. For normalized + * key and character code use {@link goog.events.KeyHandler}. + * </pre> + * + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + */ + +goog.provide('goog.events.BrowserEvent'); +goog.provide('goog.events.BrowserEvent.MouseButton'); +goog.provide('goog.events.BrowserEvent.PointerType'); + +goog.require('goog.debug'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.reflect'); +goog.require('goog.userAgent'); + + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * The content of this object will not be initialized if no event object is + * provided. If this is the case, init() needs to be invoked separately. + * @param {Event=} opt_e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + * @constructor + * @extends {goog.events.Event} + */ +goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { + goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : ''); + + /** + * Target that fired the event. + * @override + * @type {Node} + */ + this.target = null; + + /** + * Node that had the listener attached. + * @override + * @type {Node|undefined} + */ + this.currentTarget = null; + + /** + * For mouseover and mouseout events, the related object for the event. + * @type {Node} + */ + this.relatedTarget = null; + + /** + * X-coordinate relative to target. + * @type {number} + */ + this.offsetX = 0; + + /** + * Y-coordinate relative to target. + * @type {number} + */ + this.offsetY = 0; + + /** + * X-coordinate relative to the window. + * @type {number} + */ + this.clientX = 0; + + /** + * Y-coordinate relative to the window. + * @type {number} + */ + this.clientY = 0; + + /** + * X-coordinate relative to the monitor. + * @type {number} + */ + this.screenX = 0; + + /** + * Y-coordinate relative to the monitor. + * @type {number} + */ + this.screenY = 0; + + /** + * Which mouse button was pressed. + * @type {number} + */ + this.button = 0; + + /** + * Key of key press. + * @type {string} + */ + this.key = ''; + + /** + * Keycode of key press. + * @type {number} + */ + this.keyCode = 0; + + /** + * Keycode of key press. + * @type {number} + */ + this.charCode = 0; + + /** + * Whether control was pressed at time of event. + * @type {boolean} + */ + this.ctrlKey = false; + + /** + * Whether alt was pressed at time of event. + * @type {boolean} + */ + this.altKey = false; + + /** + * Whether shift was pressed at time of event. + * @type {boolean} + */ + this.shiftKey = false; + + /** + * Whether the meta key was pressed at time of event. + * @type {boolean} + */ + this.metaKey = false; + + /** + * History state object, only set for PopState events where it's a copy of the + * state object provided to pushState or replaceState. + * @type {Object} + */ + this.state = null; + + /** + * Whether the default platform modifier key was pressed at time of event. + * (This is control for all platforms except Mac, where it's Meta.) + * @type {boolean} + */ + this.platformModifierKey = false; + + /** + * @type {number} + */ + this.pointerId = 0; + + /** + * @type {string} + */ + this.pointerType = ''; + + /** + * The browser event object. + * @private {Event} + */ + this.event_ = null; + + if (opt_e) { + this.init(opt_e, opt_currentTarget); + } +}; +goog.inherits(goog.events.BrowserEvent, goog.events.Event); + + +/** + * Normalized button constants for the mouse. + * @enum {number} + */ +goog.events.BrowserEvent.MouseButton = { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 +}; + + +/** + * Normalized pointer type constants for pointer events. + * @enum {string} + */ +goog.events.BrowserEvent.PointerType = { + MOUSE: 'mouse', + PEN: 'pen', + TOUCH: 'touch' +}; + + +/** + * Static data for mapping mouse buttons. + * @type {!Array<number>} + * @deprecated Use {@code goog.events.BrowserEvent.IE_BUTTON_MAP} instead. + */ +goog.events.BrowserEvent.IEButtonMap = goog.debug.freeze([ + 1, // LEFT + 4, // MIDDLE + 2 // RIGHT +]); + + +/** + * Static data for mapping mouse buttons. + * @const {!Array<number>} + */ +goog.events.BrowserEvent.IE_BUTTON_MAP = goog.events.BrowserEvent.IEButtonMap; + + +/** + * Static data for mapping MSPointerEvent types to PointerEvent types. + * @const {!Object<number, goog.events.BrowserEvent.PointerType>} + */ +goog.events.BrowserEvent.IE_POINTER_TYPE_MAP = goog.debug.freeze({ + 2: goog.events.BrowserEvent.PointerType.TOUCH, + 3: goog.events.BrowserEvent.PointerType.PEN, + 4: goog.events.BrowserEvent.PointerType.MOUSE +}); + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * @param {Event} e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + */ +goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) { + var type = this.type = e.type; + + /** + * On touch devices use the first "changed touch" as the relevant touch. + * @type {Touch} + */ + var relevantTouch = e.changedTouches ? e.changedTouches[0] : null; + + // TODO(nicksantos): Change this.target to type EventTarget. + this.target = /** @type {Node} */ (e.target) || e.srcElement; + + // TODO(nicksantos): Change this.currentTarget to type EventTarget. + this.currentTarget = /** @type {Node} */ (opt_currentTarget); + + var relatedTarget = /** @type {Node} */ (e.relatedTarget); + if (relatedTarget) { + // There's a bug in FireFox where sometimes, relatedTarget will be a + // chrome element, and accessing any property of it will get a permission + // denied exception. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=497780 + if (goog.userAgent.GECKO) { + if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) { + relatedTarget = null; + } + } + } else if (type == goog.events.EventType.MOUSEOVER) { + relatedTarget = e.fromElement; + } else if (type == goog.events.EventType.MOUSEOUT) { + relatedTarget = e.toElement; + } + + this.relatedTarget = relatedTarget; + + if (!goog.isNull(relevantTouch)) { + this.clientX = relevantTouch.clientX !== undefined ? relevantTouch.clientX : + relevantTouch.pageX; + this.clientY = relevantTouch.clientY !== undefined ? relevantTouch.clientY : + relevantTouch.pageY; + this.screenX = relevantTouch.screenX || 0; + this.screenY = relevantTouch.screenY || 0; + } else { + // Webkit emits a lame warning whenever layerX/layerY is accessed. + // http://code.google.com/p/chromium/issues/detail?id=101733 + this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ? + e.offsetX : + e.layerX; + this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ? + e.offsetY : + e.layerY; + this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; + this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; + this.screenX = e.screenX || 0; + this.screenY = e.screenY || 0; + } + + this.button = e.button; + + this.keyCode = e.keyCode || 0; + this.key = e.key || ''; + this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0); + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.shiftKey = e.shiftKey; + this.metaKey = e.metaKey; + this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; + this.pointerId = e.pointerId || 0; + this.pointerType = goog.events.BrowserEvent.getPointerType_(e); + this.state = e.state; + this.event_ = e; + if (e.defaultPrevented) { + this.preventDefault(); + } +}; + + +/** + * Tests to see which button was pressed during the event. This is really only + * useful in IE and Gecko browsers. And in IE, it's only useful for + * mousedown/mouseup events, because click only fires for the left mouse button. + * + * Safari 2 only reports the left button being clicked, and uses the value '1' + * instead of 0. Opera only reports a mousedown event for the middle button, and + * no mouse events for the right button. Opera has default behavior for left and + * middle click that can only be overridden via a configuration setting. + * + * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html. + * + * @param {goog.events.BrowserEvent.MouseButton} button The button + * to test for. + * @return {boolean} True if button was pressed. + */ +goog.events.BrowserEvent.prototype.isButton = function(button) { + if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) { + if (this.type == 'click') { + return button == goog.events.BrowserEvent.MouseButton.LEFT; + } else { + return !!( + this.event_.button & goog.events.BrowserEvent.IE_BUTTON_MAP[button]); + } + } else { + return this.event_.button == button; + } +}; + + +/** + * Whether this has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @return {boolean} The result. + */ +goog.events.BrowserEvent.prototype.isMouseActionButton = function() { + // Webkit does not ctrl+click to be a right-click, so we + // normalize it to behave like Gecko and Opera. + return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey); +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.stopPropagation = function() { + goog.events.BrowserEvent.superClass_.stopPropagation.call(this); + if (this.event_.stopPropagation) { + this.event_.stopPropagation(); + } else { + this.event_.cancelBubble = true; + } +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.preventDefault = function() { + goog.events.BrowserEvent.superClass_.preventDefault.call(this); + var be = this.event_; + if (!be.preventDefault) { + be.returnValue = false; + if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) { + + try { + // Most keys can be prevented using returnValue. Some special keys + // require setting the keyCode to -1 as well: + // + // In IE7: + // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6) + // + // In IE8: + // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event) + // + // We therefore do this for all function keys as well as when Ctrl key + // is pressed. + var VK_F1 = 112; + var VK_F12 = 123; + if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) { + be.keyCode = -1; + } + } catch (ex) { + // IE throws an 'access denied' exception when trying to change + // keyCode in some situations (e.g. srcElement is input[type=file], + // or srcElement is an anchor tag rewritten by parent's innerHTML). + // Do nothing in this case. + } + } + } else { + be.preventDefault(); + } +}; + + +/** + * @return {Event} The underlying browser event object. + */ +goog.events.BrowserEvent.prototype.getBrowserEvent = function() { + return this.event_; +}; + + +/** + * Extracts the pointer type from the given event. + * @param {!Event} e + * @return {string} The pointer type, e.g. 'mouse', 'pen', or 'touch'. + * @private + */ +goog.events.BrowserEvent.getPointerType_ = function(e) { + if (goog.isString(e.pointerType)) { + return e.pointerType; + } + // IE10 uses integer codes for pointer type. + // https://msdn.microsoft.com/en-us/library/hh772359(v=vs.85).aspx + return goog.events.BrowserEvent.IE_POINTER_TYPE_MAP[e.pointerType] || ''; +};
diff --git a/third_party/ink/closure/events/browserfeature.js b/third_party/ink/closure/events/browserfeature.js new file mode 100644 index 0000000..10ef20d --- /dev/null +++ b/third_party/ink/closure/events/browserfeature.js
@@ -0,0 +1,140 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Browser capability checks for the events package. + * + * @author zhyder@google.com (Zohair Hyder) + */ + + +goog.provide('goog.events.BrowserFeature'); + +goog.require('goog.userAgent'); +goog.scope(function() { + + + +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.events.BrowserFeature = { + /** + * Whether the button attribute of the event is W3C compliant. False in + * Internet Explorer prior to version 9; document-version dependent. + */ + HAS_W3C_BUTTON: + !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9), + + /** + * Whether the browser supports full W3C event model. + */ + HAS_W3C_EVENT_SUPPORT: + !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9), + + /** + * To prevent default in IE7-8 for certain keydown events we need set the + * keyCode to -1. + */ + SET_KEY_CODE_TO_PREVENT_DEFAULT: + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'), + + /** + * Whether the {@code navigator.onLine} property is supported. + */ + HAS_NAVIGATOR_ONLINE_PROPERTY: + !goog.userAgent.WEBKIT || goog.userAgent.isVersionOrHigher('528'), + + /** + * Whether HTML5 network online/offline events are supported. + */ + HAS_HTML5_NETWORK_EVENT_SUPPORT: + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') || + goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') || + goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'), + + /** + * Whether HTML5 network events fire on document.body, or otherwise the + * window. + */ + HTML5_NETWORK_EVENTS_FIRE_ON_BODY: + goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'), + + /** + * Whether touch is enabled in the browser. + */ + TOUCH_ENABLED: + ('ontouchstart' in goog.global || + !!(goog.global['document'] && document.documentElement && + 'ontouchstart' in document.documentElement) || + // IE10 uses non-standard touch events, so it has a different check. + !!(goog.global['navigator'] && + (goog.global['navigator']['maxTouchPoints'] || + goog.global['navigator']['msMaxTouchPoints']))), + + /** + * Whether addEventListener supports W3C standard pointer events. + * http://www.w3.org/TR/pointerevents/ + */ + POINTER_EVENTS: ('PointerEvent' in goog.global), + + /** + * Whether addEventListener supports MSPointer events (only used in IE10). + * http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx + * http://msdn.microsoft.com/library/hh673557(v=vs.85).aspx + */ + MSPOINTER_EVENTS: + ('MSPointerEvent' in goog.global && + !!(goog.global['navigator'] && + goog.global['navigator']['msPointerEnabled'])), + + /** + * Whether addEventListener supports {passive: true}. + * https://developers.google.com/web/updates/2016/06/passive-event-listeners + */ + PASSIVE_EVENTS: purify(function() { + // If we're in a web worker or other custom environment, we can't tell. + if (!goog.global.addEventListener || !Object.defineProperty) { // IE 8 + return false; + } + + var passive = false; + var options = Object.defineProperty({}, 'passive', { + get: function() { + passive = true; + } + }); + goog.global.addEventListener('test', goog.nullFunction, options); + goog.global.removeEventListener('test', goog.nullFunction, options); + + return passive; + }) +}; + + +/** + * Tricks Closure Compiler into believing that a function is pure. The compiler + * assumes that any `valueOf` function is pure, without analyzing its contents. + * + * @param {function(): T} fn + * @return {T} + * @template T + */ +function purify(fn) { + return ({valueOf: fn}).valueOf(); +} +}); // goog.scope
diff --git a/third_party/ink/closure/events/event.js b/third_party/ink/closure/events/event.js new file mode 100644 index 0000000..22efba9 --- /dev/null +++ b/third_party/ink/closure/events/event.js
@@ -0,0 +1,144 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A base class for event objects. + * + * @author pupius@google.com (Daniel Pupius) + */ + + +goog.provide('goog.events.Event'); +goog.provide('goog.events.EventLike'); + +/** + * goog.events.Event no longer depends on goog.Disposable. Keep requiring + * goog.Disposable here to not break projects which assume this dependency. + * @suppress {extraRequire} + */ +goog.require('goog.Disposable'); +goog.require('goog.events.EventId'); + + +/** + * A typedef for event like objects that are dispatchable via the + * goog.events.dispatchEvent function. strings are treated as the type for a + * goog.events.Event. Objects are treated as an extension of a new + * goog.events.Event with the type property of the object being used as the type + * of the Event. + * @typedef {string|Object|goog.events.Event|goog.events.EventId} + */ +goog.events.EventLike; + + + +/** + * A base class for event objects, so that they can support preventDefault and + * stopPropagation. + * + * @suppress {underscore} Several properties on this class are technically + * public, but referencing these properties outside this package is strongly + * discouraged. + * + * @param {string|!goog.events.EventId} type Event Type. + * @param {Object=} opt_target Reference to the object that is the target of + * this event. It has to implement the {@code EventTarget} interface + * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}. + * @constructor + */ +goog.events.Event = function(type, opt_target) { + /** + * Event type. + * @type {string} + */ + this.type = type instanceof goog.events.EventId ? String(type) : type; + + /** + * TODO(tbreisacher): The type should probably be + * EventTarget|goog.events.EventTarget. + * + * Target of the event. + * @type {Object|undefined} + */ + this.target = opt_target; + + /** + * Object that had the listener attached. + * @type {Object|undefined} + */ + this.currentTarget = this.target; + + /** + * Whether to cancel the event in internal capture/bubble processing for IE. + * @type {boolean} + * @public + */ + this.propagationStopped_ = false; + + /** + * Whether the default action has been prevented. + * This is a property to match the W3C specification at + * {@link http://www.w3.org/TR/DOM-Level-3-Events/ + * #events-event-type-defaultPrevented}. + * Must be treated as read-only outside the class. + * @type {boolean} + */ + this.defaultPrevented = false; + + /** + * Return value for in internal capture/bubble processing for IE. + * @type {boolean} + * @public + */ + this.returnValue_ = true; +}; + + +/** + * Stops event propagation. + */ +goog.events.Event.prototype.stopPropagation = function() { + this.propagationStopped_ = true; +}; + + +/** + * Prevents the default action, for example a link redirecting to a url. + */ +goog.events.Event.prototype.preventDefault = function() { + this.defaultPrevented = true; + this.returnValue_ = false; +}; + + +/** + * Stops the propagation of the event. It is equivalent to + * {@code e.stopPropagation()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.stopPropagation = function(e) { + e.stopPropagation(); +}; + + +/** + * Prevents the default action. It is equivalent to + * {@code e.preventDefault()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.preventDefault = function(e) { + e.preventDefault(); +};
diff --git a/third_party/ink/closure/events/eventhandler.js b/third_party/ink/closure/events/eventhandler.js new file mode 100644 index 0000000..6a887f9 --- /dev/null +++ b/third_party/ink/closure/events/eventhandler.js
@@ -0,0 +1,479 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Class to create objects which want to handle multiple events + * and have their listeners easily cleaned up via a dispose method. + * + * Example: + * <pre> + * function Something() { + * Something.base(this); + * + * ... set up object ... + * + * // Add event listeners + * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar); + * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand); + * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover); + * } + * goog.inherits(Something, goog.events.EventHandler); + * + * Something.prototype.disposeInternal = function() { + * Something.base(this, 'disposeInternal'); + * goog.dom.removeNode(this.container); + * }; + * + * + * // Then elsewhere: + * + * var activeSomething = null; + * function openSomething() { + * activeSomething = new Something(); + * } + * + * function closeSomething() { + * if (activeSomething) { + * activeSomething.dispose(); // Remove event listeners + * activeSomething = null; + * } + * } + * </pre> + * + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.events.EventHandler'); + +goog.require('goog.Disposable'); +goog.require('goog.events'); +goog.require('goog.object'); + +goog.forwardDeclare('goog.events.EventWrapper'); + + + +/** + * Super class for objects that want to easily manage a number of event + * listeners. It allows a short cut to listen and also provides a quick way + * to remove all events listeners belonging to this object. + * @param {SCOPE=} opt_scope Object in whose scope to call the listeners. + * @constructor + * @extends {goog.Disposable} + * @template SCOPE + */ +goog.events.EventHandler = function(opt_scope) { + goog.Disposable.call(this); + // TODO(mknichel): Rename this to this.scope_ and fix the classes in google3 + // that access this private variable. :( + this.handler_ = opt_scope; + + /** + * Keys for events that are being listened to. + * @type {!Object<!goog.events.Key>} + * @private + */ + this.keys_ = {}; +}; +goog.inherits(goog.events.EventHandler, goog.Disposable); + + +/** + * Utility array used to unify the cases of listening for an array of types + * and listening for a single event, without using recursion or allocating + * an array each time. + * @type {!Array<string>} + * @const + * @private + */ +goog.events.EventHandler.typeArray_ = []; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} + * opt_fn Optional callback function to be used as the listener or an object + * with handleEvent function. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template EVENTOBJ, THIS + */ +goog.events.EventHandler.prototype.listen = function( + src, type, opt_fn, opt_options) { + var self = /** @type {!goog.events.EventHandler} */ (this); + return self.listen_(src, type, opt_fn, opt_options); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|!AddEventListenerOptions|undefined} options + * @param {T} scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template T, EVENTOBJ, THIS + */ +goog.events.EventHandler.prototype.listenWithScope = function( + src, type, fn, options, scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + // TODO(mknichel): Deprecate this function. + return self.listen_(src, type, fn, options, scope); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template EVENTOBJ, THIS + * @private + */ +goog.events.EventHandler.prototype.listen_ = function( + src, type, opt_fn, opt_options, opt_scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + if (!goog.isArray(type)) { + if (type) { + goog.events.EventHandler.typeArray_[0] = type.toString(); + } + type = goog.events.EventHandler.typeArray_; + } + for (var i = 0; i < type.length; i++) { + var listenerObj = goog.events.listen( + src, type[i], opt_fn || self.handleEvent, opt_options || false, + opt_scope || self.handler_ || self); + + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return self; + } + + var key = listenerObj.key; + self.keys_[key] = listenerObj; + } + + return self; +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} + * opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template EVENTOBJ, THIS + */ +goog.events.EventHandler.prototype.listenOnce = function( + src, type, opt_fn, opt_options) { + var self = /** @type {!goog.events.EventHandler} */ (this); + return self.listenOnce_(src, type, opt_fn, opt_options); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template T, EVENTOBJ, THIS + */ +goog.events.EventHandler.prototype.listenOnceWithScope = function( + src, type, fn, capture, scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + // TODO(mknichel): Deprecate this function. + return self.listenOnce_(src, type, fn, capture, scope); +}; + + +/** + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired + * the event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template EVENTOBJ, THIS + * @private + */ +goog.events.EventHandler.prototype.listenOnce_ = function( + src, type, opt_fn, opt_options, opt_scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + self.listenOnce_(src, type[i], opt_fn, opt_options, opt_scope); + } + } else { + var listenerObj = goog.events.listenOnce( + src, type, opt_fn || self.handleEvent, opt_options, + opt_scope || self.handler_ || self); + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return self; + } + + var key = listenerObj.key; + self.keys_[key] = listenerObj; + } + + return self; +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template THIS + */ +goog.events.EventHandler.prototype.listenWithWrapper = function( + src, wrapper, listener, opt_capt) { + var self = /** @type {!goog.events.EventHandler} */ (this); + // TODO(mknichel): Remove the opt_scope from this function and then + // templatize it. + return self.listenWithWrapper_(src, wrapper, listener, opt_capt); +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null} + * listener Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template T, THIS + */ +goog.events.EventHandler.prototype.listenWithWrapperAndScope = function( + src, wrapper, listener, capture, scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + // TODO(mknichel): Deprecate this function. + return self.listenWithWrapper_(src, wrapper, listener, capture, scope); +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback + * method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template THIS + * @private + */ +goog.events.EventHandler.prototype.listenWithWrapper_ = function( + src, wrapper, listener, opt_capt, opt_scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + wrapper.listen( + src, listener, opt_capt, opt_scope || self.handler_ || self, self); + return self; +}; + + +/** + * @return {number} Number of listeners registered by this handler. + */ +goog.events.EventHandler.prototype.getListenerCount = function() { + var count = 0; + for (var key in this.keys_) { + if (Object.prototype.hasOwnProperty.call(this.keys_, key)) { + count++; + } + } + return count; +}; + + +/** + * Unlistens on an event. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type or array of event types to unlisten to. + * @param {function(this:?, EVENTOBJ):?|{handleEvent:function(?):?}|null=} + * opt_fn Optional callback function to be used as the listener or an object + * with handleEvent function. + * @param {(boolean|!EventListenerOptions)=} opt_options + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template EVENTOBJ, THIS + */ +goog.events.EventHandler.prototype.unlisten = function( + src, type, opt_fn, opt_options, opt_scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + self.unlisten(src, type[i], opt_fn, opt_options, opt_scope); + } + } else { + var capture = + goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; + var listener = goog.events.getListener( + src, type, opt_fn || self.handleEvent, capture, + opt_scope || self.handler_ || self); + + if (listener) { + goog.events.unlistenByKey(listener); + delete self.keys_[listener.key]; + } + } + + return self; +}; + + +/** + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.EventTarget} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {THIS} This object, allowing for chaining of calls. + * @this {THIS} + * @template THIS + */ +goog.events.EventHandler.prototype.unlistenWithWrapper = function( + src, wrapper, listener, opt_capt, opt_scope) { + var self = /** @type {!goog.events.EventHandler} */ (this); + wrapper.unlisten( + src, listener, opt_capt, opt_scope || self.handler_ || self, self); + return self; +}; + + +/** + * Unlistens to all events. + */ +goog.events.EventHandler.prototype.removeAll = function() { + goog.object.forEach(this.keys_, function(listenerObj, key) { + if (this.keys_.hasOwnProperty(key)) { + goog.events.unlistenByKey(listenerObj); + } + }, this); + + this.keys_ = {}; +}; + + +/** + * Disposes of this EventHandler and removes all listeners that it registered. + * @override + * @protected + */ +goog.events.EventHandler.prototype.disposeInternal = function() { + goog.events.EventHandler.superClass_.disposeInternal.call(this); + this.removeAll(); +}; + + +/** + * Default event handler + * @param {goog.events.Event} e Event object. + */ +goog.events.EventHandler.prototype.handleEvent = function(e) { + throw new Error('EventHandler.handleEvent not implemented'); +};
diff --git a/third_party/ink/closure/events/eventid.js b/third_party/ink/closure/events/eventid.js new file mode 100644 index 0000000..9ff9e40 --- /dev/null +++ b/third_party/ink/closure/events/eventid.js
@@ -0,0 +1,46 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.events.EventId'); + + + +/** + * A templated class that is used when registering for events. Typical usage: + * + * /** @type {goog.events.EventId<MyEventObj>} *\ + * var myEventId = new goog.events.EventId( + * goog.events.getUniqueId(('someEvent')); + * + * // No need to cast or declare here since the compiler knows the + * // correct type of 'evt' (MyEventObj). + * something.listen(myEventId, function(evt) {}); + * + * @param {string} eventId + * @template T + * @constructor + * @struct + * @final + */ +goog.events.EventId = function(eventId) { + /** @const */ this.id = eventId; +}; + + +/** + * @override + */ +goog.events.EventId.prototype.toString = function() { + return this.id; +};
diff --git a/third_party/ink/closure/events/events.js b/third_party/ink/closure/events/events.js new file mode 100644 index 0000000..4466923 --- /dev/null +++ b/third_party/ink/closure/events/events.js
@@ -0,0 +1,1006 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An event manager for both native browser event + * targets and custom JavaScript event targets + * ({@code goog.events.Listenable}). This provides an abstraction + * over browsers' event systems. + * + * It also provides a simulation of W3C event model's capture phase in + * Internet Explorer (IE 8 and below). Caveat: the simulation does not + * interact well with listeners registered directly on the elements + * (bypassing goog.events) or even with listeners registered via + * goog.events in a separate JS binary. In these cases, we provide + * no ordering guarantees. + * + * The listeners will receive a "patched" event object. Such event object + * contains normalized values for certain event properties that differs in + * different browsers. + * + * Example usage: + * <pre> + * goog.events.listen(myNode, 'click', function(e) { alert('woo') }); + * goog.events.listen(myNode, 'mouseover', mouseHandler, true); + * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true); + * goog.events.removeAll(myNode); + * </pre> + * + * @author aa@google.com (Aaron Boodman) [Original implementation of listen()] + * @author pupius@google.com (Daniel Pupius) [Port to closure plus capture phase + * in IE and event object patching] + * @author arv@google.com (Erik Arvidsson) + * + * @see ../demos/events.html + * @see ../demos/event-propagation.html + * @see ../demos/stopevent.html + */ + +// IMPLEMENTATION NOTES: +// goog.events stores an auxiliary data structure on each EventTarget +// source being listened on. This allows us to take advantage of GC, +// having the data structure GC'd when the EventTarget is GC'd. This +// GC behavior is equivalent to using W3C DOM Events directly. + +goog.provide('goog.events'); +goog.provide('goog.events.CaptureSimulationMode'); +goog.provide('goog.events.Key'); +goog.provide('goog.events.ListenableType'); + +goog.require('goog.asserts'); +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.ListenerMap'); + +goog.forwardDeclare('goog.debug.ErrorHandler'); +goog.forwardDeclare('goog.events.EventWrapper'); + + +/** + * @typedef {number|goog.events.ListenableKey} + */ +goog.events.Key; + + +/** + * @typedef {EventTarget|goog.events.Listenable} + */ +goog.events.ListenableType; + + +/** + * Property name on a native event target for the listener map + * associated with the event target. + * @private @const {string} + */ +goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0); + + +/** + * String used to prepend to IE event types. + * @const + * @private + */ +goog.events.onString_ = 'on'; + + +/** + * Map of computed "on<eventname>" strings for IE event types. Caching + * this removes an extra object allocation in goog.events.listen which + * improves IE6 performance. + * @const + * @dict + * @private + */ +goog.events.onStringMap_ = {}; + + +/** + * @enum {number} Different capture simulation mode for IE8-. + */ +goog.events.CaptureSimulationMode = { + /** + * Does not perform capture simulation. Will asserts in IE8- when you + * add capture listeners. + */ + OFF_AND_FAIL: 0, + + /** + * Does not perform capture simulation, silently ignore capture + * listeners. + */ + OFF_AND_SILENT: 1, + + /** + * Performs capture simulation. + */ + ON: 2 +}; + + +/** + * @define {number} The capture simulation mode for IE8-. By default, + * this is ON. + */ +goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2); + + +/** + * Estimated count of total native listeners. + * @private {number} + */ +goog.events.listenerCountEstimate_ = 0; + + +/** + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. A listener can only be added once + * to an object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} + * listener Callback method, or an object with a handleEvent function. + * WARNING: passing an Object is now softly deprecated. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @param {T=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + * @template T,EVENTOBJ + */ +goog.events.listen = function(src, type, listener, opt_options, opt_handler) { + if (opt_options && opt_options.once) { + return goog.events.listenOnce( + src, type, listener, opt_options, opt_handler); + } + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listen(src, type[i], listener, opt_options, opt_handler); + } + return null; + } + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + var capture = + goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; + return src.listen( + /** @type {string|!goog.events.EventId} */ (type), listener, capture, + opt_handler); + } else { + return goog.events.listen_( + /** @type {!EventTarget} */ (src), type, listener, + /* callOnce */ false, opt_options, opt_handler); + } +}; + + +/** + * Adds an event listener for a specific event on a native event + * target. A listener can only be added once to an object and if it + * is added again the key for the listener is returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {EventTarget} src The node to listen to events on. + * @param {string|?goog.events.EventId<EVENTOBJ>} type Event type. + * @param {!Function} listener Callback function. + * @param {boolean} callOnce Whether the listener is a one-off + * listener or otherwise. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template EVENTOBJ + * @private + */ +goog.events.listen_ = function( + src, type, listener, callOnce, opt_options, opt_handler) { + if (!type) { + throw new Error('Invalid event type'); + } + + var capture = + goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; + if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_FAIL) { + goog.asserts.fail('Can not register capture listener in IE8-.'); + return null; + } else if ( + goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_SILENT) { + return null; + } + } + + var listenerMap = goog.events.getListenerMap_(src); + if (!listenerMap) { + src[goog.events.LISTENER_MAP_PROP_] = listenerMap = + new goog.events.ListenerMap(src); + } + + var listenerObj = /** @type {goog.events.Listener} */ ( + listenerMap.add(type, listener, callOnce, capture, opt_handler)); + + // If the listenerObj already has a proxy, it has been set up + // previously. We simply return. + if (listenerObj.proxy) { + return listenerObj; + } + + var proxy = goog.events.getProxy(); + listenerObj.proxy = proxy; + + proxy.src = src; + proxy.listener = listenerObj; + + // Attach the proxy through the browser's API + if (src.addEventListener) { + // Don't pass an object as `capture` if the browser doesn't support that. + if (!goog.events.BrowserFeature.PASSIVE_EVENTS) { + opt_options = capture; + } + // Don't break tests that expect a boolean. + if (opt_options === undefined) opt_options = false; + src.addEventListener(type.toString(), proxy, opt_options); + } else if (src.attachEvent) { + // The else if above used to be an unconditional else. It would call + // attachEvent come gws or high water. This would sometimes throw an + // exception on IE11, spoiling the day of some callers. The previous + // incarnation of this code, from 2007, indicates that it replaced an + // earlier still version that caused excess allocations on IE6. + src.attachEvent(goog.events.getOnString_(type.toString()), proxy); + } else { + throw new Error('addEventListener and attachEvent are unavailable.'); + } + + goog.events.listenerCountEstimate_++; + return listenerObj; +}; + + +/** + * Helper function for returning a proxy function. + * @return {!Function} A new or reused function object. + */ +goog.events.getProxy = function() { + var proxyCallbackFunction = goog.events.handleBrowserEvent_; + // Use a local var f to prevent one allocation. + var f = + goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? function(eventObject) { + return proxyCallbackFunction.call(f.src, f.listener, eventObject); + } : function(eventObject) { + var v = proxyCallbackFunction.call(f.src, f.listener, eventObject); + // NOTE(chrishenry): In IE, we hack in a capture phase. However, if + // there is inline event handler which tries to prevent default (for + // example <a href="..." onclick="return false">...</a>) in a + // descendant element, the prevent default will be overridden + // by this listener if this listener were to return true. Hence, we + // return undefined. + if (!v) return v; + }; + return f; +}; + + +/** + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. After the event has fired the event + * listener is removed from the target. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null} + * listener Callback method. + * @param {(boolean|!AddEventListenerOptions)=} opt_options + * @param {T=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + * @template T,EVENTOBJ + */ +goog.events.listenOnce = function( + src, type, listener, opt_options, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listenOnce(src, type[i], listener, opt_options, opt_handler); + } + return null; + } + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + var capture = + goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; + return src.listenOnce( + /** @type {string|!goog.events.EventId} */ (type), listener, capture, + opt_handler); + } else { + return goog.events.listen_( + /** @type {!EventTarget} */ (src), type, listener, + /* callOnce */ true, opt_options, opt_handler); + } +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.Listenable}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.Listenable} src The target to + * listen to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {T=} opt_handler Element in whose scope to call the listener. + * @template T + */ +goog.events.listenWithWrapper = function( + src, wrapper, listener, opt_capt, opt_handler) { + wrapper.listen(src, listener, opt_capt, opt_handler); +}; + + +/** + * Removes an event listener which was added with listen(). + * + * @param {EventTarget|goog.events.Listenable} src The target to stop + * listening to events on. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type or array of event types to unlisten to. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {(boolean|!EventListenerOptions)=} opt_options + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {?boolean} indicating whether the listener was there to remove. + * @template EVENTOBJ + */ +goog.events.unlisten = function(src, type, listener, opt_options, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.unlisten(src, type[i], listener, opt_options, opt_handler); + } + return null; + } + var capture = + goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options; + + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.unlisten( + /** @type {string|!goog.events.EventId} */ (type), listener, capture, + opt_handler); + } + + if (!src) { + // TODO(chrishenry): We should tighten the API to only accept + // non-null objects, or add an assertion here. + return false; + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (src)); + if (listenerMap) { + var listenerObj = listenerMap.getListener( + /** @type {string|!goog.events.EventId} */ (type), listener, capture, + opt_handler); + if (listenerObj) { + return goog.events.unlistenByKey(listenerObj); + } + } + + return false; +}; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {goog.events.Key} key The key returned by listen() for this + * event listener. + * @return {boolean} indicating whether the listener was there to remove. + */ +goog.events.unlistenByKey = function(key) { + // TODO(chrishenry): Remove this check when tests that rely on this + // are fixed. + if (goog.isNumber(key)) { + return false; + } + + var listener = key; + if (!listener || listener.removed) { + return false; + } + + var src = listener.src; + if (goog.events.Listenable.isImplementedBy(src)) { + return /** @type {!goog.events.Listenable} */ (src).unlistenByKey(listener); + } + + var type = listener.type; + var proxy = listener.proxy; + if (src.removeEventListener) { + src.removeEventListener(type, proxy, listener.capture); + } else if (src.detachEvent) { + src.detachEvent(goog.events.getOnString_(type), proxy); + } + goog.events.listenerCountEstimate_--; + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (src)); + // TODO(chrishenry): Try to remove this conditional and execute the + // first branch always. This should be safe. + if (listenerMap) { + listenerMap.removeByKey(listener); + if (listenerMap.getTypeCount() == 0) { + // Null the src, just because this is simple to do (and useful + // for IE <= 7). + listenerMap.src = null; + // We don't use delete here because IE does not allow delete + // on a window object. + src[goog.events.LISTENER_MAP_PROP_] = null; + } + } else { + /** @type {!goog.events.Listener} */ (listener).markAsRemoved(); + } + + return true; +}; + + +/** + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.Listenable} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + */ +goog.events.unlistenWithWrapper = function( + src, wrapper, listener, opt_capt, opt_handler) { + wrapper.unlisten(src, listener, opt_capt, opt_handler); +}; + + +/** + * Removes all listeners from an object. You can also optionally + * remove listeners of a particular type. + * + * @param {Object|undefined} obj Object to remove listeners from. Must be an + * EventTarget or a goog.events.Listenable. + * @param {string|!goog.events.EventId=} opt_type Type of event to remove. + * Default is all types. + * @return {number} Number of listeners removed. + */ +goog.events.removeAll = function(obj, opt_type) { + // TODO(chrishenry): Change the type of obj to + // (!EventTarget|!goog.events.Listenable). + + if (!obj) { + return 0; + } + + if (goog.events.Listenable.isImplementedBy(obj)) { + return /** @type {?} */ (obj).removeAllListeners(opt_type); + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (obj)); + if (!listenerMap) { + return 0; + } + + var count = 0; + var typeStr = opt_type && opt_type.toString(); + for (var type in listenerMap.listeners) { + if (!typeStr || type == typeStr) { + // Clone so that we don't need to worry about unlistenByKey + // changing the content of the ListenerMap. + var listeners = listenerMap.listeners[type].concat(); + for (var i = 0; i < listeners.length; ++i) { + if (goog.events.unlistenByKey(listeners[i])) { + ++count; + } + } + } + } + return count; +}; + + +/** + * Gets the listeners for a given object, type and capture phase. + * + * @param {Object} obj Object to get listeners for. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Capture phase?. + * @return {Array<!goog.events.Listener>} Array of listener objects. + */ +goog.events.getListeners = function(obj, type, capture) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return /** @type {!goog.events.Listenable} */ (obj).getListeners( + type, capture); + } else { + if (!obj) { + // TODO(chrishenry): We should tighten the API to accept + // !EventTarget|goog.events.Listenable, and add an assertion here. + return []; + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (obj)); + return listenerMap ? listenerMap.getListeners(type, capture) : []; + } +}; + + +/** + * Gets the goog.events.Listener for the event or null if no such listener is + * in use. + * + * @param {EventTarget|goog.events.Listenable} src The target from + * which to get listeners. + * @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The + * listener function to get. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the + * capture or bubble phase of the event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template EVENTOBJ + */ +goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { + // TODO(chrishenry): Change type from ?string to string, or add assertion. + type = /** @type {string} */ (type); + listener = goog.events.wrapListener(listener); + var capture = !!opt_capt; + if (goog.events.Listenable.isImplementedBy(src)) { + return src.getListener(type, listener, capture, opt_handler); + } + + if (!src) { + // TODO(chrishenry): We should tighten the API to only accept + // non-null objects, or add an assertion here. + return null; + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (src)); + if (listenerMap) { + return listenerMap.getListener(type, listener, capture, opt_handler); + } + return null; +}; + + +/** + * Returns whether an event target has any active listeners matching the + * specified signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {EventTarget|goog.events.Listenable} obj Target to get + * listeners for. + * @param {string|!goog.events.EventId=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble-phase + * listeners. + * @return {boolean} Whether an event target has one or more listeners matching + * the requested type and/or capture phase. + */ +goog.events.hasListener = function(obj, opt_type, opt_capture) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return obj.hasListener(opt_type, opt_capture); + } + + var listenerMap = goog.events.getListenerMap_( + /** @type {!EventTarget} */ (obj)); + return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture); +}; + + +/** + * Provides a nice string showing the normalized event objects public members + * @param {Object} e Event Object. + * @return {string} String of the public members of the normalized event object. + */ +goog.events.expose = function(e) { + var str = []; + for (var key in e) { + if (e[key] && e[key].id) { + str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')'); + } else { + str.push(key + ' = ' + e[key]); + } + } + return str.join('\n'); +}; + + +/** + * Returns a string with on prepended to the specified type. This is used for IE + * which expects "on" to be prepended. This function caches the string in order + * to avoid extra allocations in steady state. + * @param {string} type Event type. + * @return {string} The type string with 'on' prepended. + * @private + */ +goog.events.getOnString_ = function(type) { + if (type in goog.events.onStringMap_) { + return goog.events.onStringMap_[type]; + } + return goog.events.onStringMap_[type] = goog.events.onString_ + type; +}; + + +/** + * Fires an object's listeners of a particular type and phase + * + * @param {Object} obj Object whose listeners to call. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + */ +goog.events.fireListeners = function(obj, type, capture, eventObject) { + if (goog.events.Listenable.isImplementedBy(obj)) { + return /** @type {!goog.events.Listenable} */ (obj).fireListeners( + type, capture, eventObject); + } + + return goog.events.fireListeners_(obj, type, capture, eventObject); +}; + + +/** + * Fires an object's listeners of a particular type and phase. + * @param {Object} obj Object whose listeners to call. + * @param {string|!goog.events.EventId} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + * @private + */ +goog.events.fireListeners_ = function(obj, type, capture, eventObject) { + /** @type {boolean} */ + var retval = true; + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + if (listenerMap) { + // TODO(chrishenry): Original code avoids array creation when there + // is no listener, so we do the same. If this optimization turns + // out to be not required, we can replace this with + // listenerMap.getListeners(type, capture) instead, which is simpler. + var listenerArray = listenerMap.listeners[type.toString()]; + if (listenerArray) { + listenerArray = listenerArray.concat(); + for (var i = 0; i < listenerArray.length; i++) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && listener.capture == capture && !listener.removed) { + var result = goog.events.fireListener(listener, eventObject); + retval = retval && (result !== false); + } + } + } + } + return retval; +}; + + +/** + * Fires a listener with a set of arguments + * + * @param {goog.events.Listener} listener The listener object to call. + * @param {Object} eventObject The event object to pass to the listener. + * @return {*} Result of listener. + */ +goog.events.fireListener = function(listener, eventObject) { + var listenerFn = listener.listener; + var listenerHandler = listener.handler || listener.src; + + if (listener.callOnce) { + goog.events.unlistenByKey(listener); + } + return listenerFn.call(listenerHandler, eventObject); +}; + + +/** + * Gets the total number of listeners currently in the system. + * @return {number} Number of listeners. + * @deprecated This returns estimated count, now that Closure no longer + * stores a central listener registry. We still return an estimation + * to keep existing listener-related tests passing. In the near future, + * this function will be removed. + */ +goog.events.getTotalListenerCount = function() { + return goog.events.listenerCountEstimate_; +}; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.Listenable} src The event target. + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the handlers returns false) this will also return false. + * If there are no handlers, or if all handlers return true, this returns + * true. + */ +goog.events.dispatchEvent = function(src, e) { + goog.asserts.assert( + goog.events.Listenable.isImplementedBy(src), + 'Can not use goog.events.dispatchEvent with ' + + 'non-goog.events.Listenable instance.'); + return src.dispatchEvent(e); +}; + + +/** + * Installs exception protection for the browser event entry point using the + * given error handler. + * + * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to + * protect the entry point. + */ +goog.events.protectBrowserEventEntryPoint = function(errorHandler) { + goog.events.handleBrowserEvent_ = + errorHandler.protectEntryPoint(goog.events.handleBrowserEvent_); +}; + + +/** + * Handles an event and dispatches it to the correct listeners. This + * function is a proxy for the real listener the user specified. + * + * @param {goog.events.Listener} listener The listener object. + * @param {Event=} opt_evt Optional event object that gets passed in via the + * native event handlers. + * @return {*} Result of the event handler. + * @this {EventTarget} The object or Element that fired the event. + * @private + */ +goog.events.handleBrowserEvent_ = function(listener, opt_evt) { + if (listener.removed) { + return true; + } + + // Synthesize event propagation if the browser does not support W3C + // event model. + if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + var ieEvent = opt_evt || + /** @type {Event} */ (goog.getObjectByName('window.event')); + var evt = new goog.events.BrowserEvent(ieEvent, this); + /** @type {*} */ + var retval = true; + + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.ON) { + // If we have not marked this event yet, we should perform capture + // simulation. + if (!goog.events.isMarkedIeEvent_(ieEvent)) { + goog.events.markIeEvent_(ieEvent); + + var ancestors = []; + for (var parent = evt.currentTarget; parent; + parent = parent.parentNode) { + ancestors.push(parent); + } + + // Fire capture listeners. + var type = listener.type; + for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0; + i--) { + evt.currentTarget = ancestors[i]; + var result = + goog.events.fireListeners_(ancestors[i], type, true, evt); + retval = retval && result; + } + + // Fire bubble listeners. + // + // We can technically rely on IE to perform bubble event + // propagation. However, it turns out that IE fires events in + // opposite order of attachEvent registration, which broke + // some code and tests that rely on the order. (While W3C DOM + // Level 2 Events TR leaves the event ordering unspecified, + // modern browsers and W3C DOM Level 3 Events Working Draft + // actually specify the order as the registration order.) + for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) { + evt.currentTarget = ancestors[i]; + var result = + goog.events.fireListeners_(ancestors[i], type, false, evt); + retval = retval && result; + } + } + } else { + retval = goog.events.fireListener(listener, evt); + } + return retval; + } + + // Otherwise, simply fire the listener. + return goog.events.fireListener( + listener, new goog.events.BrowserEvent(opt_evt, this)); +}; + + +/** + * This is used to mark the IE event object so we do not do the Closure pass + * twice for a bubbling event. + * @param {Event} e The IE browser event. + * @private + */ +goog.events.markIeEvent_ = function(e) { + // Only the keyCode and the returnValue can be changed. We use keyCode for + // non keyboard events. + // event.returnValue is a bit more tricky. It is undefined by default. A + // boolean false prevents the default action. In a window.onbeforeunload and + // the returnValue is non undefined it will be alerted. However, we will only + // modify the returnValue for keyboard events. We can get a problem if non + // closure events sets the keyCode or the returnValue + + var useReturnValue = false; + + if (e.keyCode == 0) { + // We cannot change the keyCode in case that srcElement is input[type=file]. + // We could test that that is the case but that would allocate 3 objects. + // If we use try/catch we will only allocate extra objects in the case of a + // failure. + + try { + e.keyCode = -1; + return; + } catch (ex) { + useReturnValue = true; + } + } + + if (useReturnValue || + /** @type {boolean|undefined} */ (e.returnValue) == undefined) { + e.returnValue = true; + } +}; + + +/** + * This is used to check if an IE event has already been handled by the Closure + * system so we do not do the Closure pass twice for a bubbling event. + * @param {Event} e The IE browser event. + * @return {boolean} True if the event object has been marked. + * @private + */ +goog.events.isMarkedIeEvent_ = function(e) { + return e.keyCode < 0 || e.returnValue != undefined; +}; + + +/** + * Counter to create unique event ids. + * @private {number} + */ +goog.events.uniqueIdCounter_ = 0; + + +/** + * Creates a unique event id. + * + * @param {string} identifier The identifier. + * @return {string} A unique identifier. + * @idGenerator {unique} + */ +goog.events.getUniqueId = function(identifier) { + return identifier + '_' + goog.events.uniqueIdCounter_++; +}; + + +/** + * @param {EventTarget} src The source object. + * @return {goog.events.ListenerMap} A listener map for the given + * source object, or null if none exists. + * @private + */ +goog.events.getListenerMap_ = function(src) { + var listenerMap = src[goog.events.LISTENER_MAP_PROP_]; + // IE serializes the property as well (e.g. when serializing outer + // HTML). So we must check that the value is of the correct type. + return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null; +}; + + +/** + * Expando property for listener function wrapper for Object with + * handleEvent. + * @private @const {string} + */ +goog.events.LISTENER_WRAPPER_PROP_ = + '__closure_events_fn_' + ((Math.random() * 1e9) >>> 0); + + +/** + * @param {Object|Function} listener The listener function or an + * object that contains handleEvent method. + * @return {!Function} Either the original function or a function that + * calls obj.handleEvent. If the same listener is passed to this + * function more than once, the same function is guaranteed to be + * returned. + */ +goog.events.wrapListener = function(listener) { + goog.asserts.assert(listener, 'Listener can not be null.'); + + if (goog.isFunction(listener)) { + return listener; + } + + goog.asserts.assert( + listener.handleEvent, 'An object listener must have handleEvent method.'); + if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) { + listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) { + return /** @type {?} */ (listener).handleEvent(e); + }; + } + return listener[goog.events.LISTENER_WRAPPER_PROP_]; +}; + + +// Register the browser event handler as an entry point, so that +// it can be monitored for exception handling, etc. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.events.handleBrowserEvent_ = + transformer(goog.events.handleBrowserEvent_); + });
diff --git a/third_party/ink/closure/events/eventtarget.js b/third_party/ink/closure/events/eventtarget.js new file mode 100644 index 0000000..b8adcaee --- /dev/null +++ b/third_party/ink/closure/events/eventtarget.js
@@ -0,0 +1,395 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A disposable implementation of a custom + * listenable/event target. See also: documentation for + * {@code goog.events.Listenable}. + * + * @author arv@google.com (Erik Arvidsson) [Original implementation] + * @author pupius@google.com (Daniel Pupius) [Port to use goog.events] + * @see ../demos/eventtarget.html + * @see goog.events.Listenable + */ + +goog.provide('goog.events.EventTarget'); + +goog.require('goog.Disposable'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.ListenerMap'); +goog.require('goog.object'); + + + +/** + * An implementation of {@code goog.events.Listenable} with full W3C + * EventTarget-like support (capture/bubble mechanism, stopping event + * propagation, preventing default actions). + * + * You may subclass this class to turn your class into a Listenable. + * + * Unless propagation is stopped, an event dispatched by an + * EventTarget will bubble to the parent returned by + * {@code getParentEventTarget}. To set the parent, call + * {@code setParentEventTarget}. Subclasses that don't support + * changing the parent can override the setter to throw an error. + * + * Example usage: + * <pre> + * var source = new goog.events.EventTarget(); + * function handleEvent(e) { + * alert('Type: ' + e.type + '; Target: ' + e.target); + * } + * source.listen('foo', handleEvent); + * // Or: goog.events.listen(source, 'foo', handleEvent); + * ... + * source.dispatchEvent('foo'); // will call handleEvent + * ... + * source.unlisten('foo', handleEvent); + * // Or: goog.events.unlisten(source, 'foo', handleEvent); + * </pre> + * + * @constructor + * @extends {goog.Disposable} + * @implements {goog.events.Listenable} + */ +goog.events.EventTarget = function() { + goog.Disposable.call(this); + + /** + * Maps of event type to an array of listeners. + * @private {!goog.events.ListenerMap} + */ + this.eventTargetListeners_ = new goog.events.ListenerMap(this); + + /** + * The object to use for event.target. Useful when mixing in an + * EventTarget to another object. + * @private {!Object} + */ + this.actualEventTarget_ = this; + + /** + * Parent event target, used during event bubbling. + * + * TODO(chrishenry): Change this to goog.events.Listenable. This + * currently breaks people who expect getParentEventTarget to return + * goog.events.EventTarget. + * + * @private {goog.events.EventTarget} + */ + this.parentEventTarget_ = null; +}; +goog.inherits(goog.events.EventTarget, goog.Disposable); +goog.events.Listenable.addImplementation(goog.events.EventTarget); + + +/** + * An artificial cap on the number of ancestors you can have. This is mainly + * for loop detection. + * @const {number} + * @private + */ +goog.events.EventTarget.MAX_ANCESTORS_ = 1000; + + +/** + * Returns the parent of this event target to use for bubbling. + * + * @return {goog.events.EventTarget} The parent EventTarget or null if + * there is no parent. + * @override + */ +goog.events.EventTarget.prototype.getParentEventTarget = function() { + return this.parentEventTarget_; +}; + + +/** + * Sets the parent of this event target to use for capture/bubble + * mechanism. + * @param {goog.events.EventTarget} parent Parent listenable (null if none). + */ +goog.events.EventTarget.prototype.setParentEventTarget = function(parent) { + this.parentEventTarget_ = parent; +}; + + +/** + * Adds an event listener to the event target. The same handler can only be + * added once per the type. Even if you add the same handler multiple times + * using the same type then it will only be called once when the event is + * dispatched. + * + * @param {string|!goog.events.EventId} type The type of the event to listen for + * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function + * to handle the event. The handler can also be an object that implements + * the handleEvent method which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + * @deprecated Use {@code #listen} instead, when possible. Otherwise, use + * {@code goog.events.listen} if you are passing Object + * (instead of Function) as handler. + */ +goog.events.EventTarget.prototype.addEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.listen(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** + * Removes an event listener from the event target. The handler must be the + * same object as the one added. If the handler has not been added then + * nothing is done. + * + * @param {string} type The type of the event to listen for. + * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function + * to handle the event. The handler can also be an object that implements + * the handleEvent method which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use + * {@code goog.events.unlisten} if you are passing Object + * (instead of Function) as handler. + */ +goog.events.EventTarget.prototype.removeEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.dispatchEvent = function(e) { + this.assertInitialized_(); + + var ancestorsTree, ancestor = this.getParentEventTarget(); + if (ancestor) { + ancestorsTree = []; + var ancestorCount = 1; + for (; ancestor; ancestor = ancestor.getParentEventTarget()) { + ancestorsTree.push(ancestor); + goog.asserts.assert( + (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_), + 'infinite loop'); + } + } + + return goog.events.EventTarget.dispatchEventInternal_( + this.actualEventTarget_, e, ancestorsTree); +}; + + +/** + * Removes listeners from this object. Classes that extend EventTarget may + * need to override this method in order to remove references to DOM Elements + * and additional listeners. + * @override + */ +goog.events.EventTarget.prototype.disposeInternal = function() { + goog.events.EventTarget.superClass_.disposeInternal.call(this); + + this.removeAllListeners(); + this.parentEventTarget_ = null; +}; + + +/** @override */ +goog.events.EventTarget.prototype.listen = function( + type, listener, opt_useCapture, opt_listenerScope) { + this.assertInitialized_(); + return this.eventTargetListeners_.add( + String(type), listener, false /* callOnce */, opt_useCapture, + opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.listenOnce = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.eventTargetListeners_.add( + String(type), listener, true /* callOnce */, opt_useCapture, + opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlisten = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.eventTargetListeners_.remove( + String(type), listener, opt_useCapture, opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlistenByKey = function(key) { + return this.eventTargetListeners_.removeByKey(key); +}; + + +/** @override */ +goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) { + // TODO(chrishenry): Previously, removeAllListeners can be called on + // uninitialized EventTarget, so we preserve that behavior. We + // should remove this when usages that rely on that fact are purged. + if (!this.eventTargetListeners_) { + return 0; + } + return this.eventTargetListeners_.removeAll(opt_type); +}; + + +/** @override */ +goog.events.EventTarget.prototype.fireListeners = function( + type, capture, eventObject) { + // TODO(chrishenry): Original code avoids array creation when there + // is no listener, so we do the same. If this optimization turns + // out to be not required, we can replace this with + // getListeners(type, capture) instead, which is simpler. + var listenerArray = this.eventTargetListeners_.listeners[String(type)]; + if (!listenerArray) { + return true; + } + listenerArray = listenerArray.concat(); + + var rv = true; + for (var i = 0; i < listenerArray.length; ++i) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && !listener.removed && listener.capture == capture) { + var listenerFn = listener.listener; + var listenerHandler = listener.handler || listener.src; + + if (listener.callOnce) { + this.unlistenByKey(listener); + } + rv = listenerFn.call(listenerHandler, eventObject) !== false && rv; + } + } + + return rv && eventObject.returnValue_ != false; +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListeners = function(type, capture) { + return this.eventTargetListeners_.getListeners(String(type), capture); +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + return this.eventTargetListeners_.getListener( + String(type), listener, capture, opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.hasListener = function( + opt_type, opt_capture) { + var id = goog.isDef(opt_type) ? String(opt_type) : undefined; + return this.eventTargetListeners_.hasListener(id, opt_capture); +}; + + +/** + * Sets the target to be used for {@code event.target} when firing + * event. Mainly used for testing. For example, see + * {@code goog.testing.events.mixinListenable}. + * @param {!Object} target The target. + */ +goog.events.EventTarget.prototype.setTargetForTesting = function(target) { + this.actualEventTarget_ = target; +}; + + +/** + * Asserts that the event target instance is initialized properly. + * @private + */ +goog.events.EventTarget.prototype.assertInitialized_ = function() { + goog.asserts.assert( + this.eventTargetListeners_, + 'Event target is not initialized. Did you call the superclass ' + + '(goog.events.EventTarget) constructor?'); +}; + + +/** + * Dispatches the given event on the ancestorsTree. + * + * @param {!Object} target The target to dispatch on. + * @param {goog.events.Event|Object|string} e The event object. + * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors + * tree of the target, in reverse order from the closest ancestor + * to the root event target. May be null if the target has no ancestor. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false) this will also return false. + * @private + */ +goog.events.EventTarget.dispatchEventInternal_ = function( + target, e, opt_ancestorsTree) { + var type = e.type || /** @type {string} */ (e); + + // If accepting a string or object, create a custom event object so that + // preventDefault and stopPropagation work with the event. + if (goog.isString(e)) { + e = new goog.events.Event(e, target); + } else if (!(e instanceof goog.events.Event)) { + var oldEvent = e; + e = new goog.events.Event(type, target); + goog.object.extend(e, oldEvent); + } else { + e.target = e.target || target; + } + + var rv = true, currentTarget; + + // Executes all capture listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0; + i--) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, true, e) && rv; + } + } + + // Executes capture and bubble listeners on the target. + if (!e.propagationStopped_) { + currentTarget = /** @type {?} */ (e.currentTarget = target); + rv = currentTarget.fireListeners(type, true, e) && rv; + if (!e.propagationStopped_) { + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + // Executes all bubble listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + return rv; +};
diff --git a/third_party/ink/closure/events/eventtype.js b/third_party/ink/closure/events/eventtype.js new file mode 100644 index 0000000..801d7f9 --- /dev/null +++ b/third_party/ink/closure/events/eventtype.js
@@ -0,0 +1,360 @@ +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Event Types. + * + * @author arv@google.com (Erik Arvidsson) + * @author mirkov@google.com (Mirko Visontai) + */ + + +goog.provide('goog.events.EventType'); +goog.provide('goog.events.PointerFallbackEventType'); + +goog.require('goog.events.BrowserFeature'); +goog.require('goog.userAgent'); + + +/** + * Returns a prefixed event name for the current browser. + * @param {string} eventName The name of the event. + * @return {string} The prefixed event name. + * @suppress {missingRequire|missingProvide} + * @private + */ +goog.events.getVendorPrefixedName_ = function(eventName) { + return goog.userAgent.WEBKIT ? + 'webkit' + eventName : + (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() : + eventName.toLowerCase()); +}; + + +/** + * Constants for event names. + * @enum {string} + */ +goog.events.EventType = { + // Mouse events + CLICK: 'click', + RIGHTCLICK: 'rightclick', + DBLCLICK: 'dblclick', + MOUSEDOWN: 'mousedown', + MOUSEUP: 'mouseup', + MOUSEOVER: 'mouseover', + MOUSEOUT: 'mouseout', + MOUSEMOVE: 'mousemove', + MOUSEENTER: 'mouseenter', + MOUSELEAVE: 'mouseleave', + + // Selection events. + // https://www.w3.org/TR/selection-api/ + SELECTIONCHANGE: 'selectionchange', + SELECTSTART: 'selectstart', // IE, Safari, Chrome + + // Wheel events + // http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents + WHEEL: 'wheel', + + // Key events + KEYPRESS: 'keypress', + KEYDOWN: 'keydown', + KEYUP: 'keyup', + + // Focus + BLUR: 'blur', + FOCUS: 'focus', + DEACTIVATE: 'deactivate', // IE only + // NOTE: The following two events are not stable in cross-browser usage. + // WebKit and Opera implement DOMFocusIn/Out. + // IE implements focusin/out. + // Gecko implements neither see bug at + // https://bugzilla.mozilla.org/show_bug.cgi?id=396927. + // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin: + // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html + // You can use FOCUS in Capture phase until implementations converge. + FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn', + FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut', + + // Forms + CHANGE: 'change', + RESET: 'reset', + SELECT: 'select', + SUBMIT: 'submit', + INPUT: 'input', + PROPERTYCHANGE: 'propertychange', // IE only + + // Drag and drop + DRAGSTART: 'dragstart', + DRAG: 'drag', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DRAGLEAVE: 'dragleave', + DROP: 'drop', + DRAGEND: 'dragend', + + // Touch events + // Note that other touch events exist, but we should follow the W3C list here. + // http://www.w3.org/TR/touch-events/#list-of-touchevent-types + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + TOUCHCANCEL: 'touchcancel', + + // Misc + BEFOREUNLOAD: 'beforeunload', + CONSOLEMESSAGE: 'consolemessage', + CONTEXTMENU: 'contextmenu', + DEVICEMOTION: 'devicemotion', + DEVICEORIENTATION: 'deviceorientation', + DOMCONTENTLOADED: 'DOMContentLoaded', + ERROR: 'error', + HELP: 'help', + LOAD: 'load', + LOSECAPTURE: 'losecapture', + ORIENTATIONCHANGE: 'orientationchange', + READYSTATECHANGE: 'readystatechange', + RESIZE: 'resize', + SCROLL: 'scroll', + UNLOAD: 'unload', + + // Media events + CANPLAY: 'canplay', + CANPLAYTHROUGH: 'canplaythrough', + DURATIONCHANGE: 'durationchange', + EMPTIED: 'emptied', + ENDED: 'ended', + LOADEDDATA: 'loadeddata', + LOADEDMETADATA: 'loadedmetadata', + PAUSE: 'pause', + PLAY: 'play', + PLAYING: 'playing', + RATECHANGE: 'ratechange', + SEEKED: 'seeked', + SEEKING: 'seeking', + STALLED: 'stalled', + SUSPEND: 'suspend', + TIMEUPDATE: 'timeupdate', + VOLUMECHANGE: 'volumechange', + WAITING: 'waiting', + + // Media Source Extensions events + // https://www.w3.org/TR/media-source/#mediasource-events + SOURCEOPEN: 'sourceopen', + SOURCEENDED: 'sourceended', + SOURCECLOSED: 'sourceclosed', + // https://www.w3.org/TR/media-source/#sourcebuffer-events + ABORT: 'abort', + UPDATE: 'update', + UPDATESTART: 'updatestart', + UPDATEEND: 'updateend', + + // HTML 5 History events + // See http://www.w3.org/TR/html5/browsers.html#event-definitions-0 + HASHCHANGE: 'hashchange', + PAGEHIDE: 'pagehide', + PAGESHOW: 'pageshow', + POPSTATE: 'popstate', + + // Copy and Paste + // Support is limited. Make sure it works on your favorite browser + // before using. + // http://www.quirksmode.org/dom/events/cutcopypaste.html + COPY: 'copy', + PASTE: 'paste', + CUT: 'cut', + BEFORECOPY: 'beforecopy', + BEFORECUT: 'beforecut', + BEFOREPASTE: 'beforepaste', + + // HTML5 online/offline events. + // http://www.w3.org/TR/offline-webapps/#related + ONLINE: 'online', + OFFLINE: 'offline', + + // HTML 5 worker events + MESSAGE: 'message', + CONNECT: 'connect', + + // Service Worker Events - ServiceWorkerGlobalScope context + // See https://w3c.github.io/ServiceWorker/#execution-context-events + // Note: message event defined in worker events section + INSTALL: 'install', + ACTIVATE: 'activate', + FETCH: 'fetch', + FOREIGNFETCH: 'foreignfetch', + MESSAGEERROR: 'messageerror', + + // Service Worker Events - Document context + // See https://w3c.github.io/ServiceWorker/#document-context-events + STATECHANGE: 'statechange', + UPDATEFOUND: 'updatefound', + CONTROLLERCHANGE: 'controllerchange', + + // CSS animation events. + /** @suppress {missingRequire} */ + ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'), + /** @suppress {missingRequire} */ + ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'), + /** @suppress {missingRequire} */ + ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'), + + // CSS transition events. Based on the browser support described at: + // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility + /** @suppress {missingRequire} */ + TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'), + + // W3C Pointer Events + // http://www.w3.org/TR/pointerevents/ + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTERCANCEL: 'pointercancel', + POINTERMOVE: 'pointermove', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + GOTPOINTERCAPTURE: 'gotpointercapture', + LOSTPOINTERCAPTURE: 'lostpointercapture', + + // IE specific events. + // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx + // Note: these events will be supplanted in IE11. + MSGESTURECHANGE: 'MSGestureChange', + MSGESTUREEND: 'MSGestureEnd', + MSGESTUREHOLD: 'MSGestureHold', + MSGESTURESTART: 'MSGestureStart', + MSGESTURETAP: 'MSGestureTap', + MSGOTPOINTERCAPTURE: 'MSGotPointerCapture', + MSINERTIASTART: 'MSInertiaStart', + MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture', + MSPOINTERCANCEL: 'MSPointerCancel', + MSPOINTERDOWN: 'MSPointerDown', + MSPOINTERENTER: 'MSPointerEnter', + MSPOINTERHOVER: 'MSPointerHover', + MSPOINTERLEAVE: 'MSPointerLeave', + MSPOINTERMOVE: 'MSPointerMove', + MSPOINTEROUT: 'MSPointerOut', + MSPOINTEROVER: 'MSPointerOver', + MSPOINTERUP: 'MSPointerUp', + + // Native IMEs/input tools events. + TEXT: 'text', + // The textInput event is supported in IE9+, but only in lower case. All other + // browsers use the camel-case event name. + TEXTINPUT: goog.userAgent.IE ? 'textinput' : 'textInput', + COMPOSITIONSTART: 'compositionstart', + COMPOSITIONUPDATE: 'compositionupdate', + COMPOSITIONEND: 'compositionend', + + // The beforeinput event is initially only supported in Safari. See + // https://bugs.chromium.org/p/chromium/issues/detail?id=342670 for Chrome + // implementation tracking. + BEFOREINPUT: 'beforeinput', + + // Webview tag events + // See http://developer.chrome.com/dev/apps/webview_tag.html + EXIT: 'exit', + LOADABORT: 'loadabort', + LOADCOMMIT: 'loadcommit', + LOADREDIRECT: 'loadredirect', + LOADSTART: 'loadstart', + LOADSTOP: 'loadstop', + RESPONSIVE: 'responsive', + SIZECHANGED: 'sizechanged', + UNRESPONSIVE: 'unresponsive', + + // HTML5 Page Visibility API. See details at + // {@code goog.labs.dom.PageVisibilityMonitor}. + VISIBILITYCHANGE: 'visibilitychange', + + // LocalStorage event. + STORAGE: 'storage', + + // DOM Level 2 mutation events (deprecated). + DOMSUBTREEMODIFIED: 'DOMSubtreeModified', + DOMNODEINSERTED: 'DOMNodeInserted', + DOMNODEREMOVED: 'DOMNodeRemoved', + DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument', + DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument', + DOMATTRMODIFIED: 'DOMAttrModified', + DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified', + + // Print events. + BEFOREPRINT: 'beforeprint', + AFTERPRINT: 'afterprint' +}; + + +/** + * Returns one of the given pointer fallback event names in order of preference: + * 1. pointerEventName + * 2. msPointerEventName + * 3. mouseEventName + * @param {string} pointerEventName + * @param {string} msPointerEventName + * @param {string} mouseEventName + * @return {string} The supported pointer or mouse event name. + * @private + */ +goog.events.getPointerFallbackEventName_ = function( + pointerEventName, msPointerEventName, mouseEventName) { + if (goog.events.BrowserFeature.POINTER_EVENTS) { + return pointerEventName; + } + if (goog.events.BrowserFeature.MSPOINTER_EVENTS) { + return msPointerEventName; + } + return mouseEventName; +}; + + +/** + * Constants for pointer event names that fall back to corresponding mouse event + * names on unsupported platforms. These are intended to be drop-in replacements + * for corresponding values in {@code goog.events.EventType}. + * @enum {string} + */ +goog.events.PointerFallbackEventType = { + POINTERDOWN: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERDOWN, goog.events.EventType.MSPOINTERDOWN, + goog.events.EventType.MOUSEDOWN), + POINTERUP: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERUP, goog.events.EventType.MSPOINTERUP, + goog.events.EventType.MOUSEUP), + POINTERCANCEL: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERCANCEL, + goog.events.EventType.MSPOINTERCANCEL, + // When falling back to mouse events, there is no MOUSECANCEL equivalent + // of POINTERCANCEL. In this case POINTERUP already falls back to MOUSEUP + // which represents both UP and CANCEL. POINTERCANCEL does not fall back + // to MOUSEUP to prevent listening twice on the same event. + 'mousecancel'), // non-existent event; will never fire + POINTERMOVE: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERMOVE, goog.events.EventType.MSPOINTERMOVE, + goog.events.EventType.MOUSEMOVE), + POINTEROVER: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTEROVER, goog.events.EventType.MSPOINTEROVER, + goog.events.EventType.MOUSEOVER), + POINTEROUT: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTEROUT, goog.events.EventType.MSPOINTEROUT, + goog.events.EventType.MOUSEOUT), + POINTERENTER: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERENTER, goog.events.EventType.MSPOINTERENTER, + goog.events.EventType.MOUSEENTER), + POINTERLEAVE: goog.events.getPointerFallbackEventName_( + goog.events.EventType.POINTERLEAVE, goog.events.EventType.MSPOINTERLEAVE, + goog.events.EventType.MOUSELEAVE) +};
diff --git a/third_party/ink/closure/events/keycodes.js b/third_party/ink/closure/events/keycodes.js new file mode 100644 index 0000000..745a6995 --- /dev/null +++ b/third_party/ink/closure/events/keycodes.js
@@ -0,0 +1,439 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Constant declarations for common key codes. + * + * @author eae@google.com (Emil A Eklund) + * @see ../demos/keyhandler.html + */ + +goog.provide('goog.events.KeyCodes'); + +goog.require('goog.userAgent'); + +goog.forwardDeclare('goog.events.BrowserEvent'); + + +/** + * Key codes for common characters. + * + * This list is not localized and therefore some of the key codes are not + * correct for non US keyboard layouts. See comments below. + * + * @enum {number} + */ +goog.events.KeyCodes = { + WIN_KEY_FF_LINUX: 0, + MAC_ENTER: 3, + BACKSPACE: 8, + TAB: 9, + NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAUSE: 19, + CAPS_LOCK: 20, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, // also NUM_NORTH_EAST + PAGE_DOWN: 34, // also NUM_SOUTH_EAST + END: 35, // also NUM_SOUTH_WEST + HOME: 36, // also NUM_NORTH_WEST + LEFT: 37, // also NUM_WEST + UP: 38, // also NUM_NORTH + RIGHT: 39, // also NUM_EAST + DOWN: 40, // also NUM_SOUTH + PLUS_SIGN: 43, // NOT numpad plus + PRINT_SCREEN: 44, + INSERT: 45, // also NUM_INSERT + DELETE: 46, // also NUM_DELETE + ZERO: 48, + ONE: 49, + TWO: 50, + THREE: 51, + FOUR: 52, + FIVE: 53, + SIX: 54, + SEVEN: 55, + EIGHT: 56, + NINE: 57, + FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186 + FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187 + FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189 + QUESTION_MARK: 63, // needs localization + AT_SIGN: 64, + A: 65, + B: 66, + C: 67, + D: 68, + E: 69, + F: 70, + G: 71, + H: 72, + I: 73, + J: 74, + K: 75, + L: 76, + M: 77, + N: 78, + O: 79, + P: 80, + Q: 81, + R: 82, + S: 83, + T: 84, + U: 85, + V: 86, + W: 87, + X: 88, + Y: 89, + Z: 90, + META: 91, // WIN_KEY_LEFT + WIN_KEY_RIGHT: 92, + CONTEXT_MENU: 93, + NUM_ZERO: 96, + NUM_ONE: 97, + NUM_TWO: 98, + NUM_THREE: 99, + NUM_FOUR: 100, + NUM_FIVE: 101, + NUM_SIX: 102, + NUM_SEVEN: 103, + NUM_EIGHT: 104, + NUM_NINE: 105, + NUM_MULTIPLY: 106, + NUM_PLUS: 107, + NUM_MINUS: 109, + NUM_PERIOD: 110, + NUM_DIVISION: 111, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + NUMLOCK: 144, + SCROLL_LOCK: 145, + + // OS-specific media keys like volume controls and browser controls. + FIRST_MEDIA_KEY: 166, + LAST_MEDIA_KEY: 183, + + SEMICOLON: 186, // needs localization + DASH: 189, // needs localization + EQUALS: 187, // needs localization + COMMA: 188, // needs localization + PERIOD: 190, // needs localization + SLASH: 191, // needs localization + APOSTROPHE: 192, // needs localization + TILDE: 192, // needs localization + SINGLE_QUOTE: 222, // needs localization + OPEN_SQUARE_BRACKET: 219, // needs localization + BACKSLASH: 220, // needs localization + CLOSE_SQUARE_BRACKET: 221, // needs localization + WIN_KEY: 224, + MAC_FF_META: + 224, // Firefox (Gecko) fires this for the meta key instead of 91 + MAC_WK_CMD_LEFT: 91, // WebKit Left Command key fired, same as META + MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META + WIN_IME: 229, + + // "Reserved for future use". Some programs (e.g. the SlingPlayer 2.4 ActiveX + // control) fire this as a hacky way to disable screensavers. + VK_NONAME: 252, + + // We've seen users whose machines fire this keycode at regular one + // second intervals. The common thread among these users is that + // they're all using Dell Inspiron laptops, so we suspect that this + // indicates a hardware/bios problem. + // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx + PHANTOM: 255 +}; + + +/** + * Returns false if the event does not contain a text modifying key. + * + * When it returns true, the event might be text modifying. It is infeasible to + * say for sure because of the many different keyboard layouts, so this method + * errs on the side of assuming a key event is text-modifiable if we cannot be + * certain it is not. As an example, it will return true for ctrl+a, though in + * many standard keyboard layouts that key combination would mean "select all", + * and not actually modify the text. + * + * @param {goog.events.BrowserEvent} e A key event. + * @return {boolean} Whether it's a text modifying key. + */ +goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) { + if (e.altKey && !e.ctrlKey || e.metaKey || + // Function keys don't generate text + e.keyCode >= goog.events.KeyCodes.F1 && + e.keyCode <= goog.events.KeyCodes.F12) { + return false; + } + + // The following keys are quite harmless, even in combination with + // CTRL, ALT or SHIFT. + switch (e.keyCode) { + case goog.events.KeyCodes.ALT: + case goog.events.KeyCodes.CAPS_LOCK: + case goog.events.KeyCodes.CONTEXT_MENU: + case goog.events.KeyCodes.CTRL: + case goog.events.KeyCodes.DOWN: + case goog.events.KeyCodes.END: + case goog.events.KeyCodes.ESC: + case goog.events.KeyCodes.HOME: + case goog.events.KeyCodes.INSERT: + case goog.events.KeyCodes.LEFT: + case goog.events.KeyCodes.MAC_FF_META: + case goog.events.KeyCodes.META: + case goog.events.KeyCodes.NUMLOCK: + case goog.events.KeyCodes.NUM_CENTER: + case goog.events.KeyCodes.PAGE_DOWN: + case goog.events.KeyCodes.PAGE_UP: + case goog.events.KeyCodes.PAUSE: + case goog.events.KeyCodes.PHANTOM: + case goog.events.KeyCodes.PRINT_SCREEN: + case goog.events.KeyCodes.RIGHT: + case goog.events.KeyCodes.SCROLL_LOCK: + case goog.events.KeyCodes.SHIFT: + case goog.events.KeyCodes.UP: + case goog.events.KeyCodes.VK_NONAME: + case goog.events.KeyCodes.WIN_KEY: + case goog.events.KeyCodes.WIN_KEY_RIGHT: + return false; + case goog.events.KeyCodes.WIN_KEY_FF_LINUX: + return !goog.userAgent.GECKO; + default: + return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY || + e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY; + } +}; + + +/** + * Returns true if the key fires a keypress event in the current browser. + * + * Accoridng to MSDN [1] IE only fires keypress events for the following keys: + * - Letters: A - Z (uppercase and lowercase) + * - Numerals: 0 - 9 + * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ + * - System: ESC, SPACEBAR, ENTER + * + * That's not entirely correct though, for instance there's no distinction + * between upper and lower case letters. + * + * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx) + * + * Safari is similar to IE, but does not fire keypress for ESC. + * + * Additionally, IE6 does not fire keydown or keypress events for letters when + * the control or alt keys are held down and the shift key is not. IE7 does + * fire keydown in these cases, though, but not keypress. + * + * @param {number} keyCode A key code. + * @param {number=} opt_heldKeyCode Key code of a currently-held key. + * @param {boolean=} opt_shiftKey Whether the shift key is held down. + * @param {boolean=} opt_ctrlKey Whether the control key is held down. + * @param {boolean=} opt_altKey Whether the alt key is held down. + * @param {boolean=} opt_metaKey Whether the meta key is held down. + * @return {boolean} Whether it's a key that fires a keypress event. + */ +goog.events.KeyCodes.firesKeyPressEvent = function( + keyCode, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey, + opt_metaKey) { + if (!goog.userAgent.IE && !goog.userAgent.EDGE && + !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) { + return true; + } + + if (goog.userAgent.MAC && opt_altKey) { + return goog.events.KeyCodes.isCharacterKey(keyCode); + } + + // Alt but not AltGr which is represented as Alt+Ctrl. + if (opt_altKey && !opt_ctrlKey) { + return false; + } + + // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress. + // Non-IE browsers and WebKit prior to 525 won't get this far so no need to + // check the user agent. + if (goog.isNumber(opt_heldKeyCode)) { + opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode); + } + var heldKeyIsModifier = opt_heldKeyCode == goog.events.KeyCodes.CTRL || + opt_heldKeyCode == goog.events.KeyCodes.ALT || + goog.userAgent.MAC && opt_heldKeyCode == goog.events.KeyCodes.META; + // The Shift key blocks keypresses on Mac iff accompanied by another modifier. + var modifiedShiftKey = opt_heldKeyCode == goog.events.KeyCodes.SHIFT && + (opt_ctrlKey || opt_metaKey); + if ((!opt_shiftKey || goog.userAgent.MAC) && heldKeyIsModifier || + goog.userAgent.MAC && modifiedShiftKey) { + return false; + } + + // Some keys with Ctrl/Shift do not issue keypress in WEBKIT. + if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) && opt_ctrlKey && + opt_shiftKey) { + switch (keyCode) { + case goog.events.KeyCodes.BACKSLASH: + case goog.events.KeyCodes.OPEN_SQUARE_BRACKET: + case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET: + case goog.events.KeyCodes.TILDE: + case goog.events.KeyCodes.SEMICOLON: + case goog.events.KeyCodes.DASH: + case goog.events.KeyCodes.EQUALS: + case goog.events.KeyCodes.COMMA: + case goog.events.KeyCodes.PERIOD: + case goog.events.KeyCodes.SLASH: + case goog.events.KeyCodes.APOSTROPHE: + case goog.events.KeyCodes.SINGLE_QUOTE: + return false; + } + } + + // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it + // continues to fire keydown events as the event repeats. + if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) { + return false; + } + + switch (keyCode) { + case goog.events.KeyCodes.ENTER: + return true; + case goog.events.KeyCodes.ESC: + return !(goog.userAgent.WEBKIT || goog.userAgent.EDGE); + } + + return goog.events.KeyCodes.isCharacterKey(keyCode); +}; + + +/** + * Returns true if the key produces a character. + * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.). + * + * @param {number} keyCode A key code. + * @return {boolean} Whether it's a character key. + */ +goog.events.KeyCodes.isCharacterKey = function(keyCode) { + if (keyCode >= goog.events.KeyCodes.ZERO && + keyCode <= goog.events.KeyCodes.NINE) { + return true; + } + + if (keyCode >= goog.events.KeyCodes.NUM_ZERO && + keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) { + return true; + } + + if (keyCode >= goog.events.KeyCodes.A && keyCode <= goog.events.KeyCodes.Z) { + return true; + } + + // Safari sends zero key code for non-latin characters. + if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) && keyCode == 0) { + return true; + } + + switch (keyCode) { + case goog.events.KeyCodes.SPACE: + case goog.events.KeyCodes.PLUS_SIGN: + case goog.events.KeyCodes.QUESTION_MARK: + case goog.events.KeyCodes.AT_SIGN: + case goog.events.KeyCodes.NUM_PLUS: + case goog.events.KeyCodes.NUM_MINUS: + case goog.events.KeyCodes.NUM_PERIOD: + case goog.events.KeyCodes.NUM_DIVISION: + case goog.events.KeyCodes.SEMICOLON: + case goog.events.KeyCodes.FF_SEMICOLON: + case goog.events.KeyCodes.DASH: + case goog.events.KeyCodes.EQUALS: + case goog.events.KeyCodes.FF_EQUALS: + case goog.events.KeyCodes.COMMA: + case goog.events.KeyCodes.PERIOD: + case goog.events.KeyCodes.SLASH: + case goog.events.KeyCodes.APOSTROPHE: + case goog.events.KeyCodes.SINGLE_QUOTE: + case goog.events.KeyCodes.OPEN_SQUARE_BRACKET: + case goog.events.KeyCodes.BACKSLASH: + case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET: + return true; + default: + return false; + } +}; + + +/** + * Normalizes key codes from OS/Browser-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeKeyCode = function(keyCode) { + if (goog.userAgent.GECKO) { + return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode); + } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) { + return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode); + } else { + return keyCode; + } +}; + + +/** + * Normalizes key codes from their Gecko-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) { + switch (keyCode) { + case goog.events.KeyCodes.FF_EQUALS: + return goog.events.KeyCodes.EQUALS; + case goog.events.KeyCodes.FF_SEMICOLON: + return goog.events.KeyCodes.SEMICOLON; + case goog.events.KeyCodes.FF_DASH: + return goog.events.KeyCodes.DASH; + case goog.events.KeyCodes.MAC_FF_META: + return goog.events.KeyCodes.META; + case goog.events.KeyCodes.WIN_KEY_FF_LINUX: + return goog.events.KeyCodes.WIN_KEY; + default: + return keyCode; + } +}; + + +/** + * Normalizes key codes from their Mac WebKit-specific value to the general one. + * @param {number} keyCode The native key code. + * @return {number} The normalized key code. + */ +goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) { + switch (keyCode) { + case goog.events.KeyCodes.MAC_WK_CMD_RIGHT: // 93 + return goog.events.KeyCodes.META; // 91 + default: + return keyCode; + } +};
diff --git a/third_party/ink/closure/events/listenable.js b/third_party/ink/closure/events/listenable.js new file mode 100644 index 0000000..0f29d81 --- /dev/null +++ b/third_party/ink/closure/events/listenable.js
@@ -0,0 +1,338 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An interface for a listenable JavaScript object. + * @author chrishenry@google.com (Chris Henry) + */ + +goog.provide('goog.events.Listenable'); +goog.provide('goog.events.ListenableKey'); + +/** @suppress {extraRequire} */ +goog.require('goog.events.EventId'); + +goog.forwardDeclare('goog.events.EventLike'); +goog.forwardDeclare('goog.events.EventTarget'); + + + +/** + * A listenable interface. A listenable is an object with the ability + * to dispatch/broadcast events to "event listeners" registered via + * listen/listenOnce. + * + * The interface allows for an event propagation mechanism similar + * to one offered by native browser event targets, such as + * capture/bubble mechanism, stopping propagation, and preventing + * default actions. Capture/bubble mechanism depends on the ancestor + * tree constructed via {@code #getParentEventTarget}; this tree + * must be directed acyclic graph. The meaning of default action(s) + * in preventDefault is specific to a particular use case. + * + * Implementations that do not support capture/bubble or can not have + * a parent listenable can simply not implement any ability to set the + * parent listenable (and have {@code #getParentEventTarget} return + * null). + * + * Implementation of this class can be used with or independently from + * goog.events. + * + * Implementation must call {@code #addImplementation(implClass)}. + * + * @interface + * @see goog.events + * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html + */ +goog.events.Listenable = function() {}; + + +/** + * An expando property to indicate that an object implements + * goog.events.Listenable. + * + * See addImplementation/isImplementedBy. + * + * @type {string} + * @const + */ +goog.events.Listenable.IMPLEMENTED_BY_PROP = + 'closure_listenable_' + ((Math.random() * 1e6) | 0); + + +/** + * Marks a given class (constructor) as an implementation of + * Listenable, do that we can query that fact at runtime. The class + * must have already implemented the interface. + * @param {!function(new:goog.events.Listenable,...)} cls The class constructor. + * The corresponding class must have already implemented the interface. + */ +goog.events.Listenable.addImplementation = function(cls) { + cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true; +}; + + +/** + * @param {Object} obj The object to check. + * @return {boolean} Whether a given instance implements Listenable. The + * class/superclass of the instance must call addImplementation. + */ +goog.events.Listenable.isImplementedBy = function(obj) { + return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]); +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {!goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.listen; + + +/** + * Adds an event listener that is removed automatically after the + * listener fired once. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {!goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.listenOnce; + + +/** + * Removes an event listener which was added with listen() or listenOnce(). + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call + * the listener. + * @return {boolean} Whether any listener was removed. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.unlisten; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {!goog.events.ListenableKey} key The key returned by + * listen() or listenOnce(). + * @return {boolean} Whether any listener was removed. + */ +goog.events.Listenable.prototype.unlistenByKey; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false) this will also return false. + */ +goog.events.Listenable.prototype.dispatchEvent; + + +/** + * Removes all listeners from this listenable. If type is specified, + * it will only remove listeners of the particular type. otherwise all + * registered listeners will be removed. + * + * @param {string=} opt_type Type of event to remove, default is to + * remove all types. + * @return {number} Number of listeners removed. + */ +goog.events.Listenable.prototype.removeAllListeners; + + +/** + * Returns the parent of this event target to use for capture/bubble + * mechanism. + * + * NOTE(chrishenry): The name reflects the original implementation of + * custom event target ({@code goog.events.EventTarget}). We decided + * that changing the name is not worth it. + * + * @return {goog.events.Listenable} The parent EventTarget or null if + * there is no parent. + */ +goog.events.Listenable.prototype.getParentEventTarget; + + +/** + * Fires all registered listeners in this listenable for the given + * type and capture mode, passing them the given eventObject. This + * does not perform actual capture/bubble. Only implementors of the + * interface should be using this. + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the + * listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @param {EVENTOBJ} eventObject The event object to fire. + * @return {boolean} Whether all listeners succeeded without + * attempting to prevent default behavior. If any listener returns + * false or called goog.events.Event#preventDefault, this returns + * false. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.fireListeners; + + +/** + * Gets all listeners in this listenable for the given type and + * capture mode. + * + * @param {string|!goog.events.EventId} type The type of the listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @return {!Array<!goog.events.ListenableKey>} An array of registered + * listeners. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.getListeners; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event + * without the 'on' prefix. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The + * listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.getListener; + + +/** + * Whether there is any active listeners matching the specified + * signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble + * listeners. + * @return {boolean} Whether there is any active listeners matching + * the requested type and/or capture phase. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.hasListener; + + + +/** + * An interface that describes a single registered listener. + * @interface + */ +goog.events.ListenableKey = function() {}; + + +/** + * Counter used to create a unique key + * @type {number} + * @private + */ +goog.events.ListenableKey.counter_ = 0; + + +/** + * Reserves a key to be used for ListenableKey#key field. + * @return {number} A number to be used to fill ListenableKey#key + * field. + */ +goog.events.ListenableKey.reserveKey = function() { + return ++goog.events.ListenableKey.counter_; +}; + + +/** + * The source event target. + * @type {Object|goog.events.Listenable|goog.events.EventTarget} + */ +goog.events.ListenableKey.prototype.src; + + +/** + * The event type the listener is listening to. + * @type {string} + */ +goog.events.ListenableKey.prototype.type; + + +/** + * The listener function. + * @type {function(?):?|{handleEvent:function(?):?}|null} + */ +goog.events.ListenableKey.prototype.listener; + + +/** + * Whether the listener works on capture phase. + * @type {boolean} + */ +goog.events.ListenableKey.prototype.capture; + + +/** + * The 'this' object for the listener function's scope. + * @type {Object|undefined} + */ +goog.events.ListenableKey.prototype.handler; + + +/** + * A globally unique number to identify the key. + * @type {number} + */ +goog.events.ListenableKey.prototype.key;
diff --git a/third_party/ink/closure/events/listener.js b/third_party/ink/closure/events/listener.js new file mode 100644 index 0000000..282c69fc --- /dev/null +++ b/third_party/ink/closure/events/listener.js
@@ -0,0 +1,129 @@ +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Listener object. + * @author pupius@google.com (Daniel Pupius) + * @see ../demos/events.html + */ + +goog.provide('goog.events.Listener'); + +goog.require('goog.events.ListenableKey'); + + + +/** + * Simple class that stores information about a listener + * @param {function(?):?} listener Callback function. + * @param {Function} proxy Wrapper for the listener that patches the event. + * @param {EventTarget|goog.events.Listenable} src Source object for + * the event. + * @param {string} type Event type. + * @param {boolean} capture Whether in capture or bubble phase. + * @param {Object=} opt_handler Object in whose context to execute the callback. + * @implements {goog.events.ListenableKey} + * @constructor + */ +goog.events.Listener = function( + listener, proxy, src, type, capture, opt_handler) { + if (goog.events.Listener.ENABLE_MONITORING) { + this.creationStack = new Error().stack; + } + + /** @override */ + this.listener = listener; + + /** + * A wrapper over the original listener. This is used solely to + * handle native browser events (it is used to simulate the capture + * phase and to patch the event object). + * @type {Function} + */ + this.proxy = proxy; + + /** + * Object or node that callback is listening to + * @type {EventTarget|goog.events.Listenable} + */ + this.src = src; + + /** + * The event type. + * @const {string} + */ + this.type = type; + + /** + * Whether the listener is being called in the capture or bubble phase + * @const {boolean} + */ + this.capture = !!capture; + + /** + * Optional object whose context to execute the listener in + * @type {Object|undefined} + */ + this.handler = opt_handler; + + /** + * The key of the listener. + * @const {number} + * @override + */ + this.key = goog.events.ListenableKey.reserveKey(); + + /** + * Whether to remove the listener after it has been called. + * @type {boolean} + */ + this.callOnce = false; + + /** + * Whether the listener has been removed. + * @type {boolean} + */ + this.removed = false; +}; + + +/** + * @define {boolean} Whether to enable the monitoring of the + * goog.events.Listener instances. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.define('goog.events.Listener.ENABLE_MONITORING', false); + + +/** + * If monitoring the goog.events.Listener instances is enabled, stores the + * creation stack trace of the Disposable instance. + * @type {string} + */ +goog.events.Listener.prototype.creationStack; + + +/** + * Marks this listener as removed. This also remove references held by + * this listener object (such as listener and event source). + */ +goog.events.Listener.prototype.markAsRemoved = function() { + this.removed = true; + this.listener = null; + this.proxy = null; + this.src = null; + this.handler = null; +};
diff --git a/third_party/ink/closure/events/listenermap.js b/third_party/ink/closure/events/listenermap.js new file mode 100644 index 0000000..30fea18 --- /dev/null +++ b/third_party/ink/closure/events/listenermap.js
@@ -0,0 +1,307 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A map of listeners that provides utility functions to + * deal with listeners on an event target. Used by + * {@code goog.events.EventTarget}. + * + * WARNING: Do not use this class from outside goog.events package. + * + * @visibility {//javascript/closure/bin/sizetests:__pkg__} + * @visibility {//javascript/closure:__pkg__} + * @visibility {//javascript/closure/events:__pkg__} + * @visibility {//javascript/closure/labs/events:__pkg__} + */ + +goog.provide('goog.events.ListenerMap'); + +goog.require('goog.array'); +goog.require('goog.events.Listener'); +goog.require('goog.object'); + + + +/** + * Creates a new listener map. + * @param {EventTarget|goog.events.Listenable} src The src object. + * @constructor + * @final + */ +goog.events.ListenerMap = function(src) { + /** @type {EventTarget|goog.events.Listenable} */ + this.src = src; + + /** + * Maps of event type to an array of listeners. + * @type {!Object<string, !Array<!goog.events.Listener>>} + */ + this.listeners = {}; + + /** + * The count of types in this map that have registered listeners. + * @private {number} + */ + this.typeCount_ = 0; +}; + + +/** + * @return {number} The count of event types in this map that actually + * have registered listeners. + */ +goog.events.ListenerMap.prototype.getTypeCount = function() { + return this.typeCount_; +}; + + +/** + * @return {number} Total number of registered listeners. + */ +goog.events.ListenerMap.prototype.getListenerCount = function() { + var count = 0; + for (var type in this.listeners) { + count += this.listeners[type].length; + } + return count; +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean} callOnce Whether the listener is a one-off + * listener. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {!goog.events.ListenableKey} Unique key for the listener. + */ +goog.events.ListenerMap.prototype.add = function( + type, listener, callOnce, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + var listenerArray = this.listeners[typeStr]; + if (!listenerArray) { + listenerArray = this.listeners[typeStr] = []; + this.typeCount_++; + } + + var listenerObj; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + listenerObj = listenerArray[index]; + if (!callOnce) { + // Ensure that, if there is an existing callOnce listener, it is no + // longer a callOnce listener. + listenerObj.callOnce = false; + } + } else { + listenerObj = new goog.events.Listener( + listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope); + listenerObj.callOnce = callOnce; + listenerArray.push(listenerObj); + } + return listenerObj; +}; + + +/** + * Removes a matching listener. + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {boolean} Whether any listener was removed. + */ +goog.events.ListenerMap.prototype.remove = function( + type, listener, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + if (!(typeStr in this.listeners)) { + return false; + } + + var listenerArray = this.listeners[typeStr]; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + var listenerObj = listenerArray[index]; + listenerObj.markAsRemoved(); + goog.array.removeAt(listenerArray, index); + if (listenerArray.length == 0) { + delete this.listeners[typeStr]; + this.typeCount_--; + } + return true; + } + return false; +}; + + +/** + * Removes the given listener object. + * @param {!goog.events.ListenableKey} listener The listener to remove. + * @return {boolean} Whether the listener is removed. + */ +goog.events.ListenerMap.prototype.removeByKey = function(listener) { + var type = listener.type; + if (!(type in this.listeners)) { + return false; + } + + var removed = goog.array.remove(this.listeners[type], listener); + if (removed) { + /** @type {!goog.events.Listener} */ (listener).markAsRemoved(); + if (this.listeners[type].length == 0) { + delete this.listeners[type]; + this.typeCount_--; + } + } + return removed; +}; + + +/** + * Removes all listeners from this map. If opt_type is provided, only + * listeners that match the given type are removed. + * @param {string|!goog.events.EventId=} opt_type Type of event to remove. + * @return {number} Number of listeners removed. + */ +goog.events.ListenerMap.prototype.removeAll = function(opt_type) { + var typeStr = opt_type && opt_type.toString(); + var count = 0; + for (var type in this.listeners) { + if (!typeStr || type == typeStr) { + var listenerArray = this.listeners[type]; + for (var i = 0; i < listenerArray.length; i++) { + ++count; + listenerArray[i].markAsRemoved(); + } + delete this.listeners[type]; + this.typeCount_--; + } + } + return count; +}; + + +/** + * Gets all listeners that match the given type and capture mode. The + * returned array is a copy (but the listener objects are not). + * @param {string|!goog.events.EventId} type The type of the listeners + * to retrieve. + * @param {boolean} capture The capture mode of the listeners to retrieve. + * @return {!Array<!goog.events.ListenableKey>} An array of matching + * listeners. + */ +goog.events.ListenerMap.prototype.getListeners = function(type, capture) { + var listenerArray = this.listeners[type.toString()]; + var rv = []; + if (listenerArray) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (listenerObj.capture == capture) { + rv.push(listenerObj); + } + } + } + return rv; +}; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId} type The type of the listener + * to retrieve. + * @param {!Function} listener The listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + */ +goog.events.ListenerMap.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + var listenerArray = this.listeners[type.toString()]; + var i = -1; + if (listenerArray) { + i = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, capture, opt_listenerScope); + } + return i > -1 ? listenerArray[i] : null; +}; + + +/** + * Whether there is a matching listener. If either the type or capture + * parameters are unspecified, the function will match on the + * remaining criteria. + * + * @param {string|!goog.events.EventId=} opt_type The type of the listener. + * @param {boolean=} opt_capture The capture mode of the listener. + * @return {boolean} Whether there is an active listener matching + * the requested type and/or capture phase. + */ +goog.events.ListenerMap.prototype.hasListener = function( + opt_type, opt_capture) { + var hasType = goog.isDef(opt_type); + var typeStr = hasType ? opt_type.toString() : ''; + var hasCapture = goog.isDef(opt_capture); + + return goog.object.some(this.listeners, function(listenerArray, type) { + for (var i = 0; i < listenerArray.length; ++i) { + if ((!hasType || listenerArray[i].type == typeStr) && + (!hasCapture || listenerArray[i].capture == opt_capture)) { + return true; + } + } + + return false; + }); +}; + + +/** + * Finds the index of a matching goog.events.Listener in the given + * listenerArray. + * @param {!Array<!goog.events.Listener>} listenerArray Array of listener. + * @param {!Function} listener The listener function. + * @param {boolean=} opt_useCapture The capture flag for the listener. + * @param {Object=} opt_listenerScope The listener scope. + * @return {number} The index of the matching listener within the + * listenerArray. + * @private + */ +goog.events.ListenerMap.findListenerIndex_ = function( + listenerArray, listener, opt_useCapture, opt_listenerScope) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (!listenerObj.removed && listenerObj.listener == listener && + listenerObj.capture == !!opt_useCapture && + listenerObj.handler == opt_listenerScope) { + return i; + } + } + return -1; +};
diff --git a/third_party/ink/closure/events/wheelevent.js b/third_party/ink/closure/events/wheelevent.js new file mode 100644 index 0000000..770724a --- /dev/null +++ b/third_party/ink/closure/events/wheelevent.js
@@ -0,0 +1,170 @@ +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview This class aims to smooth out inconsistencies between browser + * handling of wheel events by providing an event that is similar to that + * defined in the standard, but also easier to consume. + * + * It is based upon the WheelEvent, which allows for up to 3 dimensional + * scrolling events that come in units of either pixels, lines or pages. + * http://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#interface-WheelEvent + * + * The significant difference here is that it also provides reasonable pixel + * deltas for clients that do not want to treat line and page scrolling events + * specially. + * + * Clients of this code should be aware that some input devices only fire a few + * discrete events (such as a mouse wheel without acceleration) whereas some can + * generate a large number of events for a single interaction (such as a + * touchpad with acceleration). There is no signal in the events to reliably + * distinguish between these. + * + * @author joshuawilder@google.com (Joshua Wilder) + * @see ../demos/wheelhandler.html + */ + +goog.provide('goog.events.WheelEvent'); + +goog.require('goog.asserts'); +goog.require('goog.events.BrowserEvent'); + + + +/** + * A common class for wheel events. This is used with the WheelHandler. + * + * @param {Event} browserEvent Browser event object. + * @param {goog.events.WheelEvent.DeltaMode} deltaMode The delta mode units of + * the wheel event. + * @param {number} deltaX The number of delta units the user in the X axis. + * @param {number} deltaY The number of delta units the user in the Y axis. + * @param {number} deltaZ The number of delta units the user in the Z axis. + * @constructor + * @extends {goog.events.BrowserEvent} + * @final + */ +goog.events.WheelEvent = function( + browserEvent, deltaMode, deltaX, deltaY, deltaZ) { + goog.events.WheelEvent.base(this, 'constructor', browserEvent); + goog.asserts.assert(browserEvent, 'Expecting a non-null browserEvent'); + + /** @type {goog.events.WheelEvent.EventType} */ + this.type = goog.events.WheelEvent.EventType.WHEEL; + + /** + * An enum corresponding to the units of this event. + * @type {goog.events.WheelEvent.DeltaMode} + */ + this.deltaMode = deltaMode; + + /** + * The number of delta units in the X axis. + * @type {number} + */ + this.deltaX = deltaX; + + /** + * The number of delta units in the Y axis. + * @type {number} + */ + this.deltaY = deltaY; + + /** + * The number of delta units in the Z axis. + * @type {number} + */ + this.deltaZ = deltaZ; + + // Ratio between delta and pixel values. + var pixelRatio = 1; // Value for DeltaMode.PIXEL + switch (deltaMode) { + case goog.events.WheelEvent.DeltaMode.PAGE: + pixelRatio *= goog.events.WheelEvent.PIXELS_PER_PAGE_; + break; + case goog.events.WheelEvent.DeltaMode.LINE: + pixelRatio *= goog.events.WheelEvent.PIXELS_PER_LINE_; + break; + } + + /** + * The number of delta pixels in the X axis. Code that doesn't want to handle + * different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaX = this.deltaX * pixelRatio; + + /** + * The number of pixels in the Y axis. Code that doesn't want to + * handle different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaY = this.deltaY * pixelRatio; + + /** + * The number of pixels scrolled in the Z axis. Code that doesn't want to + * handle different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaZ = this.deltaZ * pixelRatio; +}; +goog.inherits(goog.events.WheelEvent, goog.events.BrowserEvent); + + +/** + * Enum type for the events fired by the wheel handler. + * @enum {string} + */ +goog.events.WheelEvent.EventType = { + /** The user has provided wheel-based input. */ + WHEEL: 'wheel' +}; + + +/** + * Units for the deltas in a WheelEvent. + * @enum {number} + */ +goog.events.WheelEvent.DeltaMode = { + /** The units are in pixels. From DOM_DELTA_PIXEL. */ + PIXEL: 0, + /** The units are in lines. From DOM_DELTA_LINE. */ + LINE: 1, + /** The units are in pages. From DOM_DELTA_PAGE. */ + PAGE: 2 +}; + + +/** + * A conversion number between line scroll units and pixel scroll units. The + * actual value per line can vary a lot between devices and font sizes. This + * number can not be perfect, but it should be reasonable for converting lines + * scroll events into pixels. + * @const {number} + * @private + */ +goog.events.WheelEvent.PIXELS_PER_LINE_ = 15; + + +/** + * A conversion number between page scroll units and pixel scroll units. The + * actual value per page can vary a lot as many different devices have different + * screen sizes, and the window might not be taking up the full screen. This + * number can not be perfect, but it should be reasonable for converting page + * scroll events into pixels. + * @const {number} + * @private + */ +goog.events.WheelEvent.PIXELS_PER_PAGE_ = + 30 * goog.events.WheelEvent.PIXELS_PER_LINE_;
diff --git a/third_party/ink/closure/format/format.js b/third_party/ink/closure/format/format.js new file mode 100644 index 0000000..b938905 --- /dev/null +++ b/third_party/ink/closure/format/format.js
@@ -0,0 +1,503 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides utility functions for formatting strings, numbers etc. + * + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.format'); + +goog.require('goog.i18n.GraphemeBreak'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Formats a number of bytes in human readable form. + * 54, 450K, 1.3M, 5G etc. + * @param {number} bytes The number of bytes to show. + * @param {number=} opt_decimals The number of decimals to use. Defaults to 2. + * @return {string} The human readable form of the byte size. + */ +goog.format.fileSize = function(bytes, opt_decimals) { + return goog.format.numBytesToString(bytes, opt_decimals, false); +}; + + +/** + * Checks whether string value containing scaling units (K, M, G, T, P, m, + * u, n) can be converted to a number. + * + * Where there is a decimal, there must be a digit to the left of the + * decimal point. + * + * Negative numbers are valid. + * + * Examples: + * 0, 1, 1.0, 10.4K, 2.3M, -0.3P, 1.2m + * + * @param {string} val String value to check. + * @return {boolean} True if string could be converted to a numeric value. + */ +goog.format.isConvertableScaledNumber = function(val) { + return goog.format.SCALED_NUMERIC_RE_.test(val); +}; + + +/** + * Converts a string to numeric value, taking into account the units. + * If string ends in 'B', use binary conversion. + * @param {string} stringValue String to be converted to numeric value. + * @return {number} Numeric value for string. + */ +goog.format.stringToNumericValue = function(stringValue) { + if (goog.string.endsWith(stringValue, 'B')) { + return goog.format.stringToNumericValue_( + stringValue, goog.format.NUMERIC_SCALES_BINARY_); + } + return goog.format.stringToNumericValue_( + stringValue, goog.format.NUMERIC_SCALES_SI_); +}; + + +/** + * Converts a string to number of bytes, taking into account the units. + * Binary conversion. + * @param {string} stringValue String to be converted to numeric value. + * @return {number} Numeric value for string. + */ +goog.format.stringToNumBytes = function(stringValue) { + return goog.format.stringToNumericValue_( + stringValue, goog.format.NUMERIC_SCALES_BINARY_); +}; + + +/** + * Converts a numeric value to string representation. SI conversion. + * @param {number} val Value to be converted. + * @param {number=} opt_decimals The number of decimals to use. Defaults to 2. + * @return {string} String representation of number. + */ +goog.format.numericValueToString = function(val, opt_decimals) { + return goog.format.numericValueToString_( + val, goog.format.NUMERIC_SCALES_SI_, opt_decimals); +}; + + +/** + * Converts number of bytes to string representation. Binary conversion. + * Default is to return the additional 'B' suffix only for scales greater than + * 1K, e.g. '10.5KB' to minimize confusion with counts that are scaled by powers + * of 1000. Otherwise, suffix is empty string. + * @param {number} val Value to be converted. + * @param {number=} opt_decimals The number of decimals to use. Defaults to 2. + * @param {boolean=} opt_suffix If true, include trailing 'B' in returned + * string. Default is true. + * @param {boolean=} opt_useSeparator If true, number and scale will be + * separated by a no break space. Default is false. + * @return {string} String representation of number of bytes. + */ +goog.format.numBytesToString = function( + val, opt_decimals, opt_suffix, opt_useSeparator) { + var suffix = ''; + if (!goog.isDef(opt_suffix) || opt_suffix) { + suffix = 'B'; + } + return goog.format.numericValueToString_( + val, goog.format.NUMERIC_SCALES_BINARY_, opt_decimals, suffix, + opt_useSeparator); +}; + + +/** + * Converts a string to numeric value, taking into account the units. + * @param {string} stringValue String to be converted to numeric value. + * @param {Object} conversion Dictionary of conversion scales. + * @return {number} Numeric value for string. If it cannot be converted, + * returns NaN. + * @private + */ +goog.format.stringToNumericValue_ = function(stringValue, conversion) { + var match = stringValue.match(goog.format.SCALED_NUMERIC_RE_); + if (!match) { + return NaN; + } + var val = Number(match[1]) * conversion[match[2]]; + return val; +}; + + +/** + * Converts a numeric value to string, using specified conversion + * scales. + * @param {number} val Value to be converted. + * @param {Object} conversion Dictionary of scaling factors. + * @param {number=} opt_decimals The number of decimals to use. Default is 2. + * @param {string=} opt_suffix Optional suffix to append. + * @param {boolean=} opt_useSeparator If true, number and scale will be + * separated by a space. Default is false. + * @return {string} The human readable form of the byte size. + * @private + */ +goog.format.numericValueToString_ = function( + val, conversion, opt_decimals, opt_suffix, opt_useSeparator) { + var prefixes = goog.format.NUMERIC_SCALE_PREFIXES_; + var orig_val = val; + var symbol = ''; + var separator = ''; + var scale = 1; + if (val < 0) { + val = -val; + } + for (var i = 0; i < prefixes.length; i++) { + var unit = prefixes[i]; + scale = conversion[unit]; + if (val >= scale || (scale <= 1 && val > 0.1 * scale)) { + // Treat values less than 1 differently, allowing 0.5 to be "0.5" rather + // than "500m" + symbol = unit; + break; + } + } + if (!symbol) { + scale = 1; + } else { + if (opt_suffix) { + symbol += opt_suffix; + } + if (opt_useSeparator) { + separator = ' '; + } + } + var ex = Math.pow(10, goog.isDef(opt_decimals) ? opt_decimals : 2); + return Math.round(orig_val / scale * ex) / ex + separator + symbol; +}; + + +/** + * Regular expression for detecting scaling units, such as K, M, G, etc. for + * converting a string representation to a numeric value. + * + * Also allow 'k' to be aliased to 'K'. These could be used for SI (powers + * of 1000) or Binary (powers of 1024) conversions. + * + * Also allow final 'B' to be interpreted as byte-count, implicitly triggering + * binary conversion (e.g., '10.2MB'). + * + * @type {RegExp} + * @private + */ +goog.format.SCALED_NUMERIC_RE_ = + /^([-]?\d+\.?\d*)([K,M,G,T,P,E,Z,Y,k,m,u,n]?)[B]?$/; + + +/** + * Ordered list of scaling prefixes in decreasing order. + * @private {Array<string>} + */ +goog.format.NUMERIC_SCALE_PREFIXES_ = + ['Y', 'Z', 'E', 'P', 'T', 'G', 'M', 'K', '', 'm', 'u', 'n']; + + +/** + * Scaling factors for conversion of numeric value to string. SI conversion. + * @type {Object} + * @private + */ +goog.format.NUMERIC_SCALES_SI_ = { + '': 1, + 'n': 1e-9, + 'u': 1e-6, + 'm': 1e-3, + 'k': 1e3, + 'K': 1e3, + 'M': 1e6, + 'G': 1e9, + 'T': 1e12, + 'P': 1e15, + 'E': 1e18, + 'Z': 1e21, + 'Y': 1e24 +}; + + +/** + * Scaling factors for conversion of numeric value to string. Binary + * conversion. + * @type {Object} + * @private + */ +goog.format.NUMERIC_SCALES_BINARY_ = { + '': 1, + 'n': Math.pow(1024, -3), + 'u': Math.pow(1024, -2), + 'm': 1.0 / 1024, + 'k': 1024, + 'K': 1024, + 'M': Math.pow(1024, 2), + 'G': Math.pow(1024, 3), + 'T': Math.pow(1024, 4), + 'P': Math.pow(1024, 5), + 'E': Math.pow(1024, 6), + 'Z': Math.pow(1024, 7), + 'Y': Math.pow(1024, 8) +}; + + +/** + * First Unicode code point that has the Mark property. + * @type {number} + * @private + */ +goog.format.FIRST_GRAPHEME_EXTEND_ = 0x300; + + +/** + * Returns true if and only if given character should be treated as a breaking + * space. All ASCII control characters, the main Unicode range of spacing + * characters (U+2000 to U+200B inclusive except for U+2007), and several other + * Unicode space characters are treated as breaking spaces. + * @param {number} charCode The character code under consideration. + * @return {boolean} True if the character is a breaking space. + * @private + */ +goog.format.isTreatedAsBreakingSpace_ = function(charCode) { + return (charCode <= goog.format.WbrToken_.SPACE) || + (charCode >= 0x1000 && + ((charCode >= 0x2000 && charCode <= 0x2006) || + (charCode >= 0x2008 && charCode <= 0x200B) || charCode == 0x1680 || + charCode == 0x180E || charCode == 0x2028 || charCode == 0x2029 || + charCode == 0x205f || charCode == 0x3000)); +}; + + +/** + * Returns true if and only if given character is an invisible formatting + * character. + * @param {number} charCode The character code under consideration. + * @return {boolean} True if the character is an invisible formatting character. + * @private + */ +goog.format.isInvisibleFormattingCharacter_ = function(charCode) { + // See: http://unicode.org/charts/PDF/U2000.pdf + return (charCode >= 0x200C && charCode <= 0x200F) || + (charCode >= 0x202A && charCode <= 0x202E); +}; + + +/** + * Inserts word breaks into an HTML string at a given interval. The counter is + * reset if a space or a character which behaves like a space is encountered, + * but it isn't incremented if an invisible formatting character is encountered. + * WBRs aren't inserted into HTML tags or entities. Entities count towards the + * character count, HTML tags do not. + * + * With common strings aliased, objects allocations are constant based on the + * length of the string: N + 3. This guarantee does not hold if the string + * contains an element >= U+0300 and hasGraphemeBreak is non-trivial. + * + * @param {string} str HTML to insert word breaks into. + * @param {function(number, number, boolean): boolean} hasGraphemeBreak A + * function determining if there is a grapheme break between two characters, + * in the same signature as goog.i18n.GraphemeBreak.hasGraphemeBreak. + * @param {number=} opt_maxlen Maximum length after which to ensure + * there is a break. Default is 10 characters. + * @return {string} The string including word breaks. + * @private + */ +goog.format.insertWordBreaksGeneric_ = function( + str, hasGraphemeBreak, opt_maxlen) { + var maxlen = opt_maxlen || 10; + if (maxlen > str.length) return str; + + var rv = []; + var n = 0; // The length of the current token + + // This will contain the ampersand or less-than character if one of the + // two has been seen; otherwise, the value is zero. + var nestingCharCode = 0; + + // First character position from input string that has not been outputted. + var lastDumpPosition = 0; + + var charCode = 0; + for (var i = 0; i < str.length; i++) { + // Using charCodeAt versus charAt avoids allocating new string objects. + var lastCharCode = charCode; + charCode = str.charCodeAt(i); + + // Don't add a WBR before characters that might be grapheme extending. + var isPotentiallyGraphemeExtending = + charCode >= goog.format.FIRST_GRAPHEME_EXTEND_ && + !hasGraphemeBreak(lastCharCode, charCode, true); + + // Don't add a WBR at the end of a word. For the purposes of determining + // work breaks, all ASCII control characters and some commonly encountered + // Unicode spacing characters are treated as breaking spaces. + if (n >= maxlen && !goog.format.isTreatedAsBreakingSpace_(charCode) && + !isPotentiallyGraphemeExtending) { + // Flush everything seen so far, and append a word break. + rv.push(str.substring(lastDumpPosition, i), goog.format.WORD_BREAK_HTML); + lastDumpPosition = i; + n = 0; + } + + if (!nestingCharCode) { + // Not currently within an HTML tag or entity + + if (charCode == goog.format.WbrToken_.LT || + charCode == goog.format.WbrToken_.AMP) { + // Entering an HTML Entity '&' or open tag '<' + nestingCharCode = charCode; + } else if (goog.format.isTreatedAsBreakingSpace_(charCode)) { + // A space or control character -- reset the token length + n = 0; + } else if (!goog.format.isInvisibleFormattingCharacter_(charCode)) { + // A normal flow character - increment. For grapheme extending + // characters, this is not *technically* a new character. However, + // since the grapheme break detector might be overly conservative, + // we have to continue incrementing, or else we won't even be able + // to add breaks when we get to things like punctuation. For the + // case where we have a full grapheme break detector, it is okay if + // we occasionally break slightly early. + n++; + } + } else if ( + charCode == goog.format.WbrToken_.GT && + nestingCharCode == goog.format.WbrToken_.LT) { + // Leaving an HTML tag, treat the tag as zero-length + nestingCharCode = 0; + } else if ( + charCode == goog.format.WbrToken_.SEMI_COLON && + nestingCharCode == goog.format.WbrToken_.AMP) { + // Leaving an HTML entity, treat it as length one + nestingCharCode = 0; + n++; + } + } + + // Take care of anything we haven't flushed so far. + rv.push(str.substr(lastDumpPosition)); + + return rv.join(''); +}; + + +/** + * Inserts word breaks into an HTML string at a given interval. + * + * This method is as aggressive as possible, using a full table of Unicode + * characters where it is legal to insert word breaks; however, this table + * comes at a 2.5k pre-gzip (~1k post-gzip) size cost. Consider using + * insertWordBreaksBasic to minimize the size impact. + * + * @param {string} str HTML to insert word breaks into. + * @param {number=} opt_maxlen Maximum length after which to ensure there is a + * break. Default is 10 characters. + * @return {string} The string including word breaks. + * @deprecated Prefer wrapping with CSS word-wrap: break-word. + */ +goog.format.insertWordBreaks = function(str, opt_maxlen) { + return goog.format.insertWordBreaksGeneric_( + str, goog.i18n.GraphemeBreak.hasGraphemeBreak, opt_maxlen); +}; + + +/** + * Determines conservatively if a character has a Grapheme break. + * + * Conforms to a similar signature as goog.i18n.GraphemeBreak, but is overly + * conservative, returning true only for characters in common scripts that + * are simple to account for. + * + * @param {number} lastCharCode The previous character code. Ignored. + * @param {number} charCode The character code under consideration. It must be + * at least \u0300 as a precondition -- this case is covered by + * insertWordBreaksGeneric_. + * @param {boolean=} opt_extended Ignored, to conform with the interface. + * @return {boolean} Whether it is one of the recognized subsets of characters + * with a grapheme break. + * @private + */ +goog.format.conservativelyHasGraphemeBreak_ = function( + lastCharCode, charCode, opt_extended) { + // Return false for everything except the most common Cyrillic characters. + // Don't worry about Latin characters, because insertWordBreaksGeneric_ + // itself already handles those. + // TODO(gboyer): Also account for Greek, Armenian, and Georgian if it is + // simple to do so. + return charCode >= 0x400 && charCode < 0x523; +}; + + +// TODO(gboyer): Consider using a compile-time flag to switch implementations +// rather than relying on the developers to toggle implementations. +/** + * Inserts word breaks into an HTML string at a given interval. + * + * This method is less aggressive than insertWordBreaks, only inserting + * breaks next to punctuation and between Latin or Cyrillic characters. + * However, this is good enough for the common case of URLs. It also + * works for all Latin and Cyrillic languages, plus CJK has no need for word + * breaks. When this method is used, goog.i18n.GraphemeBreak may be dead + * code eliminated. + * + * @param {string} str HTML to insert word breaks into. + * @param {number=} opt_maxlen Maximum length after which to ensure there is a + * break. Default is 10 characters. + * @return {string} The string including word breaks. + * @deprecated Prefer wrapping with CSS word-wrap: break-word. + */ +goog.format.insertWordBreaksBasic = function(str, opt_maxlen) { + return goog.format.insertWordBreaksGeneric_( + str, goog.format.conservativelyHasGraphemeBreak_, opt_maxlen); +}; + + +/** + * True iff the current userAgent is IE8 or above. + * @type {boolean} + * @private + */ +goog.format.IS_IE8_OR_ABOVE_ = + goog.userAgent.IE && goog.userAgent.isVersionOrHigher(8); + + +/** + * Constant for the WBR replacement used by insertWordBreaks. Safari requires + * <wbr></wbr>, Opera needs the ­ entity, though this will give a visible + * hyphen at breaks. IE8 uses a zero width space. + * Other browsers just use <wbr>. + * @type {string} + */ +goog.format.WORD_BREAK_HTML = + goog.userAgent.WEBKIT ? '<wbr></wbr>' : goog.userAgent.OPERA ? + '­' : + goog.format.IS_IE8_OR_ABOVE_ ? '​' : '<wbr>'; + + +/** + * Tokens used within insertWordBreaks. + * @private + * @enum {number} + */ +goog.format.WbrToken_ = { + LT: 60, // '<'.charCodeAt(0) + GT: 62, // '>'.charCodeAt(0) + AMP: 38, // '&'.charCodeAt(0) + SEMI_COLON: 59, // ';'.charCodeAt(0) + SPACE: 32 // ' '.charCodeAt(0) +};
diff --git a/third_party/ink/closure/fs/url.js b/third_party/ink/closure/fs/url.js new file mode 100644 index 0000000..1204908 --- /dev/null +++ b/third_party/ink/closure/fs/url.js
@@ -0,0 +1,106 @@ +// Copyright 2015 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Wrapper for URL and its createObjectUrl and revokeObjectUrl + * methods that are part of the HTML5 File API. + */ + +goog.provide('goog.fs.url'); + + +/** + * Creates a blob URL for a blob object. + * Throws an error if the browser does not support Object Urls. + * + * @param {!Blob} blob The object for which to create the URL. + * @return {string} The URL for the object. + */ +goog.fs.url.createObjectUrl = function(blob) { + return goog.fs.url.getUrlObject_().createObjectURL(blob); +}; + + +/** + * Revokes a URL created by {@link goog.fs.url.createObjectUrl}. + * Throws an error if the browser does not support Object Urls. + * + * @param {string} url The URL to revoke. + */ +goog.fs.url.revokeObjectUrl = function(url) { + goog.fs.url.getUrlObject_().revokeObjectURL(url); +}; + + +/** + * @typedef {{createObjectURL: (function(!Blob): string), + * revokeObjectURL: function(string): void}} + */ +goog.fs.url.UrlObject_; + + +/** + * Get the object that has the createObjectURL and revokeObjectURL functions for + * this browser. + * + * @return {goog.fs.url.UrlObject_} The object for this browser. + * @private + */ +goog.fs.url.getUrlObject_ = function() { + var urlObject = goog.fs.url.findUrlObject_(); + if (urlObject != null) { + return urlObject; + } else { + throw new Error('This browser doesn\'t seem to support blob URLs'); + } +}; + + +/** + * Finds the object that has the createObjectURL and revokeObjectURL functions + * for this browser. + * + * @return {?goog.fs.url.UrlObject_} The object for this browser or null if the + * browser does not support Object Urls. + * @private + */ +goog.fs.url.findUrlObject_ = function() { + // This is what the spec says to do + // http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL + if (goog.isDef(goog.global.URL) && + goog.isDef(goog.global.URL.createObjectURL)) { + return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL); + // This is what Chrome does (as of 10.0.648.6 dev) + } else if ( + goog.isDef(goog.global.webkitURL) && + goog.isDef(goog.global.webkitURL.createObjectURL)) { + return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL); + // This is what the spec used to say to do + } else if (goog.isDef(goog.global.createObjectURL)) { + return /** @type {goog.fs.url.UrlObject_} */ (goog.global); + } else { + return null; + } +}; + + +/** + * Checks whether this browser supports Object Urls. If not, calls to + * createObjectUrl and revokeObjectUrl will result in an error. + * + * @return {boolean} True if this browser supports Object Urls. + */ +goog.fs.url.browserSupportsObjectUrls = function() { + return goog.fs.url.findUrlObject_() != null; +};
diff --git a/third_party/ink/closure/functions/functions.js b/third_party/ink/closure/functions/functions.js new file mode 100644 index 0000000..f735888 --- /dev/null +++ b/third_party/ink/closure/functions/functions.js
@@ -0,0 +1,485 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for creating functions. Loosely inspired by the + * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8. + * + * @author nicksantos@google.com (Nick Santos) + */ + + +goog.provide('goog.functions'); + + +/** + * Creates a function that always returns the same value. + * @param {T} retValue The value to return. + * @return {function():T} The new function. + * @template T + */ +goog.functions.constant = function(retValue) { + return function() { return retValue; }; +}; + + +/** + * Always returns false. + * @type {function(...): boolean} + */ +goog.functions.FALSE = goog.functions.constant(false); + + +/** + * Always returns true. + * @type {function(...): boolean} + */ +goog.functions.TRUE = goog.functions.constant(true); + + +/** + * Always returns NULL. + * @type {function(...): null} + */ +goog.functions.NULL = goog.functions.constant(null); + + +/** + * A simple function that returns the first argument of whatever is passed + * into it. + * @param {T=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {T} The first argument passed in, or undefined if nothing was passed. + * @template T + */ +goog.functions.identity = function(opt_returnValue, var_args) { + return opt_returnValue; +}; + + +/** + * Creates a function that always throws an error with the given message. + * @param {string} message The error message. + * @return {!Function} The error-throwing function. + */ +goog.functions.error = function(message) { + return function() { + throw new Error(message); + }; +}; + + +/** + * Creates a function that throws the given object. + * @param {*} err An object to be thrown. + * @return {!Function} The error-throwing function. + */ +goog.functions.fail = function(err) { + return function() { throw err; }; +}; + + +/** + * Given a function, create a function that keeps opt_numArgs arguments and + * silently discards all additional arguments. + * @param {Function} f The original function. + * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0. + * @return {!Function} A version of f that only keeps the first opt_numArgs + * arguments. + */ +goog.functions.lock = function(f, opt_numArgs) { + opt_numArgs = opt_numArgs || 0; + return function() { + return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs)); + }; +}; + + +/** + * Creates a function that returns its nth argument. + * @param {number} n The position of the return argument. + * @return {!Function} A new function. + */ +goog.functions.nth = function(n) { + return function() { return arguments[n]; }; +}; + + +/** + * Like goog.partial(), except that arguments are added after arguments to the + * returned function. + * + * Usage: + * function f(arg1, arg2, arg3, arg4) { ... } + * var g = goog.functions.partialRight(f, arg3, arg4); + * g(arg1, arg2); + * + * @param {!Function} fn A function to partially apply. + * @param {...*} var_args Additional arguments that are partially applied to fn + * at the end. + * @return {!Function} A partially-applied form of the function goog.partial() + * was invoked as a method of. + */ +goog.functions.partialRight = function(fn, var_args) { + var rightArgs = Array.prototype.slice.call(arguments, 1); + return function() { + var newArgs = Array.prototype.slice.call(arguments); + newArgs.push.apply(newArgs, rightArgs); + return fn.apply(this, newArgs); + }; +}; + + +/** + * Given a function, create a new function that swallows its return value + * and replaces it with a new one. + * @param {Function} f A function. + * @param {T} retValue A new return value. + * @return {function(...?):T} A new function. + * @template T + */ +goog.functions.withReturnValue = function(f, retValue) { + return goog.functions.sequence(f, goog.functions.constant(retValue)); +}; + + +/** + * Creates a function that returns whether its argument equals the given value. + * + * Example: + * var key = goog.object.findKey(obj, goog.functions.equalTo('needle')); + * + * @param {*} value The value to compare to. + * @param {boolean=} opt_useLooseComparison Whether to use a loose (==) + * comparison rather than a strict (===) one. Defaults to false. + * @return {function(*):boolean} The new function. + */ +goog.functions.equalTo = function(value, opt_useLooseComparison) { + return function(other) { + return opt_useLooseComparison ? (value == other) : (value === other); + }; +}; + + +/** + * Creates the composition of the functions passed in. + * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)). + * @param {function(...?):T} fn The final function. + * @param {...Function} var_args A list of functions. + * @return {function(...?):T} The composition of all inputs. + * @template T + */ +goog.functions.compose = function(fn, var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + if (length) { + result = functions[length - 1].apply(this, arguments); + } + + for (var i = length - 2; i >= 0; i--) { + result = functions[i].call(this, result); + } + return result; + }; +}; + + +/** + * Creates a function that calls the functions passed in in sequence, and + * returns the value of the last function. For example, + * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x). + * @param {...Function} var_args A list of functions. + * @return {!Function} A function that calls all inputs in sequence. + */ +goog.functions.sequence = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * Creates a function that returns true if each of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns false. + * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ANDs its component + * functions. + */ +goog.functions.and = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (!functions[i].apply(this, arguments)) { + return false; + } + } + return true; + }; +}; + + +/** + * Creates a function that returns true if any of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns true. + * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ORs its component + * functions. + */ +goog.functions.or = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (functions[i].apply(this, arguments)) { + return true; + } + } + return false; + }; +}; + + +/** + * Creates a function that returns the Boolean opposite of a provided function. + * For example, (goog.functions.not(f))(x) is equivalent to !f(x). + * @param {!Function} f The original function. + * @return {function(...?):boolean} A function that delegates to f and returns + * opposite. + */ +goog.functions.not = function(f) { + return function() { return !f.apply(this, arguments); }; +}; + + +/** + * Generic factory function to construct an object given the constructor + * and the arguments. Intended to be bound to create object factories. + * + * Example: + * + * var factory = goog.partial(goog.functions.create, Class); + * + * @param {function(new:T, ...)} constructor The constructor for the Object. + * @param {...*} var_args The arguments to be passed to the constructor. + * @return {T} A new instance of the class given in {@code constructor}. + * @template T + */ +goog.functions.create = function(constructor, var_args) { + /** + * @constructor + * @final + */ + var temp = function() {}; + temp.prototype = constructor.prototype; + + // obj will have constructor's prototype in its chain and + // 'obj instanceof constructor' will be true. + var obj = new temp(); + + // obj is initialized by constructor. + // arguments is only array-like so lacks shift(), but can be used with + // the Array prototype function. + constructor.apply(obj, Array.prototype.slice.call(arguments, 1)); + return obj; +}; + + +/** + * @define {boolean} Whether the return value cache should be used. + * This should only be used to disable caches when testing. + */ +goog.define('goog.functions.CACHE_RETURN_VALUE', true); + + +/** + * Gives a wrapper function that caches the return value of a parameterless + * function when first called. + * + * When called for the first time, the given function is called and its + * return value is cached (thus this is only appropriate for idempotent + * functions). Subsequent calls will return the cached return value. This + * allows the evaluation of expensive functions to be delayed until first used. + * + * To cache the return values of functions with parameters, see goog.memoize. + * + * @param {function():T} fn A function to lazily evaluate. + * @return {function():T} A wrapped version the function. + * @template T + */ +goog.functions.cacheReturnValue = function(fn) { + var called = false; + var value; + + return function() { + if (!goog.functions.CACHE_RETURN_VALUE) { + return fn(); + } + + if (!called) { + value = fn(); + called = true; + } + + return value; + }; +}; + + +/** + * Wraps a function to allow it to be called, at most, once. All + * additional calls are no-ops. + * + * This is particularly useful for initialization functions + * that should be called, at most, once. + * + * @param {function():*} f Function to call. + * @return {function():undefined} Wrapped function. + */ +goog.functions.once = function(f) { + // Keep a reference to the function that we null out when we're done with + // it -- that way, the function can be GC'd when we're done with it. + var inner = f; + return function() { + if (inner) { + var tmp = inner; + inner = null; + tmp(); + } + }; +}; + + +/** + * Wraps a function to allow it to be called, at most, once per interval + * (specified in milliseconds). If the wrapper function is called N times within + * that interval, only the Nth call will go through. + * + * This is particularly useful for batching up repeated actions where the + * last action should win. This can be used, for example, for refreshing an + * autocomplete pop-up every so often rather than updating with every keystroke, + * since the final text typed by the user is the one that should produce the + * final autocomplete results. For more stateful debouncing with support for + * pausing, resuming, and canceling debounced actions, use {@code + * goog.async.Debouncer}. + * + * @param {function(this:SCOPE, ...?)} f Function to call. + * @param {number} interval Interval over which to debounce. The function will + * only be called after the full interval has elapsed since the last call. + * @param {SCOPE=} opt_scope Object in whose scope to call the function. + * @return {function(...?): undefined} Wrapped function. + * @template SCOPE + */ +goog.functions.debounce = function(f, interval, opt_scope) { + var timeout = 0; + return /** @type {function(...?)} */ (function(var_args) { + goog.global.clearTimeout(timeout); + var args = arguments; + timeout = goog.global.setTimeout(function() { + f.apply(opt_scope, args); + }, interval); + }); +}; + + +/** + * Wraps a function to allow it to be called, at most, once per interval + * (specified in milliseconds). If the wrapper function is called N times in + * that interval, both the 1st and the Nth calls will go through. + * + * This is particularly useful for limiting repeated user requests where the + * the last action should win, but you also don't want to wait until the end of + * the interval before sending a request out, as it leads to a perception of + * slowness for the user. + * + * @param {function(this:SCOPE, ...?)} f Function to call. + * @param {number} interval Interval over which to throttle. The function can + * only be called once per interval. + * @param {SCOPE=} opt_scope Object in whose scope to call the function. + * @return {function(...?): undefined} Wrapped function. + * @template SCOPE + */ +goog.functions.throttle = function(f, interval, opt_scope) { + var timeout = 0; + var shouldFire = false; + var args = []; + + var handleTimeout = function() { + timeout = 0; + if (shouldFire) { + shouldFire = false; + fire(); + } + }; + + var fire = function() { + timeout = goog.global.setTimeout(handleTimeout, interval); + f.apply(opt_scope, args); + }; + + return /** @type {function(...?)} */ (function(var_args) { + args = arguments; + if (!timeout) { + fire(); + } else { + shouldFire = true; + } + }); +}; + + +/** + * Wraps a function to allow it to be called, at most, once per interval + * (specified in milliseconds). If the wrapper function is called N times within + * that interval, only the 1st call will go through. + * + * This is particularly useful for limiting repeated user requests where the + * first request is guaranteed to have all the data required to perform the + * final action, so there's no need to wait until the end of the interval before + * sending the request out. + * + * @param {function(this:SCOPE, ...?)} f Function to call. + * @param {number} interval Interval over which to rate-limit. The function will + * only be called once per interval, and ignored for the remainer of the + * interval. + * @param {SCOPE=} opt_scope Object in whose scope to call the function. + * @return {function(...?): undefined} Wrapped function. + * @template SCOPE + */ +goog.functions.rateLimit = function(f, interval, opt_scope) { + var timeout = 0; + + var handleTimeout = function() { + timeout = 0; + }; + + return /** @type {function(...?)} */ (function(var_args) { + if (!timeout) { + timeout = goog.global.setTimeout(handleTimeout, interval); + f.apply(opt_scope, arguments); + } + }); +};
diff --git a/third_party/ink/closure/html/legacyconversions.js b/third_party/ink/closure/html/legacyconversions.js new file mode 100644 index 0000000..0148977 --- /dev/null +++ b/third_party/ink/closure/html/legacyconversions.js
@@ -0,0 +1,204 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Transitional utilities to unsafely trust random strings as + * goog.html types. Intended for temporary use when upgrading a library that + * used to accept plain strings to use safe types, but where it's not + * practical to transitively update callers. + * + * IMPORTANT: No new code should use the conversion functions in this file, + * they are intended for refactoring old code to use goog.html types. New code + * should construct goog.html types via their APIs, template systems or + * sanitizers. If that’s not possible it should use + * goog.html.uncheckedconversions and undergo security review. + + * MOE:begin_intracomment_strip + * At Google goog.html.legacyconversions are restricted via both BUILD + * visibility and Conformance rules. The goal is to allow us to progressively + * get rid of using strings to represent HTML-related data which is passed to + * DOM APIs that execute script (like innerHTML or Anchor.href), while avoiding + * regressions. Please carefully read the documentation below before using + * these functions. If you have questions contact ise-hardening@ and we’ll + * gladly help. + * MOE:end_intracomment_strip + * + * The semantics of the conversions in goog.html.legacyconversions are very + * different from the ones provided by goog.html.uncheckedconversions. The + * latter are for use in code where it has been established through manual + * security review that the value produced by a piece of code will always + * satisfy the SafeHtml contract (e.g., the output of a secure HTML sanitizer). + * In uses of goog.html.legacyconversions, this guarantee is not given -- the + * value in question originates in unreviewed legacy code and there is no + * guarantee that it satisfies the SafeHtml contract. + * + * There are only three valid uses of legacyconversions: + * + * 1. Introducing a goog.html version of a function which currently consumes + * string and passes that string to a DOM API which can execute script - and + * hence cause XSS - like innerHTML. For example, Dialog might expose a + * setContent method which takes a string and sets the innerHTML property of + * an element with it. In this case a setSafeHtmlContent function could be + * added, consuming goog.html.SafeHtml instead of string, and using + * goog.dom.safe.setInnerHtml instead of directly setting innerHTML. + * setContent could then internally use legacyconversions to create a SafeHtml + * from string and pass the SafeHtml to setSafeHtmlContent. In this scenario + * remember to document the use of legacyconversions in the modified setContent + * and consider deprecating it as well. + * + * 2. Automated refactoring of application code which handles HTML as string + * but needs to call a function which only takes goog.html types. For example, + * in the Dialog scenario from (1) an alternative option would be to refactor + * setContent to accept goog.html.SafeHtml instead of string and then refactor + * all current callers to use legacyconversions to pass SafeHtml. This is + * generally preferable to (1) because it keeps the library clean of + * legacyconversions, and makes code sites in application code that are + * potentially vulnerable to XSS more apparent. + * + * 3. Old code which needs to call APIs which consume goog.html types and for + * which it is prohibitively expensive to refactor to use goog.html types. + * Generally, this is code where safety from XSS is either hopeless or + * unimportant. + * + * @visibility {//javascript/closure/html:approved_for_legacy_conversion} + * @visibility {//javascript/closure/bin/sizetests:__pkg__} + */ + + +goog.provide('goog.html.legacyconversions'); + +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); + + +/** + * Performs an "unchecked conversion" from string to SafeHtml for legacy API + * purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} html A string to be converted to SafeHtml. + * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml + * object. + */ +goog.html.legacyconversions.safeHtmlFromString = function(html) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + html, null /* dir */); +}; + + +/** + * Performs an "unchecked conversion" from string to SafeScript for legacy API + * purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} script A string to be converted to SafeScript. + * @return {!goog.html.SafeScript} The value of script, wrapped in a SafeScript + * object. + */ +goog.html.legacyconversions.safeScriptFromString = function(script) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( + script); +}; + + +/** + * Performs an "unchecked conversion" from string to SafeStyle for legacy API + * purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} style A string to be converted to SafeStyle. + * @return {!goog.html.SafeStyle} The value of style, wrapped in a SafeStyle + * object. + */ +goog.html.legacyconversions.safeStyleFromString = function(style) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); +}; + + +/** + * Performs an "unchecked conversion" from string to SafeStyleSheet for legacy + * API purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} styleSheet A string to be converted to SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} The value of style sheet, wrapped in + * a SafeStyleSheet object. + */ +goog.html.legacyconversions.safeStyleSheetFromString = function(styleSheet) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); +}; + + +/** + * Performs an "unchecked conversion" from string to SafeUrl for legacy API + * purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} url A string to be converted to SafeUrl. + * @return {!goog.html.SafeUrl} The value of url, wrapped in a SafeUrl + * object. + */ +goog.html.legacyconversions.safeUrlFromString = function(url) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; + + +/** + * Performs an "unchecked conversion" from string to TrustedResourceUrl for + * legacy API purposes. + * + * Please read fileoverview documentation before using. + * + * @param {string} url A string to be converted to TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} The value of url, wrapped in a + * TrustedResourceUrl object. + */ +goog.html.legacyconversions.trustedResourceUrlFromString = function(url) { + goog.html.legacyconversions.reportCallback_(); + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); +}; + +/** + * @private {function(): undefined} + */ +goog.html.legacyconversions.reportCallback_ = goog.nullFunction; + + +/** + * Sets a function that will be called every time a legacy conversion is + * performed. The function is called with no parameters but it can use + * goog.debug.getStacktrace to get a stacktrace. + * + * @param {function(): undefined} callback Error callback as defined above. + */ +goog.html.legacyconversions.setReportCallback = function(callback) { + goog.html.legacyconversions.reportCallback_ = callback; +};
diff --git a/third_party/ink/closure/html/safehtml.js b/third_party/ink/closure/html/safehtml.js new file mode 100644 index 0000000..088e014 --- /dev/null +++ b/third_party/ink/closure/html/safehtml.js
@@ -0,0 +1,994 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview The SafeHtml type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.SafeHtml'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.tags'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A string that is safe to use in HTML context in DOM APIs and HTML documents. + * + * A SafeHtml is a string-like object that carries the security type contract + * that its value as a string will not cause untrusted script execution when + * evaluated as HTML in a browser. + * + * Values of this type are guaranteed to be safe to use in HTML contexts, + * such as, assignment to the innerHTML DOM property, or interpolation into + * a HTML template in HTML PC_DATA context, in the sense that the use will not + * result in a Cross-Site-Scripting vulnerability. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}), + * etc and not by invoking its constructor. The constructor intentionally + * takes no parameters and the type is immutable; hence only a default instance + * corresponding to the empty string can be obtained via constructor invocation. + * + * @see goog.html.SafeHtml#create + * @see goog.html.SafeHtml#htmlEscape + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} + */ +goog.html.SafeHtml = function() { + /** + * The contained value of this SafeHtml. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeHtml#unwrap + * @const {!Object} + * @private + */ + this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + + /** + * This SafeHtml's directionality, or null if unknown. + * @private {?goog.i18n.bidi.Dir} + */ + this.dir_ = null; +}; + + +/** + * @override + * @const + */ +goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true; + + +/** @override */ +goog.html.SafeHtml.prototype.getDirection = function() { + return this.dir_; +}; + + +/** + * @override + * @const + */ +goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true; + + +/** + * Returns this SafeHtml's value as string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * that the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeHtml#unwrap + * @override + */ +goog.html.SafeHtml.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeHtml, use + * {@code goog.html.SafeHtml.unwrap}. + * + * @see goog.html.SafeHtml#unwrap + * @override + */ + goog.html.SafeHtml.prototype.toString = function() { + return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a SafeHtml + * object, and returns its value. + * @param {!goog.html.SafeHtml} safeHtml The object to extract from. + * @return {string} The SafeHtml object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeHtml.unwrap = function(safeHtml) { + // Perform additional run-time type-checking to ensure that safeHtml is indeed + // an instance of the expected type. This provides some additional protection + // against security bugs due to application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeHtml instanceof goog.html.SafeHtml && + safeHtml.constructor === goog.html.SafeHtml && + safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeHtml, got \'' + + safeHtml + '\' of type ' + goog.typeOf(safeHtml)); + return 'type_error:SafeHtml'; + } +}; + + +/** + * Shorthand for union of types that can sensibly be converted to strings + * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString). + * @private + * @typedef {string|number|boolean|!goog.string.TypedString| + * !goog.i18n.bidi.DirectionalString} + */ +goog.html.SafeHtml.TextOrHtml_; + + +/** + * Returns HTML-escaped text as a SafeHtml object. + * + * If text is of a type that implements + * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new + * {@code SafeHtml} object is set to {@code text}'s directionality, if known. + * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e., + * {@code null}). + * + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + */ +goog.html.SafeHtml.htmlEscape = function(textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; + } + var dir = null; + if (textOrHtml.implementsGoogI18nBidiDirectionalString) { + dir = textOrHtml.getDirection(); + } + var textAsString; + if (textOrHtml.implementsGoogStringTypedString) { + textAsString = textOrHtml.getTypedStringValue(); + } else { + textAsString = String(textOrHtml); + } + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.htmlEscape(textAsString), dir); +}; + + +/** + * Returns HTML-escaped text as a SafeHtml object, with newlines changed to + * <br>. + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + */ +goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; + } + var html = goog.html.SafeHtml.htmlEscape(textOrHtml); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)), + html.getDirection()); +}; + + +/** + * Returns HTML-escaped text as a SafeHtml object, with newlines changed to + * <br> and escaping whitespace to preserve spatial formatting. Character + * entity #160 is used to make it safer for XML. + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + */ +goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function( + textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; + } + var html = goog.html.SafeHtml.htmlEscape(textOrHtml); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)), + html.getDirection()); +}; + + +/** + * Coerces an arbitrary object into a SafeHtml object. + * + * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same + * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and + * HTML-escaped. If {@code textOrHtml} is of a type that implements + * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is + * preserved. + * + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to + * coerce. + * @return {!goog.html.SafeHtml} The resulting SafeHtml object. + * @deprecated Use goog.html.SafeHtml.htmlEscape. + */ +goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape; + + +/** + * @const + * @private + */ +goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/; + + +/** + * Set of attributes containing URL as defined at + * http://www.w3.org/TR/html5/index.html#attributes-1. + * @private @const {!Object<string,boolean>} + */ +goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet( + 'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster', + 'src'); + + +/** + * Tags which are unsupported via create(). They might be supported via a + * tag-specific create method. These are tags which might require a + * TrustedResourceUrl in one of their attributes or a restricted type for + * their content. + * @private @const {!Object<string,boolean>} + */ +goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet( + goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED, + goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH, + goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT, + goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE); + + +/** + * @typedef {string|number|goog.string.TypedString| + * goog.html.SafeStyle.PropertyMap|undefined} + */ +goog.html.SafeHtml.AttributeValue; + + +/** + * Creates a SafeHtml content consisting of a tag with optional attributes and + * optional content. + * + * For convenience tag names and attribute names are accepted as regular + * strings, instead of goog.string.Const. Nevertheless, you should not pass + * user-controlled values to these parameters. Note that these parameters are + * syntactically validated at runtime, and invalid values will result in + * an exception. + * + * Example usage: + * + * goog.html.SafeHtml.create('br'); + * goog.html.SafeHtml.create('div', {'class': 'a'}); + * goog.html.SafeHtml.create('p', {}, 'a'); + * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br')); + * + * goog.html.SafeHtml.create('span', { + * 'style': {'margin': '0'} + * }); + * + * To guarantee SafeHtml's type contract is upheld there are restrictions on + * attribute values and tag names. + * + * - For attributes which contain script code (on*), a goog.string.Const is + * required. + * - For attributes which contain style (style), a goog.html.SafeStyle or a + * goog.html.SafeStyle.PropertyMap is required. + * - For attributes which are interpreted as URLs (e.g. src, href) a + * goog.html.SafeUrl, goog.string.Const or string is required. If a string + * is passed, it will be sanitized with SafeUrl.sanitize(). + * - For tags which can load code or set security relevant page metadata, + * more specific goog.html.SafeHtml.create*() functions must be used. Tags + * which are not supported by this function are applet, base, embed, iframe, + * link, math, object, script, style, svg, and template. + * + * @param {!goog.dom.TagName|string} tagName The name of the tag. Only tag names + * consisting of [a-zA-Z0-9-] are allowed. Tag names documented above are + * disallowed. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes + * the attribute to be omitted. + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to + * HTML-escape and put inside the tag. This must be empty for void tags + * like <br>. Array elements are concatenated. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid tag name, attribute name, or attribute value is + * provided. + * @throws {goog.asserts.AssertionError} If content for void tag is provided. + */ +goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) { + goog.html.SafeHtml.verifyTagName(String(tagName)); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + String(tagName), opt_attributes, opt_content); +}; + + +/** + * Verifies if the tag name is valid and if it doesn't change the context. + * E.g. STRONG is fine but SCRIPT throws because it changes context. See + * goog.html.SafeHtml.create for an explanation of allowed tags. + * @param {string} tagName + * @throws {Error} If invalid tag name is provided. + * @package + */ +goog.html.SafeHtml.verifyTagName = function(tagName) { + if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) { + throw new Error('Invalid tag name <' + tagName + '>.'); + } + if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) { + throw new Error('Tag name <' + tagName + '> is not allowed for SafeHtml.'); + } +}; + + +/** + * Creates a SafeHtml representing an iframe tag. + * + * This by default restricts the iframe as much as possible by setting the + * sandbox attribute to the empty string. If the iframe requires less + * restrictions, set the sandbox attribute as tight as possible, but do not rely + * on the sandbox as a security feature because it is not supported by older + * browsers. If a sandbox is essential to security (e.g. for third-party + * frames), use createSandboxIframe which checks for browser support. + * + * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox + * + * @param {?goog.html.TrustedResourceUrl=} opt_src The value of the src + * attribute. If null or undefined src will not be set. + * @param {?goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute. + * If null or undefined srcdoc will not be set. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes + * the attribute to be omitted. + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to + * HTML-escape and put inside the tag. Array elements are concatenated. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid tag name, attribute name, or attribute value is + * provided. If opt_attributes contains the src or srcdoc attributes. + */ +goog.html.SafeHtml.createIframe = function( + opt_src, opt_srcdoc, opt_attributes, opt_content) { + if (opt_src) { + // Check whether this is really TrustedResourceUrl. + goog.html.TrustedResourceUrl.unwrap(opt_src); + } + + var fixedAttributes = {}; + fixedAttributes['src'] = opt_src || null; + fixedAttributes['srcdoc'] = + opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc); + var defaultAttributes = {'sandbox': ''}; + var attributes = goog.html.SafeHtml.combineAttributes( + fixedAttributes, defaultAttributes, opt_attributes); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'iframe', attributes, opt_content); +}; + + +/** + * Creates a SafeHtml representing a sandboxed iframe tag. + * + * The sandbox attribute is enforced in its most restrictive mode, an empty + * string. Consequently, the security requirements for the src and srcdoc + * attributes are relaxed compared to SafeHtml.createIframe. This function + * will throw on browsers that do not support the sandbox attribute, as + * determined by SafeHtml.canUseSandboxIframe. + * + * The SafeHtml returned by this function can trigger downloads with no + * user interaction on Chrome (though only a few, further attempts are blocked). + * Firefox and IE will block all downloads from the sandbox. + * + * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox + * @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html + * + * @param {string|!goog.html.SafeUrl=} opt_src The value of the src + * attribute. If null or undefined src will not be set. + * @param {string=} opt_srcdoc The value of the srcdoc attribute. + * If null or undefined srcdoc will not be set. Will not be sanitized. + * @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes + * the attribute to be omitted. + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to + * HTML-escape and put inside the tag. Array elements are concatenated. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid tag name, attribute name, or attribute value is + * provided. If opt_attributes contains the src, srcdoc or sandbox + * attributes. If browser does not support the sandbox attribute on iframe. + */ +goog.html.SafeHtml.createSandboxIframe = function( + opt_src, opt_srcdoc, opt_attributes, opt_content) { + if (!goog.html.SafeHtml.canUseSandboxIframe()) { + throw new Error('The browser does not support sandboxed iframes.'); + } + + var fixedAttributes = {}; + if (opt_src) { + // Note that sanitize is a no-op on SafeUrl. + fixedAttributes['src'] = + goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src)); + } else { + fixedAttributes['src'] = null; + } + fixedAttributes['srcdoc'] = opt_srcdoc || null; + fixedAttributes['sandbox'] = ''; + var attributes = + goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'iframe', attributes, opt_content); +}; + + +/** + * Checks if the user agent supports sandboxed iframes. + * @return {boolean} + */ +goog.html.SafeHtml.canUseSandboxIframe = function() { + return goog.global['HTMLIFrameElement'] && + ('sandbox' in goog.global['HTMLIFrameElement'].prototype); +}; + + +/** + * Creates a SafeHtml representing a script tag with the src attribute. + * @param {!goog.html.TrustedResourceUrl} src The value of the src + * attribute. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} + * opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined + * causes the attribute to be omitted. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid attribute name or value is provided. If + * opt_attributes contains the src attribute. + */ +goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) { + // TODO(mlourenco): The charset attribute should probably be blocked. If + // its value is attacker controlled, the script contains attacker controlled + // sub-strings (even if properly escaped) and the server does not set charset + // then XSS is likely possible. + // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-charset + + // Check whether this is really TrustedResourceUrl. + goog.html.TrustedResourceUrl.unwrap(src); + + var fixedAttributes = {'src': src}; + var defaultAttributes = {}; + var attributes = goog.html.SafeHtml.combineAttributes( + fixedAttributes, defaultAttributes, opt_attributes); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'script', attributes); +}; + + +/** + * Creates a SafeHtml representing a script tag. Does not allow the language, + * src, text or type attributes to be set. + * @param {!goog.html.SafeScript|!Array<!goog.html.SafeScript>} + * script Content to put inside the tag. Array elements are + * concatenated. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes + * the attribute to be omitted. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid attribute name or attribute value is provided. If + * opt_attributes contains the language, src, text or type attribute. + */ +goog.html.SafeHtml.createScript = function(script, opt_attributes) { + for (var attr in opt_attributes) { + var attrLower = attr.toLowerCase(); + if (attrLower == 'language' || attrLower == 'src' || attrLower == 'text' || + attrLower == 'type') { + throw new Error('Cannot set "' + attrLower + '" attribute'); + } + } + + var content = ''; + script = goog.array.concat(script); + for (var i = 0; i < script.length; i++) { + content += goog.html.SafeScript.unwrap(script[i]); + } + // Convert to SafeHtml so that it's not HTML-escaped. This is safe because + // as part of its contract, SafeScript should have no dangerous '<'. + var htmlContent = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + content, goog.i18n.bidi.Dir.NEUTRAL); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'script', opt_attributes, htmlContent); +}; + + +/** + * Creates a SafeHtml representing a style tag. The type attribute is set + * to "text/css". + * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>} + * styleSheet Content to put inside the tag. Array elements are + * concatenated. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Mapping from attribute names to their values. Only attribute names + * consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes + * the attribute to be omitted. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid attribute name or attribute value is provided. If + * opt_attributes contains the type attribute. + */ +goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) { + var fixedAttributes = {'type': 'text/css'}; + var defaultAttributes = {}; + var attributes = goog.html.SafeHtml.combineAttributes( + fixedAttributes, defaultAttributes, opt_attributes); + + var content = ''; + styleSheet = goog.array.concat(styleSheet); + for (var i = 0; i < styleSheet.length; i++) { + content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]); + } + // Convert to SafeHtml so that it's not HTML-escaped. This is safe because + // as part of its contract, SafeStyleSheet should have no dangerous '<'. + var htmlContent = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + content, goog.i18n.bidi.Dir.NEUTRAL); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'style', attributes, htmlContent); +}; + + +/** + * Creates a SafeHtml representing a meta refresh tag. + * @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is + * passed, it will be sanitized with SafeUrl.sanitize(). + * @param {number=} opt_secs Number of seconds until the page should be + * reloaded. Will be set to 0 if unspecified. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + */ +goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) { + + // Note that sanitize is a no-op on SafeUrl. + var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url)); + + if (goog.labs.userAgent.browser.isIE() || + goog.labs.userAgent.browser.isEdge()) { + // IE/EDGE can't parse the content attribute if the url contains a + // semicolon. We can fix this by adding quotes around the url, but then we + // can't parse quotes in the URL correctly. Also, it seems that IE/EDGE + // did not unescape semicolons in these URLs at some point in the past. We + // take a best-effort approach. + // + // If the URL has semicolons (which may happen in some cases, see + // http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2 + // for instance), wrap it in single quotes to protect the semicolons. + // If the URL has semicolons and single quotes, url-encode the single quotes + // as well. + // + // This is imperfect. Notice that both ' and ; are reserved characters in + // URIs, so this could do the wrong thing, but at least it will do the wrong + // thing in only rare cases. + if (goog.string.contains(unwrappedUrl, ';')) { + unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'"; + } + } + var attributes = { + 'http-equiv': 'refresh', + 'content': (opt_secs || 0) + '; url=' + unwrappedUrl + }; + + // This function will handle the HTML escaping for attributes. + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'meta', attributes); +}; + + +/** + * @param {string} tagName The tag name. + * @param {string} name The attribute name. + * @param {!goog.html.SafeHtml.AttributeValue} value The attribute value. + * @return {string} A "name=value" string. + * @throws {Error} If attribute value is unsafe for the given tag and attribute. + * @private + */ +goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) { + // If it's goog.string.Const, allow any valid attribute name. + if (value instanceof goog.string.Const) { + value = goog.string.Const.unwrap(value); + } else if (name.toLowerCase() == 'style') { + value = goog.html.SafeHtml.getStyleValue_(value); + } else if (/^on/i.test(name)) { + // TODO(jakubvrana): Disallow more attributes with a special meaning. + throw new Error( + 'Attribute "' + name + '" requires goog.string.Const value, "' + value + + '" given.'); + // URL attributes handled differently according to tag. + } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) { + if (value instanceof goog.html.TrustedResourceUrl) { + value = goog.html.TrustedResourceUrl.unwrap(value); + } else if (value instanceof goog.html.SafeUrl) { + value = goog.html.SafeUrl.unwrap(value); + } else if (goog.isString(value)) { + value = goog.html.SafeUrl.sanitize(value).getTypedStringValue(); + } else { + throw new Error( + 'Attribute "' + name + '" on tag "' + tagName + + '" requires goog.html.SafeUrl, goog.string.Const, or string,' + + ' value "' + value + '" given.'); + } + } + + // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require + // HTML-escaping. + if (value.implementsGoogStringTypedString) { + // Ok to call getTypedStringValue() since there's no reliance on the type + // contract for security here. + value = value.getTypedStringValue(); + } + + goog.asserts.assert( + goog.isString(value) || goog.isNumber(value), + 'String or number value expected, got ' + (typeof value) + + ' with value: ' + value); + return name + '="' + goog.string.htmlEscape(String(value)) + '"'; +}; + + +/** + * Gets value allowed in "style" attribute. + * @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a + * map which will be passed to goog.html.SafeStyle.create. + * @return {string} Unwrapped value. + * @throws {Error} If string value is given. + * @private + */ +goog.html.SafeHtml.getStyleValue_ = function(value) { + if (!goog.isObject(value)) { + throw new Error( + 'The "style" attribute requires goog.html.SafeStyle or map ' + + 'of style properties, ' + (typeof value) + ' given: ' + value); + } + if (!(value instanceof goog.html.SafeStyle)) { + // Process the property bag into a style object. + value = goog.html.SafeStyle.create(value); + } + return goog.html.SafeStyle.unwrap(value); +}; + + +/** + * Creates a SafeHtml content with known directionality consisting of a tag with + * optional attributes and optional content. + * @param {!goog.i18n.bidi.Dir} dir Directionality. + * @param {string} tagName + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + */ +goog.html.SafeHtml.createWithDir = function( + dir, tagName, opt_attributes, opt_content) { + var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content); + html.dir_ = dir; + return html; +}; + + +/** + * Creates a new SafeHtml object by concatenating values. + * @param {...(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate. + * @return {!goog.html.SafeHtml} + */ +goog.html.SafeHtml.concat = function(var_args) { + var dir = goog.i18n.bidi.Dir.NEUTRAL; + var content = ''; + + /** + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + var html = goog.html.SafeHtml.htmlEscape(argument); + content += goog.html.SafeHtml.unwrap(html); + var htmlDir = html.getDirection(); + if (dir == goog.i18n.bidi.Dir.NEUTRAL) { + dir = htmlDir; + } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) { + dir = null; + } + } + }; + + goog.array.forEach(arguments, addArgument); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + content, dir); +}; + + +/** + * Creates a new SafeHtml object with known directionality by concatenating the + * values. + * @param {!goog.i18n.bidi.Dir} dir Directionality. + * @param {...(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array + * arguments would be processed recursively. + * @return {!goog.html.SafeHtml} + */ +goog.html.SafeHtml.concatWithDir = function(dir, var_args) { + var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1)); + html.dir_ = dir; + return html; +}; + + +/** + * Type marker for the SafeHtml type, used to implement additional run-time + * type checking. + * @const {!Object} + * @private + */ +goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Package-internal utility method to create SafeHtml instances. + * + * @param {string} html The string to initialize the SafeHtml object with. + * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be + * constructed, or null if unknown. + * @return {!goog.html.SafeHtml} The initialized SafeHtml object. + * @package + */ +goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function( + html, dir) { + return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_( + html, dir); +}; + + +/** + * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This + * method exists only so that the compiler can dead code eliminate static + * fields (like EMPTY) when they're not accessed. + * @param {string} html + * @param {?goog.i18n.bidi.Dir} dir + * @return {!goog.html.SafeHtml} + * @private + */ +goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function( + html, dir) { + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html; + this.dir_ = dir; + return this; +}; + + +/** + * Like create() but does not restrict which tags can be constructed. + * + * @param {string} tagName Tag name. Set or validated by caller. + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * @param {(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content + * @return {!goog.html.SafeHtml} + * @throws {Error} If invalid or unsafe attribute name or value is provided. + * @throws {goog.asserts.AssertionError} If content for void tag is provided. + * @package + */ +goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function( + tagName, opt_attributes, opt_content) { + var dir = null; + var result = '<' + tagName; + result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes); + + var content = opt_content; + if (!goog.isDefAndNotNull(content)) { + content = []; + } else if (!goog.isArray(content)) { + content = [content]; + } + + if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) { + goog.asserts.assert( + !content.length, 'Void tag <' + tagName + '> does not allow content.'); + result += '>'; + } else { + var html = goog.html.SafeHtml.concat(content); + result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>'; + dir = html.getDirection(); + } + + var dirAttribute = opt_attributes && opt_attributes['dir']; + if (dirAttribute) { + if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) { + // If the tag has the "dir" attribute specified then its direction is + // neutral because it can be safely used in any context. + dir = goog.i18n.bidi.Dir.NEUTRAL; + } else { + dir = null; + } + } + + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + result, dir); +}; + + +/** + * Creates a string with attributes to insert after tagName. + * @param {string} tagName + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * @return {string} Returns an empty string if there are no attributes, returns + * a string starting with a space otherwise. + * @throws {Error} If attribute value is unsafe for the given tag and attribute. + * @package + */ +goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) { + var result = ''; + if (opt_attributes) { + for (var name in opt_attributes) { + if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) { + throw new Error('Invalid attribute name "' + name + '".'); + } + var value = opt_attributes[name]; + if (!goog.isDefAndNotNull(value)) { + continue; + } + result += + ' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value); + } + } + return result; +}; + + +/** + * @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>} fixedAttributes + * @param {!Object<string, string>} defaultAttributes + * @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes + * Optional attributes passed to create*(). + * @return {!Object<string, ?goog.html.SafeHtml.AttributeValue>} + * @throws {Error} If opt_attributes contains an attribute with the same name + * as an attribute in fixedAttributes. + * @package + */ +goog.html.SafeHtml.combineAttributes = function( + fixedAttributes, defaultAttributes, opt_attributes) { + var combinedAttributes = {}; + var name; + + for (name in fixedAttributes) { + goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); + combinedAttributes[name] = fixedAttributes[name]; + } + for (name in defaultAttributes) { + goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); + combinedAttributes[name] = defaultAttributes[name]; + } + + for (name in opt_attributes) { + var nameLower = name.toLowerCase(); + if (nameLower in fixedAttributes) { + throw new Error( + 'Cannot override "' + nameLower + '" attribute, got "' + name + + '" with value "' + opt_attributes[name] + '"'); + } + if (nameLower in defaultAttributes) { + delete combinedAttributes[nameLower]; + } + combinedAttributes[name] = opt_attributes[name]; + } + + return combinedAttributes; +}; + + +/** + * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>". + * @const {!goog.html.SafeHtml} + */ +goog.html.SafeHtml.DOCTYPE_HTML = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL); + + +/** + * A SafeHtml instance corresponding to the empty string. + * @const {!goog.html.SafeHtml} + */ +goog.html.SafeHtml.EMPTY = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + '', goog.i18n.bidi.Dir.NEUTRAL); + + +/** + * A SafeHtml instance corresponding to the <br> tag. + * @const {!goog.html.SafeHtml} + */ +goog.html.SafeHtml.BR = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + '<br>', goog.i18n.bidi.Dir.NEUTRAL);
diff --git a/third_party/ink/closure/html/safescript.js b/third_party/ink/closure/html/safescript.js new file mode 100644 index 0000000..7a945eb --- /dev/null +++ b/third_party/ink/closure/html/safescript.js
@@ -0,0 +1,234 @@ +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The SafeScript type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.SafeScript'); + +goog.require('goog.asserts'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A string-like object which represents JavaScript code and that carries the + * security type contract that its value, as a string, will not cause execution + * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript + * in a browser. + * + * Instances of this type must be created via the factory method + * {@code goog.html.SafeScript.fromConstant} and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. + * + * A SafeScript's string representation can safely be interpolated as the + * content of a script element within HTML. The SafeScript string should not be + * escaped before interpolation. + * + * Note that the SafeScript might contain text that is attacker-controlled but + * that text should have been interpolated with appropriate escaping, + * sanitization and/or validation into the right location in the script, such + * that it is highly constrained in its effect (for example, it had to match a + * set of whitelisted words). + * + * A SafeScript can be constructed via security-reviewed unchecked + * conversions. In this case producers of SafeScript must ensure themselves that + * the SafeScript does not contain unsafe script. Note in particular that + * {@code <} is dangerous, even when inside JavaScript strings, and so should + * always be forbidden or JavaScript escaped in user controlled input. For + * example, if {@code </script><script>evil</script>"} were + * interpolated inside a JavaScript string, it would break out of the context + * of the original script element and {@code evil} would execute. Also note + * that within an HTML script (raw text) element, HTML character references, + * such as "<" are not allowed. See + * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements. + * + * @see goog.html.SafeScript#fromConstant + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} + */ +goog.html.SafeScript = function() { + /** + * The contained value of this SafeScript. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeScript#unwrap + * @const {!Object} + * @private + */ + this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; + + +/** + * @override + * @const + */ +goog.html.SafeScript.prototype.implementsGoogStringTypedString = true; + + +/** + * Type marker for the SafeScript type, used to implement additional + * run-time type checking. + * @const {!Object} + * @private + */ +goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Creates a SafeScript object from a compile-time constant string. + * + * @param {!goog.string.Const} script A compile-time-constant string from which + * to create a SafeScript. + * @return {!goog.html.SafeScript} A SafeScript object initialized to + * {@code script}. + */ +goog.html.SafeScript.fromConstant = function(script) { + var scriptString = goog.string.Const.unwrap(script); + if (scriptString.length === 0) { + return goog.html.SafeScript.EMPTY; + } + return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( + scriptString); +}; + + +/** + * Returns this SafeScript's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeScript#unwrap + * @override + */ +goog.html.SafeScript.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeScriptWrappedValue_; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeScript, use + * {@code goog.html.SafeScript.unwrap}. + * + * @see goog.html.SafeScript#unwrap + * @override + */ + goog.html.SafeScript.prototype.toString = function() { + return 'SafeScript{' + + this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a + * SafeScript object, and returns its value. + * + * @param {!goog.html.SafeScript} safeScript The object to extract from. + * @return {string} The safeScript object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeScript.unwrap = function(safeScript) { + // Perform additional Run-time type-checking to ensure that + // safeScript is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeScript instanceof goog.html.SafeScript && + safeScript.constructor === goog.html.SafeScript && + safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeScript, got \'' + + safeScript + '\' of type ' + goog.typeOf(safeScript)); + return 'type_error:SafeScript'; + } +}; + + +/** + * Package-internal utility method to create SafeScript instances. + * + * @param {string} script The string to initialize the SafeScript object with. + * @return {!goog.html.SafeScript} The initialized SafeScript object. + * @package + */ +goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse = + function(script) { + return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_( + script); +}; + + +/** + * Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This + * method exists only so that the compiler can dead code eliminate static + * fields (like EMPTY) when they're not accessed. + * @param {string} script + * @return {!goog.html.SafeScript} + * @private + */ +goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function( + script) { + this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script; + return this; +}; + + +/** + * A SafeScript instance corresponding to the empty string. + * @const {!goog.html.SafeScript} + */ +goog.html.SafeScript.EMPTY = + goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
diff --git a/third_party/ink/closure/html/safestyle.js b/third_party/ink/closure/html/safestyle.js new file mode 100644 index 0000000..95ff10c1 --- /dev/null +++ b/third_party/ink/closure/html/safestyle.js
@@ -0,0 +1,560 @@ +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The SafeStyle type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.SafeStyle'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A string-like object which represents a sequence of CSS declarations + * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...}) + * and that carries the security type contract that its value, as a string, + * will not cause untrusted script execution (XSS) when evaluated as CSS in a + * browser. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeStyle.create} or + * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. + * + * SafeStyle's string representation can safely be: + * <ul> + * <li>Interpolated as the content of a *quoted* HTML style attribute. + * However, the SafeStyle string *must be HTML-attribute-escaped* before + * interpolation. + * <li>Interpolated as the content of a {}-wrapped block within a stylesheet. + * '<' characters in the SafeStyle string *must be CSS-escaped* before + * interpolation. The SafeStyle string is also guaranteed not to be able + * to introduce new properties or elide existing ones. + * <li>Interpolated as the content of a {}-wrapped block within an HTML + * <style> element. '<' characters in the SafeStyle string + * *must be CSS-escaped* before interpolation. + * <li>Assigned to the style property of a DOM node. The SafeStyle string + * should not be escaped before being assigned to the property. + * </ul> + * + * A SafeStyle may never contain literal angle brackets. Otherwise, it could + * be unsafe to place a SafeStyle into a <style> tag (where it can't + * be HTML escaped). For example, if the SafeStyle containing + * "{@code font: 'foo <style/><script>evil</script>'}" were + * interpolated within a <style> tag, this would then break out of the + * style context into HTML. + * + * A SafeStyle may contain literal single or double quotes, and as such the + * entire style string must be escaped when used in a style attribute (if + * this were not the case, the string could contain a matching quote that + * would escape from the style attribute). + * + * Values of this type must be composable, i.e. for any two values + * {@code style1} and {@code style2} of this type, + * {@code goog.html.SafeStyle.unwrap(style1) + + * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies + * the SafeStyle type constraint. This requirement implies that for any value + * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must + * not end in a "property value" or "property name" context. For example, + * a value of {@code background:url("} or {@code font-} would not satisfy the + * SafeStyle contract. This is because concatenating such strings with a + * second value that itself does not contain unsafe CSS can result in an + * overall string that does. For example, if {@code javascript:evil())"} is + * appended to {@code background:url("}, the resulting string may result in + * the execution of a malicious script. + * + * TODO(mlourenco): Consider whether we should implement UTF-8 interchange + * validity checks and blacklisting of newlines (including Unicode ones) and + * other whitespace characters (\t, \f). Document here if so and also update + * SafeStyle.fromConstant(). + * + * The following example values comply with this type's contract: + * <ul> + * <li><pre>width: 1em;</pre> + * <li><pre>height:1em;</pre> + * <li><pre>width: 1em;height: 1em;</pre> + * <li><pre>background:url('http://url');</pre> + * </ul> + * In addition, the empty string is safe for use in a CSS attribute. + * + * The following example values do NOT comply with this type's contract: + * <ul> + * <li><pre>background: red</pre> (missing a trailing semi-colon) + * <li><pre>background:</pre> (missing a value and a trailing semi-colon) + * <li><pre>1em</pre> (missing an attribute name, which provides context for + * the value) + * </ul> + * + * @see goog.html.SafeStyle#create + * @see goog.html.SafeStyle#fromConstant + * @see http://www.w3.org/TR/css3-syntax/ + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} + */ +goog.html.SafeStyle = function() { + /** + * The contained value of this SafeStyle. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeStyle#unwrap + * @const {!Object} + * @private + */ + this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; + + +/** + * @override + * @const + */ +goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true; + + +/** + * Type marker for the SafeStyle type, used to implement additional + * run-time type checking. + * @const {!Object} + * @private + */ +goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Creates a SafeStyle object from a compile-time constant string. + * + * {@code style} should be in the format + * {@code name: value; [name: value; ...]} and must not have any < or > + * characters in it. This is so that SafeStyle's contract is preserved, + * allowing the SafeStyle to correctly be interpreted as a sequence of CSS + * declarations and without affecting the syntactic structure of any + * surrounding CSS and HTML. + * + * This method performs basic sanity checks on the format of {@code style} + * but does not constrain the format of {@code name} and {@code value}, except + * for disallowing tag characters. + * + * @param {!goog.string.Const} style A compile-time-constant string from which + * to create a SafeStyle. + * @return {!goog.html.SafeStyle} A SafeStyle object initialized to + * {@code style}. + */ +goog.html.SafeStyle.fromConstant = function(style) { + var styleString = goog.string.Const.unwrap(style); + if (styleString.length === 0) { + return goog.html.SafeStyle.EMPTY; + } + goog.html.SafeStyle.checkStyle_(styleString); + goog.asserts.assert( + goog.string.endsWith(styleString, ';'), + 'Last character of style string is not \';\': ' + styleString); + goog.asserts.assert( + goog.string.contains(styleString, ':'), + 'Style string must contain at least one \':\', to ' + + 'specify a "name: value" pair: ' + styleString); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + styleString); +}; + + +/** + * Checks if the style definition is valid. + * @param {string} style + * @private + */ +goog.html.SafeStyle.checkStyle_ = function(style) { + goog.asserts.assert( + !/[<>]/.test(style), 'Forbidden characters in style string: ' + style); +}; + + +/** + * Returns this SafeStyle's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeStyle#unwrap + * @override + */ +goog.html.SafeStyle.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeStyleWrappedValue_; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeStyle, use + * {@code goog.html.SafeStyle.unwrap}. + * + * @see goog.html.SafeStyle#unwrap + * @override + */ + goog.html.SafeStyle.prototype.toString = function() { + return 'SafeStyle{' + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a + * SafeStyle object, and returns its value. + * + * @param {!goog.html.SafeStyle} safeStyle The object to extract from. + * @return {string} The safeStyle object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeStyle.unwrap = function(safeStyle) { + // Perform additional Run-time type-checking to ensure that + // safeStyle is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeStyle instanceof goog.html.SafeStyle && + safeStyle.constructor === goog.html.SafeStyle && + safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeStyle, got \'' + + safeStyle + '\' of type ' + goog.typeOf(safeStyle)); + return 'type_error:SafeStyle'; + } +}; + + +/** + * Package-internal utility method to create SafeStyle instances. + * + * @param {string} style The string to initialize the SafeStyle object with. + * @return {!goog.html.SafeStyle} The initialized SafeStyle object. + * @package + */ +goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = function( + style) { + return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style); +}; + + +/** + * Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This + * method exists only so that the compiler can dead code eliminate static + * fields (like EMPTY) when they're not accessed. + * @param {string} style + * @return {!goog.html.SafeStyle} + * @private + */ +goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function( + style) { + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style; + return this; +}; + + +/** + * A SafeStyle instance corresponding to the empty string. + * @const {!goog.html.SafeStyle} + */ +goog.html.SafeStyle.EMPTY = + goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(''); + + +/** + * The innocuous string generated by goog.html.SafeStyle.create when passed + * an unsafe value. + * @const {string} + */ +goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez'; + + +/** + * A single property value. + * @typedef {string|!goog.string.Const|!goog.html.SafeUrl} + */ +goog.html.SafeStyle.PropertyValue; + + +/** + * Mapping of property names to their values. + * We don't support numbers even though some values might be numbers (e.g. + * line-height or 0 for any length). The reason is that most numeric values need + * units (e.g. '1px') and allowing numbers could cause users forgetting about + * them. + * @typedef {!Object<string, ?goog.html.SafeStyle.PropertyValue| + * ?Array<!goog.html.SafeStyle.PropertyValue>>} + */ +goog.html.SafeStyle.PropertyMap; + + +/** + * Creates a new SafeStyle object from the properties specified in the map. + * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to + * their values, for example {'margin': '1px'}. Names must consist of + * [-_a-zA-Z0-9]. Values might be strings consisting of + * [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced. We also + * allow simple functions like rgb() and url() which sanitizes its contents. + * Other values must be wrapped in goog.string.Const. URLs might be passed + * as goog.html.SafeUrl which will be wrapped into url(""). We also support + * array whose elements are joined with ' '. Null value causes skipping the + * property. + * @return {!goog.html.SafeStyle} + * @throws {Error} If invalid name is provided. + * @throws {goog.asserts.AssertionError} If invalid value is provided. With + * disabled assertions, invalid value is replaced by + * goog.html.SafeStyle.INNOCUOUS_STRING. + */ +goog.html.SafeStyle.create = function(map) { + var style = ''; + for (var name in map) { + if (!/^[-_a-zA-Z0-9]+$/.test(name)) { + throw new Error('Name allows only [-_a-zA-Z0-9], got: ' + name); + } + var value = map[name]; + if (value == null) { + continue; + } + if (goog.isArray(value)) { + value = goog.array.map(value, goog.html.SafeStyle.sanitizePropertyValue_) + .join(' '); + } else { + value = goog.html.SafeStyle.sanitizePropertyValue_(value); + } + style += name + ':' + value + ';'; + } + if (!style) { + return goog.html.SafeStyle.EMPTY; + } + goog.html.SafeStyle.checkStyle_(style); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); +}; + + +/** + * Checks and converts value to string. + * @param {!goog.html.SafeStyle.PropertyValue} value + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizePropertyValue_ = function(value) { + if (value instanceof goog.html.SafeUrl) { + var url = goog.html.SafeUrl.unwrap(value); + return 'url("' + url.replace(/</g, '%3c').replace(/[\\"]/g, '\\$&') + '")'; + } + var result = value instanceof goog.string.Const ? + goog.string.Const.unwrap(value) : + goog.html.SafeStyle.sanitizePropertyValueString_(String(value)); + // These characters can be used to change context and we don't want that even + // with const values. + goog.asserts.assert(!/[{;}]/.test(result), 'Value does not allow [{;}].'); + return result; +}; + + +/** + * Checks string value. + * @param {string} value + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizePropertyValueString_ = function(value) { + var valueWithoutFunctions = + value.replace(goog.html.SafeUrl.FUNCTIONS_RE_, '$1') + .replace(goog.html.SafeUrl.URL_RE_, 'url'); + if (!goog.html.SafeStyle.VALUE_RE_.test(valueWithoutFunctions)) { + goog.asserts.fail( + 'String value allows only ' + goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ + + ' and simple functions, got: ' + value); + return goog.html.SafeStyle.INNOCUOUS_STRING; + } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) { + goog.asserts.fail('String value requires balanced quotes, got: ' + value); + return goog.html.SafeStyle.INNOCUOUS_STRING; + } + return goog.html.SafeStyle.sanitizeUrl_(value); +}; + + +/** + * Checks that quotes (" and ') are properly balanced inside a string. Assumes + * that neither escape (\) nor any other character that could result in + * breaking out of a string parsing context are allowed; + * see http://www.w3.org/TR/css3-syntax/#string-token-diagram. + * @param {string} value Untrusted CSS property value. + * @return {boolean} True if property value is safe with respect to quote + * balancedness. + * @private + */ +goog.html.SafeStyle.hasBalancedQuotes_ = function(value) { + var outsideSingle = true; + var outsideDouble = true; + for (var i = 0; i < value.length; i++) { + var c = value.charAt(i); + if (c == "'" && outsideDouble) { + outsideSingle = !outsideSingle; + } else if (c == '"' && outsideSingle) { + outsideDouble = !outsideDouble; + } + } + return outsideSingle && outsideDouble; +}; + + +/** + * Characters allowed in goog.html.SafeStyle.VALUE_RE_. + * @private {string} + */ +goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ = '[-,."\'%_!# a-zA-Z0-9]'; + + +/** + * Regular expression for safe values. + * + * Quotes (" and ') are allowed, but a check must be done elsewhere to ensure + * they're balanced. + * + * ',' allows multiple values to be assigned to the same property + * (e.g. background-attachment or font-family) and hence could allow + * multiple values to get injected, but that should pose no risk of XSS. + * + * The expression checks only for XSS safety, not for CSS validity. + * @const {!RegExp} + * @private + */ +goog.html.SafeStyle.VALUE_RE_ = + new RegExp('^' + goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ + '+$'); + + +/** + * Regular expression for url(). We support URLs allowed by + * https://www.w3.org/TR/css-syntax-3/#url-token-diagram without using escape + * sequences. Use percent-encoding if you need to use special characters like + * backslash. + * @private @const {!RegExp} + */ +goog.html.SafeUrl.URL_RE_ = new RegExp( + '\\b(url\\([ \t\n]*)(' + + '\'[ -&(-\\[\\]-~]*\'' + // Printable characters except ' and \. + '|"[ !#-\\[\\]-~]*"' + // Printable characters except " and \. + '|[!#-&*-\\[\\]-~]*' + // Printable characters except [ "'()\\]. + ')([ \t\n]*\\))', + 'g'); + + +/** + * Regular expression for simple functions. + * @private @const {!RegExp} + */ +goog.html.SafeUrl.FUNCTIONS_RE_ = new RegExp( + '\\b(hsl|hsla|rgb|rgba|(rotate|scale|translate)(X|Y|Z|3d)?)' + + '\\([-0-9a-z.%, ]+\\)', + 'g'); + + +/** + * Sanitize URLs inside url(). + * + * NOTE: We could also consider using CSS.escape once that's available in the + * browsers. However, loosely matching URL e.g. with url\(.*\) and then escaping + * the contents would result in a slightly different language than CSS leading + * to confusion of users. E.g. url(")") is valid in CSS but it would be invalid + * as seen by our parser. On the other hand, url(\) is invalid in CSS but our + * parser would be fine with it. + * + * @param {string} value Untrusted CSS property value. + * @return {string} + * @private + */ +goog.html.SafeStyle.sanitizeUrl_ = function(value) { + return value.replace( + goog.html.SafeUrl.URL_RE_, function(match, before, url, after) { + var quote = ''; + url = url.replace(/^(['"])(.*)\1$/, function(match, start, inside) { + quote = start; + return inside; + }); + var sanitized = goog.html.SafeUrl.sanitize(url).getTypedStringValue(); + return before + quote + sanitized + quote + after; + }); +}; + + +/** + * Creates a new SafeStyle object by concatenating the values. + * @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args + * SafeStyles to concatenate. + * @return {!goog.html.SafeStyle} + */ +goog.html.SafeStyle.concat = function(var_args) { + var style = ''; + + /** + * @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + style += goog.html.SafeStyle.unwrap(argument); + } + }; + + goog.array.forEach(arguments, addArgument); + if (!style) { + return goog.html.SafeStyle.EMPTY; + } + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); +};
diff --git a/third_party/ink/closure/html/safestylesheet.js b/third_party/ink/closure/html/safestylesheet.js new file mode 100644 index 0000000..f690dbda --- /dev/null +++ b/third_party/ink/closure/html/safestylesheet.js
@@ -0,0 +1,344 @@ +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The SafeStyleSheet type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.SafeStyleSheet'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A string-like object which represents a CSS style sheet and that carries the + * security type contract that its value, as a string, will not cause untrusted + * script execution (XSS) when evaluated as CSS in a browser. + * + * Instances of this type must be created via the factory method + * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. + * + * A SafeStyleSheet's string representation can safely be interpolated as the + * content of a style element within HTML. The SafeStyleSheet string should + * not be escaped before interpolation. + * + * Values of this type must be composable, i.e. for any two values + * {@code styleSheet1} and {@code styleSheet2} of this type, + * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) + + * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that + * satisfies the SafeStyleSheet type constraint. This requirement implies that + * for any value {@code styleSheet} of this type, + * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in + * "beginning of rule" context. + + * A SafeStyleSheet can be constructed via security-reviewed unchecked + * conversions. In this case producers of SafeStyleSheet must ensure themselves + * that the SafeStyleSheet does not contain unsafe script. Note in particular + * that {@code <} is dangerous, even when inside CSS strings, and so should + * always be forbidden or CSS-escaped in user controlled input. For example, if + * {@code </style><script>evil</script>"} were interpolated + * inside a CSS string, it would break out of the context of the original + * style element and {@code evil} would execute. Also note that within an HTML + * style (raw text) element, HTML character references, such as + * {@code &lt;}, are not allowed. See + * + http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements + * (similar considerations apply to the style element). + * + * @see goog.html.SafeStyleSheet#fromConstant + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} + */ +goog.html.SafeStyleSheet = function() { + /** + * The contained value of this SafeStyleSheet. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeStyleSheet#unwrap + * @const {!Object} + * @private + */ + this.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; + + +/** + * @override + * @const + */ +goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true; + + +/** + * Type marker for the SafeStyleSheet type, used to implement additional + * run-time type checking. + * @const {!Object} + * @private + */ +goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Creates a style sheet consisting of one selector and one style definition. + * Use {@link goog.html.SafeStyleSheet.concat} to create longer style sheets. + * This function doesn't support @import, @media and similar constructs. + * @param {string} selector CSS selector, e.g. '#id' or 'tag .class, #id'. We + * support CSS3 selectors: https://w3.org/TR/css3-selectors/#selectors. + * @param {!goog.html.SafeStyle.PropertyMap|!goog.html.SafeStyle} style Style + * definition associated with the selector. + * @return {!goog.html.SafeStyleSheet} + * @throws {Error} If invalid selector is provided. + */ +goog.html.SafeStyleSheet.createRule = function(selector, style) { + if (goog.string.contains(selector, '<')) { + throw new Error('Selector does not allow \'<\', got: ' + selector); + } + + // Remove strings. + var selectorToCheck = + selector.replace(/('|")((?!\1)[^\r\n\f\\]|\\[\s\S])*\1/g, ''); + + // Check characters allowed in CSS3 selectors. + if (!/^[-_a-zA-Z0-9#.:* ,>+~[\]()=^$|]+$/.test(selectorToCheck)) { + throw new Error( + 'Selector allows only [-_a-zA-Z0-9#.:* ,>+~[\\]()=^$|] and ' + + 'strings, got: ' + selector); + } + + // Check balanced () and []. + if (!goog.html.SafeStyleSheet.hasBalancedBrackets_(selectorToCheck)) { + throw new Error('() and [] in selector must be balanced, got: ' + selector); + } + + if (!(style instanceof goog.html.SafeStyle)) { + style = goog.html.SafeStyle.create(style); + } + var styleSheet = selector + '{' + goog.html.SafeStyle.unwrap(style) + '}'; + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); +}; + + +/** + * Checks if a string has balanced () and [] brackets. + * @param {string} s String to check. + * @return {boolean} + * @private + */ +goog.html.SafeStyleSheet.hasBalancedBrackets_ = function(s) { + var brackets = {'(': ')', '[': ']'}; + var expectedBrackets = []; + for (var i = 0; i < s.length; i++) { + var ch = s[i]; + if (brackets[ch]) { + expectedBrackets.push(brackets[ch]); + } else if (goog.object.contains(brackets, ch)) { + if (expectedBrackets.pop() != ch) { + return false; + } + } + } + return expectedBrackets.length == 0; +}; + + +/** + * Creates a new SafeStyleSheet object by concatenating values. + * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)} + * var_args Values to concatenate. + * @return {!goog.html.SafeStyleSheet} + */ +goog.html.SafeStyleSheet.concat = function(var_args) { + var result = ''; + + /** + * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>} + * argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + result += goog.html.SafeStyleSheet.unwrap(argument); + } + }; + + goog.array.forEach(arguments, addArgument); + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result); +}; + + +/** + * Creates a SafeStyleSheet object from a compile-time constant string. + * + * {@code styleSheet} must not have any < characters in it, so that + * the syntactic structure of the surrounding HTML is not affected. + * + * @param {!goog.string.Const} styleSheet A compile-time-constant string from + * which to create a SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to + * {@code styleSheet}. + */ +goog.html.SafeStyleSheet.fromConstant = function(styleSheet) { + var styleSheetString = goog.string.Const.unwrap(styleSheet); + if (styleSheetString.length === 0) { + return goog.html.SafeStyleSheet.EMPTY; + } + // > is a valid character in CSS selectors and there's no strict need to + // block it if we already block <. + goog.asserts.assert( + !goog.string.contains(styleSheetString, '<'), + "Forbidden '<' character in style sheet string: " + styleSheetString); + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString); +}; + + +/** + * Returns this SafeStyleSheet's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap} + * instead of this method. If in doubt, assume that it's security relevant. In + * particular, note that goog.html functions which return a goog.html type do + * not guarantee the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeStyleSheet#unwrap + * @override + */ +goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeStyleSheet, use + * {@code goog.html.SafeStyleSheet.unwrap}. + * + * @see goog.html.SafeStyleSheet#unwrap + * @override + */ + goog.html.SafeStyleSheet.prototype.toString = function() { + return 'SafeStyleSheet{' + + this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a + * SafeStyleSheet object, and returns its value. + * + * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from. + * @return {string} The safeStyleSheet object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) { + // Perform additional Run-time type-checking to ensure that + // safeStyleSheet is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeStyleSheet instanceof goog.html.SafeStyleSheet && + safeStyleSheet.constructor === goog.html.SafeStyleSheet && + safeStyleSheet + .SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeStyleSheet, got \'' + + safeStyleSheet + '\' of type ' + goog.typeOf(safeStyleSheet)); + return 'type_error:SafeStyleSheet'; + } +}; + + +/** + * Package-internal utility method to create SafeStyleSheet instances. + * + * @param {string} styleSheet The string to initialize the SafeStyleSheet + * object with. + * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object. + * @package + */ +goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse = + function(styleSheet) { + return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_( + styleSheet); +}; + + +/** + * Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This + * method exists only so that the compiler can dead code eliminate static + * fields (like EMPTY) when they're not accessed. + * @param {string} styleSheet + * @return {!goog.html.SafeStyleSheet} + * @private + */ +goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ = + function(styleSheet) { + this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet; + return this; +}; + + +/** + * A SafeStyleSheet instance corresponding to the empty string. + * @const {!goog.html.SafeStyleSheet} + */ +goog.html.SafeStyleSheet.EMPTY = + goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
diff --git a/third_party/ink/closure/html/safeurl.js b/third_party/ink/closure/html/safeurl.js new file mode 100644 index 0000000..3d1ee112 --- /dev/null +++ b/third_party/ink/closure/html/safeurl.js
@@ -0,0 +1,454 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The SafeUrl type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.SafeUrl'); + +goog.require('goog.asserts'); +goog.require('goog.fs.url'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A string that is safe to use in URL context in DOM APIs and HTML documents. + * + * A SafeUrl is a string-like object that carries the security type contract + * that its value as a string will not cause untrusted script execution + * when evaluated as a hyperlink URL in a browser. + * + * Values of this type are guaranteed to be safe to use in URL/hyperlink + * contexts, such as assignment to URL-valued DOM properties, in the sense that + * the use will not result in a Cross-Site-Scripting vulnerability. Similarly, + * SafeUrls can be interpolated into the URL context of an HTML template (e.g., + * inside a href attribute). However, appropriate HTML-escaping must still be + * applied. + * + * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's + * contract does not guarantee that instances are safe to interpolate into HTML + * without appropriate escaping. + * + * Note also that this type's contract does not imply any guarantees regarding + * the resource the URL refers to. In particular, SafeUrls are <b>not</b> + * safe to use in a context where the referred-to resource is interpreted as + * trusted code, e.g., as the src of a script tag. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}), + * etc and not by invoking its constructor. The constructor intentionally + * takes no parameters and the type is immutable; hence only a default instance + * corresponding to the empty string can be obtained via constructor invocation. + * + * @see goog.html.SafeUrl#fromConstant + * @see goog.html.SafeUrl#from + * @see goog.html.SafeUrl#sanitize + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} + */ +goog.html.SafeUrl = function() { + /** + * The contained value of this SafeUrl. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeUrl#unwrap + * @const {!Object} + * @private + */ + this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; + + +/** + * The innocuous string generated by goog.html.SafeUrl.sanitize when passed + * an unsafe URL. + * + * about:invalid is registered in + * http://www.w3.org/TR/css3-values/#about-invalid. + * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to + * contain a fragment, which is not to be considered when determining if an + * about URL is well-known. + * + * Using about:invalid seems preferable to using a fixed data URL, since + * browsers might choose to not report CSP violations on it, as legitimate + * CSS function calls to attr() can result in this URL being produced. It is + * also a standard URL which matches exactly the semantics we need: + * "The about:invalid URI references a non-existent document with a generic + * error condition. It can be used when a URI is necessary, but the default + * value shouldn't be resolveable as any type of document". + * + * @const {string} + */ +goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez'; + + +/** + * @override + * @const + */ +goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true; + + +/** + * Returns this SafeUrl's value a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this + * method. If in doubt, assume that it's security relevant. In particular, note + * that goog.html functions which return a goog.html type do not guarantee that + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof + * // goog.html.SafeHtml. + * </pre> + * + * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the + * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST + * be appropriately escaped before embedding in a HTML document. Note that the + * required escaping is context-sensitive (e.g. a different escaping is + * required for embedding a URL in a style property within a style + * attribute, as opposed to embedding in a href attribute). + * + * @see goog.html.SafeUrl#unwrap + * @override + */ +goog.html.SafeUrl.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +}; + + +/** + * @override + * @const + */ +goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true; + + +/** + * Returns this URLs directionality, which is always {@code LTR}. + * @override + */ +goog.html.SafeUrl.prototype.getDirection = function() { + return goog.i18n.bidi.Dir.LTR; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeUrl, use + * {@code goog.html.SafeUrl.unwrap}. + * + * @see goog.html.SafeUrl#unwrap + * @override + */ + goog.html.SafeUrl.prototype.toString = function() { + return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a SafeUrl + * object, and returns its value. + * + * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the + * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST + * be appropriately escaped before embedding in a HTML document. Note that the + * required escaping is context-sensitive (e.g. a different escaping is + * required for embedding a URL in a style property within a style + * attribute, as opposed to embedding in a href attribute). + * + * @param {!goog.html.SafeUrl} safeUrl The object to extract from. + * @return {string} The SafeUrl object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeUrl.unwrap = function(safeUrl) { + // Perform additional Run-time type-checking to ensure that safeUrl is indeed + // an instance of the expected type. This provides some additional protection + // against security bugs due to application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeUrl instanceof goog.html.SafeUrl && + safeUrl.constructor === goog.html.SafeUrl && + safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeUrl, got \'' + + safeUrl + '\' of type ' + goog.typeOf(safeUrl)); + return 'type_error:SafeUrl'; + } +}; + + +/** + * Creates a SafeUrl object from a compile-time constant string. + * + * Compile-time constant strings are inherently program-controlled and hence + * trusted. + * + * @param {!goog.string.Const} url A compile-time-constant string from which to + * create a SafeUrl. + * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}. + */ +goog.html.SafeUrl.fromConstant = function(url) { + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + goog.string.Const.unwrap(url)); +}; + + +/** + * A pattern that matches Blob or data types that can have SafeUrls created + * from URL.createObjectURL(blob) or via a data: URI. + * @const + * @private + */ +goog.html.SAFE_MIME_TYPE_PATTERN_ = new RegExp( + '^(?:audio/(?:3gpp|3gpp2|aac|midi|mp4|mpeg|ogg|x-m4a|x-wav|webm)|' + + 'image/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|' + + 'text/csv|' + + 'video/(?:mpeg|mp4|ogg|webm))$', + 'i'); + + +/** + * Creates a SafeUrl wrapping a blob URL for the given {@code blob}. + * + * The blob URL is created with {@code URL.createObjectURL}. If the MIME type + * for {@code blob} is not of a known safe audio, image or video MIME type, + * then the SafeUrl will wrap {@link #INNOCUOUS_STRING}. + * + * @see http://www.w3.org/TR/FileAPI/#url + * @param {!Blob} blob + * @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped + * as a SafeUrl. + */ +goog.html.SafeUrl.fromBlob = function(blob) { + var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ? + goog.fs.url.createObjectUrl(blob) : + goog.html.SafeUrl.INNOCUOUS_STRING; + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; + + +/** + * Matches a base-64 data URL, with the first match group being the MIME type. + * @const + * @private + */ +goog.html.DATA_URL_PATTERN_ = /^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i; + + +/** + * Creates a SafeUrl wrapping a data: URL, after validating it matches a + * known-safe audio, image or video MIME type. + * + * @param {string} dataUrl A valid base64 data URL with one of the whitelisted + * audio, image or video MIME types. + * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING} + * wrapped as a SafeUrl if it does not pass. + */ +goog.html.SafeUrl.fromDataUrl = function(dataUrl) { + // There's a slight risk here that a browser sniffs the content type if it + // doesn't know the MIME type and executes HTML within the data: URL. For this + // to cause XSS it would also have to execute the HTML in the same origin + // of the page with the link. It seems unlikely that both of these will + // happen, particularly in not really old IEs. + var match = dataUrl.match(goog.html.DATA_URL_PATTERN_); + var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]); + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING); +}; + + +/** + * Creates a SafeUrl wrapping a tel: URL. + * + * @param {string} telUrl A tel URL. + * @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING} + * wrapped as a SafeUrl if it does not pass. + */ +goog.html.SafeUrl.fromTelUrl = function(telUrl) { + // There's a risk that a tel: URL could immediately place a call once + // clicked, without requiring user confirmation. For that reason it is + // handled in this separate function. + if (!goog.string.caseInsensitiveStartsWith(telUrl, 'tel:')) { + telUrl = goog.html.SafeUrl.INNOCUOUS_STRING; + } + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + telUrl); +}; + + +/** + * Creates a SafeUrl from TrustedResourceUrl. This is safe because + * TrustedResourceUrl is more tightly restricted than SafeUrl. + * + * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl + * @return {!goog.html.SafeUrl} + */ +goog.html.SafeUrl.fromTrustedResourceUrl = function(trustedResourceUrl) { + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + goog.html.TrustedResourceUrl.unwrap(trustedResourceUrl)); +}; + + +/** + * A pattern that recognizes a commonly useful subset of URLs that satisfy + * the SafeUrl contract. + * + * This regular expression matches a subset of URLs that will not cause script + * execution if used in URL context within a HTML document. Specifically, this + * regular expression matches if (comment from here on and regex copied from + * Soy's EscapingConventions): + * (1) Either a protocol in a whitelist (http, https, mailto or ftp). + * (2) or no protocol. A protocol must be followed by a colon. The below + * allows that by allowing colons only after one of the characters [/?#]. + * A colon after a hash (#) must be in the fragment. + * Otherwise, a colon after a (?) must be in a query. + * Otherwise, a colon after a single solidus (/) must be in a path. + * Otherwise, a colon after a double solidus (//) must be in the authority + * (before port). + * + * @private + * @const {!RegExp} + */ +goog.html.SAFE_URL_PATTERN_ = + /^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i; + + +/** + * Creates a SafeUrl object from {@code url}. If {@code url} is a + * goog.html.SafeUrl then it is simply returned. Otherwise the input string is + * validated to match a pattern of commonly used safe URLs. + * + * {@code url} may be a URL with the http, https, mailto or ftp scheme, + * or a relative URL (i.e., a URL without a scheme; specifically, a + * scheme-relative, absolute-path-relative, or path-relative URL). + * + * @see http://url.spec.whatwg.org/#concept-relative-url + * @param {string|!goog.string.TypedString} url The URL to validate. + * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl. + */ +goog.html.SafeUrl.sanitize = function(url) { + if (url instanceof goog.html.SafeUrl) { + return url; + } else if (url.implementsGoogStringTypedString) { + url = url.getTypedStringValue(); + } else { + url = String(url); + } + if (!goog.html.SAFE_URL_PATTERN_.test(url)) { + url = goog.html.SafeUrl.INNOCUOUS_STRING; + } + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; + +/** + * Creates a SafeUrl object from {@code url}. If {@code url} is a + * goog.html.SafeUrl then it is simply returned. Otherwise the input string is + * validated to match a pattern of commonly used safe URLs. + * + * {@code url} may be a URL with the http, https, mailto or ftp scheme, + * or a relative URL (i.e., a URL without a scheme; specifically, a + * scheme-relative, absolute-path-relative, or path-relative URL). + * + * This function asserts (using goog.asserts) that the URL matches this pattern. + * If it does not, in addition to failing the assert, an innocous URL will be + * returned. + * + * @see http://url.spec.whatwg.org/#concept-relative-url + * @param {string|!goog.string.TypedString} url The URL to validate. + * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl. + */ +goog.html.SafeUrl.sanitizeAssertUnchanged = function(url) { + if (url instanceof goog.html.SafeUrl) { + return url; + } else if (url.implementsGoogStringTypedString) { + url = url.getTypedStringValue(); + } else { + url = String(url); + } + if (!goog.asserts.assert(goog.html.SAFE_URL_PATTERN_.test(url))) { + url = goog.html.SafeUrl.INNOCUOUS_STRING; + } + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; + + + +/** + * Type marker for the SafeUrl type, used to implement additional run-time + * type checking. + * @const {!Object} + * @private + */ +goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Package-internal utility method to create SafeUrl instances. + * + * @param {string} url The string to initialize the SafeUrl object with. + * @return {!goog.html.SafeUrl} The initialized SafeUrl object. + * @package + */ +goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function( + url) { + var safeUrl = new goog.html.SafeUrl(); + safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url; + return safeUrl; +}; + + +/** + * A SafeUrl corresponding to the special about:blank url. + * @const {!goog.html.SafeUrl} + */ +goog.html.SafeUrl.ABOUT_BLANK = + goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + 'about:blank');
diff --git a/third_party/ink/closure/html/trustedresourceurl.js b/third_party/ink/closure/html/trustedresourceurl.js new file mode 100644 index 0000000..b7a67bc --- /dev/null +++ b/third_party/ink/closure/html/trustedresourceurl.js
@@ -0,0 +1,412 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview The TrustedResourceUrl type and its builders. + * + * TODO(xtof): Link to document stating type contract. + */ + +goog.provide('goog.html.TrustedResourceUrl'); + +goog.require('goog.asserts'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + + + +/** + * A URL which is under application control and from which script, CSS, and + * other resources that represent executable code, can be fetched. + * + * Given that the URL can only be constructed from strings under application + * control and is used to load resources, bugs resulting in a malformed URL + * should not have a security impact and are likely to be easily detectable + * during testing. Given the wide number of non-RFC compliant URLs in use, + * stricter validation could prevent some applications from being able to use + * this type. + * + * Instances of this type must be created via the factory method, + * ({@code fromConstant}, {@code fromConstants}, {@code format} or {@code + * formatWithParams}), and not by invoking its constructor. The constructor + * intentionally takes no parameters and the type is immutable; hence only a + * default instance corresponding to the empty string can be obtained via + * constructor invocation. + * + * @see goog.html.TrustedResourceUrl#fromConstant + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} + */ +goog.html.TrustedResourceUrl = function() { + /** + * The contained value of this TrustedResourceUrl. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.TrustedResourceUrl#unwrap + * @const {!Object} + * @private + */ + this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; + + +/** + * @override + * @const + */ +goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true; + + +/** + * Returns this TrustedResourceUrl's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code TrustedResourceUrl}, use + * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in + * doubt, assume that it's security relevant. In particular, note that + * goog.html functions which return a goog.html type do not guarantee that + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof + * // goog.html.SafeHtml. + * </pre> + * + * @see goog.html.TrustedResourceUrl#unwrap + * @override + */ +goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; +}; + + +/** + * @override + * @const + */ +goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString = + true; + + +/** + * Returns this URLs directionality, which is always {@code LTR}. + * @override + */ +goog.html.TrustedResourceUrl.prototype.getDirection = function() { + return goog.i18n.bidi.Dir.LTR; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a TrustedResourceUrl, use + * {@code goog.html.TrustedResourceUrl.unwrap}. + * + * @see goog.html.TrustedResourceUrl#unwrap + * @override + */ + goog.html.TrustedResourceUrl.prototype.toString = function() { + return 'TrustedResourceUrl{' + + this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}'; + }; +} + + +/** + * Performs a runtime check that the provided object is indeed a + * TrustedResourceUrl object, and returns its value. + * + * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to + * extract from. + * @return {string} The trustedResourceUrl object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) { + // Perform additional Run-time type-checking to ensure that + // trustedResourceUrl is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl && + trustedResourceUrl.constructor === goog.html.TrustedResourceUrl && + trustedResourceUrl + .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.TrustedResourceUrl + .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return trustedResourceUrl + .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; + } else { + goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' + + trustedResourceUrl + '\' of type ' + goog.typeOf(trustedResourceUrl)); + return 'type_error:TrustedResourceUrl'; + } +}; + + +/** + * Creates a TrustedResourceUrl from a format string and arguments. + * + * The arguments for interpolation into the format string map labels to values. + * Values of type `goog.string.Const` are interpolated without modifcation. + * Values of other types are cast to string and encoded with + * encodeURIComponent. + * + * `%{<label>}` markers are used in the format string to indicate locations + * to be interpolated with the valued mapped to the given label. `<label>` + * must contain only alphanumeric and `_` characters. + * + * The format string must start with one of the following: + * - `https://<origin>/` + * - `//<origin>/` + * - `/<pathStart>` + * - `about:blank` + * + * `<origin>` must contain only alphanumeric or any of the following: `-.:[]`. + * `<pathStart>` is any character except `/` and `\`. + * + * Example usage: + * + * var url = goog.html.TrustedResourceUrl.format(goog.string.Const.from( + * 'https://www.google.com/search?q=%{query}), {'query': searchTerm}); + * + * var url = goog.html.TrustedResourceUrl.format(goog.string.Const.from( + * '//www.youtube.com/v/%{videoId}?hl=en&fs=1%{autoplay}'), { + * 'videoId': videoId, + * 'autoplay': opt_autoplay ? + * goog.string.Const.from('&autoplay=1') : goog.string.Const.EMPTY + * }); + * + * While this function can be used to create a TrustedResourceUrl from only + * constants, fromConstant() and fromConstants() are generally preferable for + * that purpose. + * + * @param {!goog.string.Const} format The format string. + * @param {!Object<string, (string|number|!goog.string.Const)>} args Mapping + * of labels to values to be interpolated into the format string. + * goog.string.Const values are interpolated without encoding. + * @return {!goog.html.TrustedResourceUrl} + * @throws {!Error} On an invalid format string or if a label used in the + * the format string is not present in args. + */ +goog.html.TrustedResourceUrl.format = function(format, args) { + var result = goog.html.TrustedResourceUrl.format_(format, args); + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(result); +}; + + +/** + * String version of TrustedResourceUrl.format. + * @param {!goog.string.Const} format + * @param {!Object<string, (string|number|!goog.string.Const)>} args + * @return {string} + * @throws {!Error} + * @private + */ +goog.html.TrustedResourceUrl.format_ = function(format, args) { + var formatStr = goog.string.Const.unwrap(format); + if (!goog.html.TrustedResourceUrl.BASE_URL_.test(formatStr)) { + throw new Error('Invalid TrustedResourceUrl format: ' + formatStr); + } + return formatStr.replace( + goog.html.TrustedResourceUrl.FORMAT_MARKER_, function(match, id) { + if (!Object.prototype.hasOwnProperty.call(args, id)) { + throw new Error( + 'Found marker, "' + id + '", in format string, "' + formatStr + + '", but no valid label mapping found ' + + 'in args: ' + JSON.stringify(args)); + } + var arg = args[id]; + if (arg instanceof goog.string.Const) { + return goog.string.Const.unwrap(arg); + } else { + return encodeURIComponent(String(arg)); + } + }); +}; + + +/** + * @private @const {!RegExp} + */ +goog.html.TrustedResourceUrl.FORMAT_MARKER_ = /%{(\w+)}/g; + + +/** + * The URL must be absolute, scheme-relative or path-absolute. So it must + * start with: + * - https:// followed by allowed origin characters. + * - // followed by allowed origin characters. + * - / not followed by / or \. There will only be an absolute path. + * + * Based on + * https://url.spec.whatwg.org/commit-snapshots/56b74ce7cca8883eab62e9a12666e2fac665d03d/#url-parsing + * an initial / which is not followed by another / or \ will end up in the "path + * state" and from there it can only go to "fragment state" and "query state". + * + * We don't enforce a well-formed domain name. So '.' or '1.2' are valid. + * That's ok because the origin comes from a compile-time constant. + * + * A regular expression is used instead of goog.uri for several reasons: + * - Strictness. E.g. we don't want any userinfo component and we don't + * want '/./, nor \' in the first path component. + * - Small trusted base. goog.uri is generic and might need to change, + * reasoning about all the ways it can parse a URL now and in the future + * is error-prone. + * - Code size. We expect many calls to .format(), many of which might + * not be using goog.uri. + * - Simplicity. Using goog.uri would likely not result in simpler nor shorter + * code. + * @private @const {!RegExp} + */ +goog.html.TrustedResourceUrl.BASE_URL_ = + /^(?:https:)?\/\/[0-9a-z.:[\]-]+\/|^\/[^\/\\]|^about:blank(#|$)/i; + + +/** + * Formats the URL same as TrustedResourceUrl.format and then adds extra URL + * parameters. + * + * Example usage: + * + * // Creates '//www.youtube.com/v/abc?autoplay=1' for videoId='abc' and + * // opt_autoplay=1. Creates '//www.youtube.com/v/abc' for videoId='abc' + * // and opt_autoplay=undefined. + * var url = goog.html.TrustedResourceUrl.formatWithParams( + * goog.string.Const.from('//www.youtube.com/v/%{videoId}'), + * {'videoId': videoId}, + * {'autoplay': opt_autoplay}); + * + * @param {!goog.string.Const} format The format string. + * @param {!Object<string, (string|number|!goog.string.Const)>} args Mapping + * of labels to values to be interpolated into the format string. + * goog.string.Const values are interpolated without encoding. + * @param {!Object<string, *>} params Parameters to add to URL. Parameters with + * value {@code null} or {@code undefined} are skipped. Both keys and values + * are encoded. If the value is an array then the same parameter is added + * for every element in the array. Note that JavaScript doesn't guarantee + * the order of values in an object which might result in non-deterministic + * order of the parameters. However, browsers currently preserve the order. + * @return {!goog.html.TrustedResourceUrl} + * @throws {!Error} On an invalid format string or if a label used in the + * the format string is not present in args. + */ +goog.html.TrustedResourceUrl.formatWithParams = function(format, args, params) { + var url = goog.html.TrustedResourceUrl.format_(format, args); + var separator = /\?/.test(url) ? '&' : '?'; + for (var key in params) { + var values = goog.isArray(params[key]) ? params[key] : [params[key]]; + for (var i = 0; i < values.length; i++) { + if (values[i] == null) { + continue; + } + url += separator + encodeURIComponent(key) + '=' + + encodeURIComponent(String(values[i])); + separator = '&'; + } + } + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); +}; + + +/** + * Creates a TrustedResourceUrl object from a compile-time constant string. + * + * Compile-time constant strings are inherently program-controlled and hence + * trusted. + * + * @param {!goog.string.Const} url A compile-time-constant string from which to + * create a TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object + * initialized to {@code url}. + */ +goog.html.TrustedResourceUrl.fromConstant = function(url) { + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse( + goog.string.Const.unwrap(url)); +}; + + +/** + * Creates a TrustedResourceUrl object from a compile-time constant strings. + * + * Compile-time constant strings are inherently program-controlled and hence + * trusted. + * + * @param {!Array<!goog.string.Const>} parts Compile-time-constant strings from + * which to create a TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object + * initialized to concatenation of {@code parts}. + */ +goog.html.TrustedResourceUrl.fromConstants = function(parts) { + var unwrapped = ''; + for (var i = 0; i < parts.length; i++) { + unwrapped += goog.string.Const.unwrap(parts[i]); + } + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(unwrapped); +}; + + +/** + * Type marker for the TrustedResourceUrl type, used to implement additional + * run-time type checking. + * @const {!Object} + * @private + */ +goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; + + +/** + * Package-internal utility method to create TrustedResourceUrl instances. + * + * @param {string} url The string to initialize the TrustedResourceUrl object + * with. + * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl + * object. + * @package + */ +goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) { + var trustedResourceUrl = new goog.html.TrustedResourceUrl(); + trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = + url; + return trustedResourceUrl; +};
diff --git a/third_party/ink/closure/html/uncheckedconversions.js b/third_party/ink/closure/html/uncheckedconversions.js new file mode 100644 index 0000000..e75dfa2 --- /dev/null +++ b/third_party/ink/closure/html/uncheckedconversions.js
@@ -0,0 +1,254 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Unchecked conversions to create values of goog.html types from + * plain strings. Use of these functions could potentially result in instances + * of goog.html types that violate their type contracts, and hence result in + * security vulnerabilties. + * + * Therefore, all uses of the methods herein must be carefully security + * reviewed. Avoid use of the methods in this file whenever possible; instead + * prefer to create instances of goog.html types using inherently safe builders + * or template systems. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * MOE:begin_intracomment_strip + * MAINTAINERS: Use of these functions is detected with a Tricorder analyzer. + * If adding functions here also add them to analyzer's list at + * j/c/g/devtools/staticanalysis/pipeline/analyzers/shared/SafeHtmlAnalyzers.java. + * MOE:end_intracomment_strip + * + * @visibility {//javascript/closure/html:approved_for_unchecked_conversion} + * @visibility {//javascript/closure/bin/sizetests:__pkg__} + */ + + +goog.provide('goog.html.uncheckedconversions'); + +goog.require('goog.asserts'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.string'); +goog.require('goog.string.Const'); + + +/** + * Performs an "unchecked conversion" to SafeHtml from a plain string that is + * known to satisfy the SafeHtml type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code html} satisfies the SafeHtml type contract in all + * possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} html A string that is claimed to adhere to the SafeHtml + * contract. + * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the + * SafeHtml to be constructed. A null or undefined value signifies an + * unknown directionality. + * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml + * object. + */ +goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract = + function(justification, html, opt_dir) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + html, opt_dir || null); +}; + + +/** + * Performs an "unchecked conversion" to SafeScript from a plain string that is + * known to satisfy the SafeScript type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code script} satisfies the SafeScript type contract in + * all possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} script The string to wrap as a SafeScript. + * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a + * SafeScript object. + */ +goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract = + function(justification, script) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( + script); +}; + + +/** + * Performs an "unchecked conversion" to SafeStyle from a plain string that is + * known to satisfy the SafeStyle type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code style} satisfies the SafeStyle type contract in all + * possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} style The string to wrap as a SafeStyle. + * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a + * SafeStyle object. + */ +goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract = + function(justification, style) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); +}; + + +/** + * Performs an "unchecked conversion" to SafeStyleSheet from a plain string + * that is known to satisfy the SafeStyleSheet type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code styleSheet} satisfies the SafeStyleSheet type + * contract in all possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} styleSheet The string to wrap as a SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped + * in a SafeStyleSheet object. + */ +goog.html.uncheckedconversions + .safeStyleSheetFromStringKnownToSatisfyTypeContract = function( + justification, styleSheet) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); +}; + + +/** + * Performs an "unchecked conversion" to SafeUrl from a plain string that is + * known to satisfy the SafeUrl type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code url} satisfies the SafeUrl type contract in all + * possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} url The string to wrap as a SafeUrl. + * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl + * object. + */ +goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract = + function(justification, url) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; + + +/** + * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string + * that is known to satisfy the TrustedResourceUrl type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code url} satisfies the TrustedResourceUrl type contract + * in all possible program states. + * + * MOE:begin_intracomment_strip + * See http://go/safehtml-unchecked for guidelines on using these functions. + * MOE:end_intracomment_strip + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} url The string to wrap as a TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in + * a TrustedResourceUrl object. + */ +goog.html.uncheckedconversions + .trustedResourceUrlFromStringKnownToSatisfyTypeContract = function( + justification, url) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString( + goog.string.Const.unwrap(justification), 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); +};
diff --git a/third_party/ink/closure/i18n/bidi.js b/third_party/ink/closure/i18n/bidi.js new file mode 100644 index 0000000..fcf1120 --- /dev/null +++ b/third_party/ink/closure/i18n/bidi.js
@@ -0,0 +1,878 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utility functions for supporting Bidi issues. + * @author shanjian@google.com (Shanjian Li) + * @author dougfelt@google.com (Doug Felt) + */ + + +/** + * Namespace for bidi supporting functions. + */ +goog.provide('goog.i18n.bidi'); +goog.provide('goog.i18n.bidi.Dir'); +goog.provide('goog.i18n.bidi.DirectionalString'); +goog.provide('goog.i18n.bidi.Format'); + + +/** + * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant + * to say that the current locale is a RTL locale. This should only be used + * if you want to override the default behavior for deciding whether the + * current locale is RTL or not. + * + * {@see goog.i18n.bidi.IS_RTL} + */ +goog.define('goog.i18n.bidi.FORCE_RTL', false); + + +/** + * Constant that defines whether or not the current locale is a RTL locale. + * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default + * to check that {@link goog.LOCALE} is one of a few major RTL locales. + * + * <p>This is designed to be a maximally efficient compile-time constant. For + * example, for the default goog.LOCALE, compiling + * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It + * is this design consideration that limits the implementation to only + * supporting a few major RTL locales, as opposed to the broader repertoire of + * something like goog.i18n.bidi.isRtlLanguage. + * + * <p>Since this constant refers to the directionality of the locale, it is up + * to the caller to determine if this constant should also be used for the + * direction of the UI. + * + * {@see goog.LOCALE} + * + * @type {boolean} + * + * TODO(aharon): write a test that checks that this is a compile-time constant. + */ +goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL || + ((goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'he' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') && + (goog.LOCALE.length == 2 || goog.LOCALE.substring(2, 3) == '-' || + goog.LOCALE.substring(2, 3) == '_')) || + (goog.LOCALE.length >= 3 && + goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' && + (goog.LOCALE.length == 3 || goog.LOCALE.substring(3, 4) == '-' || + goog.LOCALE.substring(3, 4) == '_')); + + +/** + * Unicode formatting characters and directionality string constants. + * @enum {string} + */ +goog.i18n.bidi.Format = { + /** Unicode "Left-To-Right Embedding" (LRE) character. */ + LRE: '\u202A', + /** Unicode "Right-To-Left Embedding" (RLE) character. */ + RLE: '\u202B', + /** Unicode "Pop Directional Formatting" (PDF) character. */ + PDF: '\u202C', + /** Unicode "Left-To-Right Mark" (LRM) character. */ + LRM: '\u200E', + /** Unicode "Right-To-Left Mark" (RLM) character. */ + RLM: '\u200F' +}; + + +/** + * Directionality enum. + * @enum {number} + */ +goog.i18n.bidi.Dir = { + /** + * Left-to-right. + */ + LTR: 1, + + /** + * Right-to-left. + */ + RTL: -1, + + /** + * Neither left-to-right nor right-to-left. + */ + NEUTRAL: 0 +}; + + +/** + * 'right' string constant. + * @type {string} + */ +goog.i18n.bidi.RIGHT = 'right'; + + +/** + * 'left' string constant. + * @type {string} + */ +goog.i18n.bidi.LEFT = 'left'; + + +/** + * 'left' if locale is RTL, 'right' if not. + * @type {string} + */ +goog.i18n.bidi.I18N_RIGHT = + goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : goog.i18n.bidi.RIGHT; + + +/** + * 'right' if locale is RTL, 'left' if not. + * @type {string} + */ +goog.i18n.bidi.I18N_LEFT = + goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT; + + +/** + * Convert a directionality given in various formats to a goog.i18n.bidi.Dir + * constant. Useful for interaction with different standards of directionality + * representation. + * + * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given + * in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LTR, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + * @param {boolean=} opt_noNeutral Whether a givenDir of zero or + * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in + * order to preserve legacy behavior. + * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the + * given directionality. If given null, returns null (i.e. unknown). + */ +goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { + if (typeof givenDir == 'number') { + // This includes the non-null goog.i18n.bidi.Dir case. + return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : givenDir < 0 ? + goog.i18n.bidi.Dir.RTL : + opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; + } else if (givenDir == null) { + return null; + } else { + // Must be typeof givenDir == 'boolean'. + return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; + } +}; + + +/** + * A practical pattern to identify strong LTR characters. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified for + * performance and small code size. + * @type {string} + * @private + */ +goog.i18n.bidi.ltrChars_ = + 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + + '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; + + +/** + * A practical pattern to identify strong RTL character. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified + * for performance and small code size. + * @type {string} + * @private + */ +goog.i18n.bidi.rtlChars_ = + '\u0591-\u06EF\u06FA-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; + + +/** + * Simplified regular expression for an HTML tag (opening or closing) or an HTML + * escape. We might want to skip over such expressions when estimating the text + * directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g; + + +/** + * Returns the input text with spaces instead of HTML tags or HTML escapes, if + * opt_isStripNeeded is true. Else returns the input as is. + * Useful for text directionality estimation. + * Note: the function should not be used in other contexts; it is not 100% + * correct, but rather a good-enough implementation for directionality + * estimation purposes. + * @param {string} str The given string. + * @param {boolean=} opt_isStripNeeded Whether to perform the stripping. + * Default: false (to retain consistency with calling functions). + * @return {string} The given string cleaned of HTML tags / escapes. + * @private + */ +goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) { + return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : str; +}; + + +/** + * Regular expression to check for RTL characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Regular expression to check for LTR characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains RTL characters. + */ +goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlCharReg_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the string contains RTL characters. + * @deprecated Use hasAnyRtl. + */ +goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl; + + +/** + * Test whether the given string has any LTR characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains LTR characters. + */ +goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrCharReg_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Regular expression pattern to check if the first character in the string + * is LTR. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Regular expression pattern to check if the first character in the string + * is RTL. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Check if the first character in the string is RTL or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an RTL char. + */ +goog.i18n.bidi.isRtlChar = function(str) { + return goog.i18n.bidi.rtlRe_.test(str); +}; + + +/** + * Check if the first character in the string is LTR or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an LTR char. + */ +goog.i18n.bidi.isLtrChar = function(str) { + return goog.i18n.bidi.ltrRe_.test(str); +}; + + +/** + * Check if the first character in the string is neutral or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is a neutral char. + */ +goog.i18n.bidi.isNeutralChar = function(str) { + return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str); +}; + + +/** + * Regular expressions to check if a piece of text is of LTR directionality + * on first character with strong directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']'); + + +/** + * Regular expressions to check if a piece of text is of RTL directionality + * on first character with strong directionality. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']'); + + +/** + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. + */ +goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithRtl. + */ +goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl; + + +/** + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. + */ +goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithLtr. + */ +goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr; + + +/** + * Regular expression to check if a string looks like something that must + * always be LTR even in RTL text, e.g. a URL. When estimating the + * directionality of text containing these, we treat these as weakly LTR, + * like numbers. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/; + + +/** + * Check whether the input string either contains no strongly directional + * characters or looks like a url. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether neutral directionality is detected. + */ +goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) { + str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml); + return goog.i18n.bidi.isRequiredLtrRe_.test(str) || + !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str); +}; + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is LTR. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$'); + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is RTL. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$'); + + +/** + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + */ +goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + * @deprecated Use endsWithLtr. + */ +goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr; + + +/** + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + */ +goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); +}; + + +/** + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + * @deprecated Use endsWithRtl. + */ +goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl; + + +/** + * A regular expression for matching right-to-left language codes. + * See {@link #isRtlLanguage} for the design. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rtlLocalesRe_ = new RegExp( + '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' + + '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' + + '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)', + 'i'); + + +/** + * Check if a BCP 47 / III language code indicates an RTL language, i.e. either: + * - a language code explicitly specifying one of the right-to-left scripts, + * e.g. "az-Arab", or<p> + * - a language code specifying one of the languages normally written in a + * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying + * Latin or Cyrillic script (which are the usual LTR alternatives).<p> + * The list of right-to-left scripts appears in the 100-199 range in + * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and + * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and + * Tifinagh, which also have significant modern usage. The rest (Syriac, + * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage + * and are not recognized to save on code size. + * The languages usually written in a right-to-left script are taken as those + * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in + * http://www.iana.org/assignments/language-subtag-registry, + * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug). + * Other subtags of the language code, e.g. regions like EG (Egypt), are + * ignored. + * @param {string} lang BCP 47 (a.k.a III) language code. + * @return {boolean} Whether the language code is an RTL language. + */ +goog.i18n.bidi.isRtlLanguage = function(lang) { + return goog.i18n.bidi.rtlLocalesRe_.test(lang); +}; + + +/** + * Regular expression for bracket guard replacement in text. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.bracketGuardTextRe_ = + /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g; + + +/** + * Apply bracket guard using LRM and RLM. This is to address the problem of + * messy bracket display frequently happens in RTL layout. + * This function works for plain text, not for HTML. In HTML, the opening + * bracket might be in a different context than the closing bracket (such as + * an attribute value). + * @param {string} s The string that need to be processed. + * @param {boolean=} opt_isRtlContext specifies default direction (usually + * direction of the UI). + * @return {string} The processed string, with all bracket guarded. + */ +goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) { + var useRtl = opt_isRtlContext === undefined ? goog.i18n.bidi.hasAnyRtl(s) : + opt_isRtlContext; + var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM; + return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark); +}; + + +/** + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. + */ +goog.i18n.bidi.enforceRtlInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=rtl'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=rtl>' + html + '</span>'; +}; + + +/** + * Enforce RTL on both end of the given text piece using unicode BiDi formatting + * characters RLE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. + */ +goog.i18n.bidi.enforceRtlInText = function(text) { + return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF; +}; + + +/** + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. + */ +goog.i18n.bidi.enforceLtrInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=ltr'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=ltr>' + html + '</span>'; +}; + + +/** + * Enforce LTR on both end of the given text piece using unicode BiDi formatting + * characters LRE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. + */ +goog.i18n.bidi.enforceLtrInText = function(text) { + return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF; +}; + + +/** + * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;" + * @type {RegExp} + * @private + */ +goog.i18n.bidi.dimensionsRe_ = + /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g; + + +/** + * Regular expression for left. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.leftRe_ = /left/gi; + + +/** + * Regular expression for right. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.rightRe_ = /right/gi; + + +/** + * Placeholder regular expression for swapping. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.tempRe_ = /%%%%/g; + + +/** + * Swap location parameters and 'left'/'right' in CSS specification. The + * processed string will be suited for RTL layout. Though this function can + * cover most cases, there are always exceptions. It is suggested to put + * those exceptions in separate group of CSS string. + * @param {string} cssStr CSS spefication string. + * @return {string} Processed CSS specification string. + */ +goog.i18n.bidi.mirrorCSS = function(cssStr) { + return cssStr + . + // reverse dimensions + replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2') + .replace(goog.i18n.bidi.leftRe_, '%%%%') + . // swap left and right + replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT) + .replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT); +}; + + +/** + * Regular expression for hebrew double quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g; + + +/** + * Regular expression for hebrew single quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g; + + +/** + * Replace the double and single quote directly after a Hebrew character with + * GERESH and GERSHAYIM. In such case, most likely that's user intention. + * @param {string} str String that need to be processed. + * @return {string} Processed string with double/single quote replaced. + */ +goog.i18n.bidi.normalizeHebrewQuote = function(str) { + return str.replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4') + .replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3'); +}; + + +/** + * Regular expression to split a string into "words" for directionality + * estimation based on relative word counts. + * @type {RegExp} + * @private + */ +goog.i18n.bidi.wordSeparatorRe_ = /\s+/; + + +/** + * Regular expression to check if a string contains any numerals. Used to + * differentiate between completely neutral strings and those containing + * numbers, which are weakly LTR. + * + * Native Arabic digits (\u0660 - \u0669) are not included because although they + * do flow left-to-right inside a number, this is the case even if the overall + * directionality is RTL, and a mathematical expression using these digits is + * supposed to flow right-to-left overall, including unary plus and minus + * appearing to the right of a number, and this does depend on the overall + * directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the + * other hand, are included, since Farsi math (including unary plus and minus) + * does flow left-to-right. + * + * @type {RegExp} + * @private + */ +goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/; + + +/** + * This constant controls threshold of RTL directionality. + * @type {number} + * @private + */ +goog.i18n.bidi.rtlDetectionThreshold_ = 0.40; + + +/** + * Estimates the directionality of a string based on relative word counts. + * If the number of RTL words is above a certain percentage of the total number + * of strongly directional words, returns RTL. + * Otherwise, if any words are strongly or weakly LTR, returns LTR. + * Otherwise, returns UNKNOWN, which is used to mean "neutral". + * Numbers are counted as weakly LTR. + * @param {string} str The string to be checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. + */ +goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { + var rtlCount = 0; + var totalCount = 0; + var hasWeaklyLtr = false; + var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml) + .split(goog.i18n.bidi.wordSeparatorRe_); + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (goog.i18n.bidi.startsWithRtl(token)) { + rtlCount++; + totalCount++; + } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) { + hasWeaklyLtr = true; + } else if (goog.i18n.bidi.hasAnyLtr(token)) { + totalCount++; + } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) { + hasWeaklyLtr = true; + } + } + + return totalCount == 0 ? + (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : + (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ? + goog.i18n.bidi.Dir.RTL : + goog.i18n.bidi.Dir.LTR); +}; + + +/** + * Check the directionality of a piece of text, return true if the piece of + * text should be laid out in RTL direction. + * @param {string} str The piece of text that need to be detected. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether this piece of text should be laid out in RTL. + */ +goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) { + return goog.i18n.bidi.estimateDirection(str, opt_isHtml) == + goog.i18n.bidi.Dir.RTL; +}; + + +/** + * Sets text input element's directionality and text alignment based on a + * given directionality. Does nothing if the given directionality is unknown or + * neutral. + * @param {Element} element Input field element to set directionality to. + * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality, + * given in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LRT, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + */ +goog.i18n.bidi.setElementDirAndAlign = function(element, dir) { + if (element) { + dir = goog.i18n.bidi.toDir(dir); + if (dir) { + element.style.textAlign = dir == goog.i18n.bidi.Dir.RTL ? + goog.i18n.bidi.RIGHT : + goog.i18n.bidi.LEFT; + element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; + } + } +}; + + +/** + * Sets element dir based on estimated directionality of the given text. + * @param {!Element} element + * @param {string} text + */ +goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) { + switch (goog.i18n.bidi.estimateDirection(text)) { + case (goog.i18n.bidi.Dir.LTR): + element.dir = 'ltr'; + break; + case (goog.i18n.bidi.Dir.RTL): + element.dir = 'rtl'; + break; + default: + // Default for no direction, inherit from document. + element.removeAttribute('dir'); + } +}; + + + +/** + * Strings that have an (optional) known direction. + * + * Implementations of this interface are string-like objects that carry an + * attached direction, if known. + * @interface + */ +goog.i18n.bidi.DirectionalString = function() {}; + + +/** + * Interface marker of the DirectionalString interface. + * + * This property can be used to determine at runtime whether or not an object + * implements this interface. All implementations of this interface set this + * property to {@code true}. + * @type {boolean} + */ +goog.i18n.bidi.DirectionalString.prototype + .implementsGoogI18nBidiDirectionalString; + + +/** + * Retrieves this object's known direction (if any). + * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown. + */ +goog.i18n.bidi.DirectionalString.prototype.getDirection;
diff --git a/third_party/ink/closure/i18n/bidiformatter.js b/third_party/ink/closure/i18n/bidiformatter.js new file mode 100644 index 0000000..19bb2dd --- /dev/null +++ b/third_party/ink/closure/i18n/bidiformatter.js
@@ -0,0 +1,556 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utility for formatting text for display in a potentially + * opposite-directionality context without garbling. + * Mostly a port of http://go/formatter.cc. + * @author tomerigo@google.com (Tomer Greenberg) + */ + + +goog.provide('goog.i18n.BidiFormatter'); + +goog.require('goog.html.SafeHtml'); +goog.require('goog.i18n.bidi'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.Format'); + + + +/** + * Utility class for formatting text for display in a potentially + * opposite-directionality context without garbling. Provides the following + * functionality: + * + * 1. BiDi Wrapping + * When text in one language is mixed into a document in another, opposite- + * directionality language, e.g. when an English business name is embedded in a + * Hebrew web page, both the inserted string and the text following it may be + * displayed incorrectly unless the inserted string is explicitly separated + * from the surrounding text in a "wrapper" that declares its directionality at + * the start and then resets it back at the end. This wrapping can be done in + * HTML mark-up (e.g. a 'span dir="rtl"' tag) or - only in contexts where + * mark-up can not be used - in Unicode BiDi formatting codes (LRE|RLE and PDF). + * Providing such wrapping services is the basic purpose of the BiDi formatter. + * + * 2. Directionality estimation + * How does one know whether a string about to be inserted into surrounding + * text has the same directionality? Well, in many cases, one knows that this + * must be the case when writing the code doing the insertion, e.g. when a + * localized message is inserted into a localized page. In such cases there is + * no need to involve the BiDi formatter at all. In the remaining cases, e.g. + * when the string is user-entered or comes from a database, the language of + * the string (and thus its directionality) is not known a priori, and must be + * estimated at run-time. The BiDi formatter does this automatically. + * + * 3. Escaping + * When wrapping plain text - i.e. text that is not already HTML or HTML- + * escaped - in HTML mark-up, the text must first be HTML-escaped to prevent XSS + * attacks and other nasty business. This of course is always true, but the + * escaping can not be done after the string has already been wrapped in + * mark-up, so the BiDi formatter also serves as a last chance and includes + * escaping services. + * + * Thus, in a single call, the formatter will escape the input string as + * specified, determine its directionality, and wrap it as necessary. It is + * then up to the caller to insert the return value in the output. + * + * See http://wiki/Main/TemplatesAndBiDi for more information. + * + * @param {goog.i18n.bidi.Dir|number|boolean|null} contextDir The context + * directionality, in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. NEUTRAL is treated the same as null, + * i.e. unknown, for backward compatibility with legacy calls. + * 2. A number (positive = LTR, negative = RTL, 0 = unknown). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + * @param {boolean=} opt_alwaysSpan Whether {@link #spanWrap} should always + * use a 'span' tag, even when the input directionality is neutral or + * matches the context, so that the DOM structure of the output does not + * depend on the combination of directionalities. Default: false. + * @constructor + * @final + */ +goog.i18n.BidiFormatter = function(contextDir, opt_alwaysSpan) { + /** + * The overall directionality of the context in which the formatter is being + * used. + * @type {?goog.i18n.bidi.Dir} + * @private + */ + this.contextDir_ = goog.i18n.bidi.toDir(contextDir, true /* opt_noNeutral */); + + /** + * Whether {@link #spanWrap} and similar methods should always use the same + * span structure, regardless of the combination of directionalities, for a + * stable DOM structure. + * @type {boolean} + * @private + */ + this.alwaysSpan_ = !!opt_alwaysSpan; +}; + + +/** + * @return {?goog.i18n.bidi.Dir} The context directionality. + */ +goog.i18n.BidiFormatter.prototype.getContextDir = function() { + return this.contextDir_; +}; + + +/** + * @return {boolean} Whether alwaysSpan is set. + */ +goog.i18n.BidiFormatter.prototype.getAlwaysSpan = function() { + return this.alwaysSpan_; +}; + + +/** + * @param {goog.i18n.bidi.Dir|number|boolean|null} contextDir The context + * directionality, in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. NEUTRAL is treated the same as null, + * i.e. unknown. + * 2. A number (positive = LTR, negative = RTL, 0 = unknown). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + */ +goog.i18n.BidiFormatter.prototype.setContextDir = function(contextDir) { + this.contextDir_ = goog.i18n.bidi.toDir(contextDir, true /* opt_noNeutral */); +}; + + +/** + * @param {boolean} alwaysSpan Whether {@link #spanWrap} should always use a + * 'span' tag, even when the input directionality is neutral or matches the + * context, so that the DOM structure of the output does not depend on the + * combination of directionalities. + */ +goog.i18n.BidiFormatter.prototype.setAlwaysSpan = function(alwaysSpan) { + this.alwaysSpan_ = alwaysSpan; +}; + + +/** + * Returns the directionality of input argument {@code str}. + * Identical to {@link goog.i18n.bidi.estimateDirection}. + * + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. + */ +goog.i18n.BidiFormatter.prototype.estimateDirection = + goog.i18n.bidi.estimateDirection; + + +/** + * Returns true if two given directionalities are opposite. + * Note: the implementation is based on the numeric values of the Dir enum. + * + * @param {?goog.i18n.bidi.Dir} dir1 1st directionality. + * @param {?goog.i18n.bidi.Dir} dir2 2nd directionality. + * @return {boolean} Whether the directionalities are opposite. + * @private + */ +goog.i18n.BidiFormatter.prototype.areDirectionalitiesOpposite_ = function( + dir1, dir2) { + return Number(dir1) * Number(dir2) < 0; +}; + + +/** + * Returns a unicode BiDi mark matching the context directionality (LRM or + * RLM) if {@code opt_dirReset}, and if either the directionality or the exit + * directionality of {@code str} is opposite to the context directionality. + * Otherwise returns the empty string. + * + * @param {string} str The input text. + * @param {goog.i18n.bidi.Dir} dir {@code str}'s overall directionality. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @param {boolean=} opt_dirReset Whether to perform the reset. Default: false. + * @return {string} A unicode BiDi mark or the empty string. + * @private + */ +goog.i18n.BidiFormatter.prototype.dirResetIfNeeded_ = function( + str, dir, opt_isHtml, opt_dirReset) { + // endsWithRtl and endsWithLtr are called only if needed (short-circuit). + if (opt_dirReset && + (this.areDirectionalitiesOpposite_(dir, this.contextDir_) || + (this.contextDir_ == goog.i18n.bidi.Dir.LTR && + goog.i18n.bidi.endsWithRtl(str, opt_isHtml)) || + (this.contextDir_ == goog.i18n.bidi.Dir.RTL && + goog.i18n.bidi.endsWithLtr(str, opt_isHtml)))) { + return this.contextDir_ == goog.i18n.bidi.Dir.LTR ? + goog.i18n.bidi.Format.LRM : + goog.i18n.bidi.Format.RLM; + } else { + return ''; + } +}; + + +/** + * Returns "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" if + * it is LTR. In case it's NEUTRAL, returns "rtl" if the context directionality + * is RTL, and "ltr" otherwise. + * Needed for GXP, which can't handle dirAttr. + * Example use case: + * <td expr:dir='bidiFormatter.dirAttrValue(foo)'> + * <gxp:eval expr='foo'> + * </td> + * + * @param {string} str Text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @return {string} "rtl" or "ltr", according to the logic described above. + */ +goog.i18n.BidiFormatter.prototype.dirAttrValue = function(str, opt_isHtml) { + return this.knownDirAttrValue(this.estimateDirection(str, opt_isHtml)); +}; + + +/** + * Returns "rtl" if the given directionality is RTL, and "ltr" if it is LTR. In + * case it's NEUTRAL, returns "rtl" if the context directionality is RTL, and + * "ltr" otherwise. + * + * @param {goog.i18n.bidi.Dir} dir A directionality. + * @return {string} "rtl" or "ltr", according to the logic described above. + */ +goog.i18n.BidiFormatter.prototype.knownDirAttrValue = function(dir) { + var resolvedDir = dir == goog.i18n.bidi.Dir.NEUTRAL ? this.contextDir_ : dir; + return resolvedDir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; +}; + + +/** + * Returns 'dir="ltr"' or 'dir="rtl"', depending on {@code str}'s estimated + * directionality, if it is not the same as the context directionality. + * Otherwise, returns the empty string. + * + * @param {string} str Text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @return {string} 'dir="rtl"' for RTL text in non-RTL context; 'dir="ltr"' for + * LTR text in non-LTR context; else, the empty string. + */ +goog.i18n.BidiFormatter.prototype.dirAttr = function(str, opt_isHtml) { + return this.knownDirAttr(this.estimateDirection(str, opt_isHtml)); +}; + + +/** + * Returns 'dir="ltr"' or 'dir="rtl"', depending on the given directionality, if + * it is not the same as the context directionality. Otherwise, returns the + * empty string. + * + * @param {goog.i18n.bidi.Dir} dir A directionality. + * @return {string} 'dir="rtl"' for RTL text in non-RTL context; 'dir="ltr"' for + * LTR text in non-LTR context; else, the empty string. + */ +goog.i18n.BidiFormatter.prototype.knownDirAttr = function(dir) { + if (dir != this.contextDir_) { + return dir == goog.i18n.bidi.Dir.RTL ? + 'dir="rtl"' : + dir == goog.i18n.bidi.Dir.LTR ? 'dir="ltr"' : ''; + } + return ''; +}; + + +/** + * Formats a string of unknown directionality for use in HTML output of the + * context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * The algorithm: estimates the directionality of input argument {@code html}. + * In case its directionality doesn't match the context directionality, wraps it + * with a 'span' tag and adds a "dir" attribute (either 'dir="rtl"' or + * 'dir="ltr"'). If setAlwaysSpan(true) was used, the input is always wrapped + * with 'span', skipping just the dir attribute when it's not needed. + * + * If {@code opt_dirReset}, and if the overall directionality or the exit + * directionality of {@code str} are opposite to the context directionality, a + * trailing unicode BiDi mark matching the context directionality is appened + * (LRM or RLM). + * + * @param {!goog.html.SafeHtml} html The input HTML. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code html}. Default: true. + * @return {!goog.html.SafeHtml} Input text after applying the processing. + */ +goog.i18n.BidiFormatter.prototype.spanWrapSafeHtml = function( + html, opt_dirReset) { + return this.spanWrapSafeHtmlWithKnownDir(null, html, opt_dirReset); +}; + + +/** + * Formats a string of given directionality for use in HTML output of the + * context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * The algorithm: If {@code dir} doesn't match the context directionality, wraps + * {@code html} with a 'span' tag and adds a "dir" attribute (either 'dir="rtl"' + * or 'dir="ltr"'). If setAlwaysSpan(true) was used, the input is always wrapped + * with 'span', skipping just the dir attribute when it's not needed. + * + * If {@code opt_dirReset}, and if {@code dir} or the exit directionality of + * {@code html} are opposite to the context directionality, a trailing unicode + * BiDi mark matching the context directionality is appened (LRM or RLM). + * + * @param {?goog.i18n.bidi.Dir} dir {@code html}'s overall directionality, or + * null if unknown and needs to be estimated. + * @param {!goog.html.SafeHtml} html The input HTML. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code html}. Default: true. + * @return {!goog.html.SafeHtml} Input text after applying the processing. + */ +goog.i18n.BidiFormatter.prototype.spanWrapSafeHtmlWithKnownDir = function( + dir, html, opt_dirReset) { + if (dir == null) { + dir = this.estimateDirection(goog.html.SafeHtml.unwrap(html), true); + } + return this.spanWrapWithKnownDir_(dir, html, opt_dirReset); +}; + + +/** + * The internal implementation of spanWrapSafeHtmlWithKnownDir for non-null dir, + * to help the compiler optimize. + * + * @param {goog.i18n.bidi.Dir} dir {@code str}'s overall directionality. + * @param {!goog.html.SafeHtml} html The input HTML. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code str}. Default: true. + * @return {!goog.html.SafeHtml} Input text after applying the above processing. + * @private + */ +goog.i18n.BidiFormatter.prototype.spanWrapWithKnownDir_ = function( + dir, html, opt_dirReset) { + opt_dirReset = opt_dirReset || (opt_dirReset == undefined); + + var result; + // Whether to add the "dir" attribute. + var dirCondition = + dir != goog.i18n.bidi.Dir.NEUTRAL && dir != this.contextDir_; + if (this.alwaysSpan_ || dirCondition) { // Wrap is needed + var dirAttribute; + if (dirCondition) { + dirAttribute = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; + } + result = goog.html.SafeHtml.create('span', {'dir': dirAttribute}, html); + } else { + result = html; + } + var str = goog.html.SafeHtml.unwrap(html); + result = goog.html.SafeHtml.concatWithDir( + goog.i18n.bidi.Dir.NEUTRAL, result, + this.dirResetIfNeeded_(str, dir, true, opt_dirReset)); + return result; +}; + + +/** + * Formats a string of unknown directionality for use in plain-text output of + * the context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * As opposed to {@link #spanWrap}, this makes use of unicode BiDi formatting + * characters. In HTML, its *only* valid use is inside of elements that do not + * allow mark-up, e.g. an 'option' tag. + * The algorithm: estimates the directionality of input argument {@code str}. + * In case it doesn't match the context directionality, wraps it with Unicode + * BiDi formatting characters: RLE{@code str}PDF for RTL text, and + * LRE{@code str}PDF for LTR text. + * + * If {@code opt_dirReset}, and if the overall directionality or the exit + * directionality of {@code str} are opposite to the context directionality, a + * trailing unicode BiDi mark matching the context directionality is appended + * (LRM or RLM). + * + * Does *not* do HTML-escaping regardless of the value of {@code opt_isHtml}. + * The return value can be HTML-escaped as necessary. + * + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code str}. Default: true. + * @return {string} Input text after applying the above processing. + */ +goog.i18n.BidiFormatter.prototype.unicodeWrap = function( + str, opt_isHtml, opt_dirReset) { + return this.unicodeWrapWithKnownDir(null, str, opt_isHtml, opt_dirReset); +}; + + +/** + * Formats a string of given directionality for use in plain-text output of the + * context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * As opposed to {@link #spanWrapWithKnownDir}, makes use of unicode BiDi + * formatting characters. In HTML, its *only* valid use is inside of elements + * that do not allow mark-up, e.g. an 'option' tag. + * The algorithm: If {@code dir} doesn't match the context directionality, wraps + * {@code str} with Unicode BiDi formatting characters: RLE{@code str}PDF for + * RTL text, and LRE{@code str}PDF for LTR text. + * + * If {@code opt_dirReset}, and if the overall directionality or the exit + * directionality of {@code str} are opposite to the context directionality, a + * trailing unicode BiDi mark matching the context directionality is appended + * (LRM or RLM). + * + * Does *not* do HTML-escaping regardless of the value of {@code opt_isHtml}. + * The return value can be HTML-escaped as necessary. + * + * @param {?goog.i18n.bidi.Dir} dir {@code str}'s overall directionality, or + * null if unknown and needs to be estimated. + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code str}. Default: true. + * @return {string} Input text after applying the above processing. + */ +goog.i18n.BidiFormatter.prototype.unicodeWrapWithKnownDir = function( + dir, str, opt_isHtml, opt_dirReset) { + if (dir == null) { + dir = this.estimateDirection(str, opt_isHtml); + } + return this.unicodeWrapWithKnownDir_(dir, str, opt_isHtml, opt_dirReset); +}; + + +/** + * The internal implementation of unicodeWrapWithKnownDir for non-null dir, to + * help the compiler optimize. + * + * @param {goog.i18n.bidi.Dir} dir {@code str}'s overall directionality. + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @param {boolean=} opt_dirReset Whether to append a trailing unicode bidi mark + * matching the context directionality, when needed, to prevent the possible + * garbling of whatever may follow {@code str}. Default: true. + * @return {string} Input text after applying the above processing. + * @private + */ +goog.i18n.BidiFormatter.prototype.unicodeWrapWithKnownDir_ = function( + dir, str, opt_isHtml, opt_dirReset) { + opt_dirReset = opt_dirReset || (opt_dirReset == undefined); + var result = []; + if (dir != goog.i18n.bidi.Dir.NEUTRAL && dir != this.contextDir_) { + result.push( + dir == goog.i18n.bidi.Dir.RTL ? goog.i18n.bidi.Format.RLE : + goog.i18n.bidi.Format.LRE); + result.push(str); + result.push(goog.i18n.bidi.Format.PDF); + } else { + result.push(str); + } + + result.push(this.dirResetIfNeeded_(str, dir, opt_isHtml, opt_dirReset)); + return result.join(''); +}; + + +/** + * Returns a Unicode BiDi mark matching the context directionality (LRM or RLM) + * if the directionality or the exit directionality of {@code str} are opposite + * to the context directionality. Otherwise returns the empty string. + * + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching the global directionality or + * the empty string. + */ +goog.i18n.BidiFormatter.prototype.markAfter = function(str, opt_isHtml) { + return this.markAfterKnownDir(null, str, opt_isHtml); +}; + + +/** + * Returns a Unicode BiDi mark matching the context directionality (LRM or RLM) + * if the given directionality or the exit directionality of {@code str} are + * opposite to the context directionality. Otherwise returns the empty string. + * + * @param {?goog.i18n.bidi.Dir} dir {@code str}'s overall directionality, or + * null if unknown and needs to be estimated. + * @param {string} str The input text. + * @param {boolean=} opt_isHtml Whether {@code str} is HTML / HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching the global directionality or + * the empty string. + */ +goog.i18n.BidiFormatter.prototype.markAfterKnownDir = function( + dir, str, opt_isHtml) { + if (dir == null) { + dir = this.estimateDirection(str, opt_isHtml); + } + return this.dirResetIfNeeded_(str, dir, opt_isHtml, true); +}; + + +/** + * Returns the Unicode BiDi mark matching the context directionality (LRM for + * LTR context directionality, RLM for RTL context directionality), or the + * empty string for neutral / unknown context directionality. + * + * @return {string} LRM for LTR context directionality and RLM for RTL context + * directionality. + */ +goog.i18n.BidiFormatter.prototype.mark = function() { + switch (this.contextDir_) { + case (goog.i18n.bidi.Dir.LTR): + return goog.i18n.bidi.Format.LRM; + case (goog.i18n.bidi.Dir.RTL): + return goog.i18n.bidi.Format.RLM; + default: + return ''; + } +}; + + +/** + * Returns 'right' for RTL context directionality. Otherwise (LTR or neutral / + * unknown context directionality) returns 'left'. + * + * @return {string} 'right' for RTL context directionality and 'left' for other + * context directionality. + */ +goog.i18n.BidiFormatter.prototype.startEdge = function() { + return this.contextDir_ == goog.i18n.bidi.Dir.RTL ? goog.i18n.bidi.RIGHT : + goog.i18n.bidi.LEFT; +}; + + +/** + * Returns 'left' for RTL context directionality. Otherwise (LTR or neutral / + * unknown context directionality) returns 'right'. + * + * @return {string} 'left' for RTL context directionality and 'right' for other + * context directionality. + */ +goog.i18n.BidiFormatter.prototype.endEdge = function() { + return this.contextDir_ == goog.i18n.bidi.Dir.RTL ? goog.i18n.bidi.LEFT : + goog.i18n.bidi.RIGHT; +};
diff --git a/third_party/ink/closure/i18n/graphemebreak.js b/third_party/ink/closure/i18n/graphemebreak.js new file mode 100644 index 0000000..ba85f94 --- /dev/null +++ b/third_party/ink/closure/i18n/graphemebreak.js
@@ -0,0 +1,451 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Detect Grapheme Cluster Break in a pair of codepoints. Follows + * Unicode 10 UAX#29. Tailoring for Virama × Indic Letters is used. + * + * Reference: http://unicode.org/reports/tr29 + * + * @author cibu@google.com (Cibu Johny) + * @author fabalbon@google.com (Felipe Balbontin) + */ + +goog.provide('goog.i18n.GraphemeBreak'); + +goog.require('goog.asserts'); +goog.require('goog.i18n.uChar'); +goog.require('goog.structs.InversionMap'); + +/** + * Enum for all Grapheme Cluster Break properties. + * These enums directly corresponds to Grapheme_Cluster_Break property values + * mentioned in http://unicode.org/reports/tr29 table 2. VIRAMA and + * INDIC_LETTER are for the Virama × Base tailoring mentioned in the notes. + * + * @protected @enum {number} + */ +goog.i18n.GraphemeBreak.property = { + OTHER: 0, + CONTROL: 1, + EXTEND: 2, + PREPEND: 3, + SPACING_MARK: 4, + INDIC_LETTER: 5, + VIRAMA: 6, + L: 7, + V: 8, + T: 9, + LV: 10, + LVT: 11, + CR: 12, + LF: 13, + REGIONAL_INDICATOR: 14, + ZWJ: 15, + E_BASE: 16, + GLUE_AFTER_ZWJ: 17, + E_MODIFIER: 18, + E_BASE_GAZ: 19 +}; + + +/** + * Grapheme Cluster Break property values for all codepoints as inversion map. + * Constructed lazily. + * + * @private {?goog.structs.InversionMap} + */ +goog.i18n.GraphemeBreak.inversions_ = null; + + +/** + * Indicates if a and b form a grapheme cluster. + * + * This implements the rules in: + * http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules + * + * @param {number|string} a Code point or string with the first side of + * grapheme cluster. + * @param {number|string} b Code point or string with the second side of + * grapheme cluster. + * @param {boolean} extended If true, indicates extended grapheme cluster; + * If false, indicates legacy cluster. + * @return {boolean} True if a & b do not form a cluster; False otherwise. + * @private + */ +goog.i18n.GraphemeBreak.applyBreakRules_ = function(a, b, extended) { + var prop = goog.i18n.GraphemeBreak.property; + + var aCode = goog.isString(a) ? + goog.i18n.GraphemeBreak.getCodePoint_(a, a.length - 1) : + a; + var bCode = + goog.isString(b) ? goog.i18n.GraphemeBreak.getCodePoint_(b, 0) : b; + + var aProp = goog.i18n.GraphemeBreak.getBreakProp_(aCode); + var bProp = goog.i18n.GraphemeBreak.getBreakProp_(bCode); + + var isString = goog.isString(a); + + // GB3. + if (aProp === prop.CR && bProp === prop.LF) { + return false; + } + + // GB4. + if (aProp === prop.CONTROL || aProp === prop.CR || aProp === prop.LF) { + return true; + } + + // GB5. + if (bProp === prop.CONTROL || bProp === prop.CR || bProp === prop.LF) { + return true; + } + + // GB6. + if (aProp === prop.L && + (bProp === prop.L || bProp === prop.V || bProp === prop.LV || + bProp === prop.LVT)) { + return false; + } + + // GB7. + if ((aProp === prop.LV || aProp === prop.V) && + (bProp === prop.V || bProp === prop.T)) { + return false; + } + + // GB8. + if ((aProp === prop.LVT || aProp === prop.T) && bProp === prop.T) { + return false; + } + + // GB9. + if (bProp === prop.EXTEND || bProp === prop.ZWJ || bProp === prop.VIRAMA) { + return false; + } + + // GB9a, GB9b. + if (extended && (aProp === prop.PREPEND || bProp === prop.SPACING_MARK)) { + return false; + } + + // Tailorings for basic aksara support. + if (extended && aProp === prop.VIRAMA && bProp === prop.INDIC_LETTER) { + return false; + } + + var aStr, index, codePoint, codePointProp; + + // GB10. + if (isString) { + if (bProp === prop.E_MODIFIER) { + // If using new API, consume the string's code points starting from the + // end and test the left side of: (E_Base | EBG) Extend* × E_Modifier. + aStr = /** @type {string} */ (a); + index = aStr.length - 1; + codePoint = aCode; + codePointProp = aProp; + while (index > 0 && codePointProp === prop.EXTEND) { + index -= goog.i18n.uChar.charCount(codePoint); + codePoint = goog.i18n.GraphemeBreak.getCodePoint_(aStr, index); + codePointProp = goog.i18n.GraphemeBreak.getBreakProp_(codePoint); + } + if (codePointProp === prop.E_BASE || codePointProp === prop.E_BASE_GAZ) { + return false; + } + } + } else { + // If using legacy API, return best effort by testing: + // (E_Base | EBG) × E_Modifier. + if ((aProp === prop.E_BASE || aProp === prop.E_BASE_GAZ) && + bProp === prop.E_MODIFIER) { + return false; + } + } + + // GB11. + if (aProp === prop.ZWJ && + (bProp === prop.GLUE_AFTER_ZWJ || bProp === prop.E_BASE_GAZ)) { + return false; + } + + // GB12, GB13. + if (isString) { + if (bProp === prop.REGIONAL_INDICATOR) { + // If using new API, consume the string's code points starting from the + // end and test the left side of these rules: + // - sot (RI RI)* RI × RI + // - [^RI] (RI RI)* RI × RI. + var numberOfRi = 0; + aStr = /** @type {string} */ (a); + index = aStr.length - 1; + codePoint = aCode; + codePointProp = aProp; + while (index > 0 && codePointProp === prop.REGIONAL_INDICATOR) { + numberOfRi++; + index -= goog.i18n.uChar.charCount(codePoint); + codePoint = goog.i18n.GraphemeBreak.getCodePoint_(aStr, index); + codePointProp = goog.i18n.GraphemeBreak.getBreakProp_(codePoint); + } + if (codePointProp === prop.REGIONAL_INDICATOR) { + numberOfRi++; + } + if (numberOfRi % 2 === 1) { + return false; + } + } + } else { + // If using legacy API, return best effort by testing: RI × RI. + if (aProp === prop.REGIONAL_INDICATOR && + bProp === prop.REGIONAL_INDICATOR) { + return false; + } + } + + // GB999. + return true; +}; + + +/** + * Method to return property enum value of the code point. If it is Hangul LV or + * LVT, then it is computed; for the rest it is picked from the inversion map. + * + * @param {number} codePoint The code point value of the character. + * @return {number} Property enum value of code point. + * @private + */ +goog.i18n.GraphemeBreak.getBreakProp_ = function(codePoint) { + if (0xAC00 <= codePoint && codePoint <= 0xD7A3) { + var prop = goog.i18n.GraphemeBreak.property; + if (codePoint % 0x1C === 0x10) { + return prop.LV; + } + return prop.LVT; + } else { + if (!goog.i18n.GraphemeBreak.inversions_) { + goog.i18n.GraphemeBreak.inversions_ = new goog.structs.InversionMap( + [ + 0, 10, 1, 2, 1, 18, 95, 33, 13, 1, + 594, 112, 275, 7, 263, 45, 1, 1, 1, 2, + 1, 2, 1, 1, 56, 6, 10, 11, 1, 1, + 46, 21, 16, 1, 101, 7, 1, 1, 6, 2, + 2, 1, 4, 33, 1, 1, 1, 30, 27, 91, + 11, 58, 9, 34, 4, 1, 9, 1, 3, 1, + 5, 43, 3, 120, 14, 1, 32, 1, 17, 37, + 1, 1, 1, 1, 3, 8, 4, 1, 2, 1, + 7, 8, 2, 2, 21, 7, 1, 1, 2, 17, + 39, 1, 1, 1, 2, 6, 6, 1, 9, 5, + 4, 2, 2, 12, 2, 15, 2, 1, 17, 39, + 2, 3, 12, 4, 8, 6, 17, 2, 3, 14, + 1, 17, 39, 1, 1, 3, 8, 4, 1, 20, + 2, 29, 1, 2, 17, 39, 1, 1, 2, 1, + 6, 6, 9, 6, 4, 2, 2, 13, 1, 16, + 1, 18, 41, 1, 1, 1, 12, 1, 9, 1, + 40, 1, 3, 17, 31, 1, 5, 4, 3, 5, + 7, 8, 3, 2, 8, 2, 29, 1, 2, 17, + 39, 1, 1, 1, 1, 2, 1, 3, 1, 5, + 1, 8, 9, 1, 3, 2, 29, 1, 2, 17, + 38, 3, 1, 2, 5, 7, 1, 1, 8, 1, + 10, 2, 30, 2, 22, 48, 5, 1, 2, 6, + 7, 1, 18, 2, 13, 46, 2, 1, 1, 1, + 6, 1, 12, 8, 50, 46, 2, 1, 1, 1, + 9, 11, 6, 14, 2, 58, 2, 27, 1, 1, + 1, 1, 1, 4, 2, 49, 14, 1, 4, 1, + 1, 2, 5, 48, 9, 1, 57, 33, 12, 4, + 1, 6, 1, 2, 2, 2, 1, 16, 2, 4, + 2, 2, 4, 3, 1, 3, 2, 7, 3, 4, + 13, 1, 1, 1, 2, 6, 1, 1, 14, 1, + 98, 96, 72, 88, 349, 3, 931, 15, 2, 1, + 14, 15, 2, 1, 14, 15, 2, 15, 15, 14, + 35, 17, 2, 1, 7, 8, 1, 2, 9, 1, + 1, 9, 1, 45, 3, 1, 118, 2, 34, 1, + 87, 28, 3, 3, 4, 2, 9, 1, 6, 3, + 20, 19, 29, 44, 84, 23, 2, 2, 1, 4, + 45, 6, 2, 1, 1, 1, 8, 1, 1, 1, + 2, 8, 6, 13, 48, 84, 1, 14, 33, 1, + 1, 5, 1, 1, 5, 1, 1, 1, 7, 31, + 9, 12, 2, 1, 7, 23, 1, 4, 2, 2, + 2, 2, 2, 11, 3, 2, 36, 2, 1, 1, + 2, 3, 1, 1, 3, 2, 12, 36, 8, 8, + 2, 2, 21, 3, 128, 3, 1, 13, 1, 7, + 4, 1, 4, 2, 1, 3, 2, 198, 64, 523, + 1, 1, 1, 2, 24, 7, 49, 16, 96, 33, + 1324, 1, 34, 1, 1, 1, 82, 2, 98, 1, + 14, 1, 1, 4, 86, 1, 1418, 3, 141, 1, + 96, 32, 554, 6, 105, 2, 30164, 4, 1, 10, + 32, 2, 80, 2, 272, 1, 3, 1, 4, 1, + 23, 2, 2, 1, 24, 30, 4, 4, 3, 8, + 1, 1, 13, 2, 16, 34, 16, 1, 1, 26, + 18, 24, 24, 4, 8, 2, 23, 11, 1, 1, + 12, 32, 3, 1, 5, 3, 3, 36, 1, 2, + 4, 2, 1, 3, 1, 36, 1, 32, 35, 6, + 2, 2, 2, 2, 12, 1, 8, 1, 1, 18, + 16, 1, 3, 6, 1, 1, 1, 3, 48, 1, + 1, 3, 2, 2, 5, 2, 1, 1, 32, 9, + 1, 2, 2, 5, 1, 1, 201, 14, 2, 1, + 1, 9, 8, 2, 1, 2, 1, 2, 1, 1, + 1, 18, 11184, 27, 49, 1028, 1024, 6942, 1, 737, + 16, 16, 16, 207, 1, 158, 2, 89, 3, 513, + 1, 226, 1, 149, 5, 1670, 15, 40, 7, 1, + 165, 2, 1305, 1, 1, 1, 53, 14, 1, 56, + 1, 2, 1, 45, 3, 4, 2, 1, 1, 2, + 1, 66, 3, 36, 5, 1, 6, 2, 62, 1, + 12, 2, 1, 48, 3, 9, 1, 1, 1, 2, + 6, 3, 95, 3, 3, 2, 1, 1, 2, 6, + 1, 160, 1, 3, 7, 1, 21, 2, 2, 56, + 1, 1, 1, 1, 1, 12, 1, 9, 1, 10, + 4, 15, 192, 3, 8, 2, 1, 2, 1, 1, + 105, 1, 2, 6, 1, 1, 2, 1, 1, 2, + 1, 1, 1, 235, 1, 2, 6, 4, 2, 1, + 1, 1, 27, 2, 82, 3, 8, 2, 1, 1, + 1, 1, 106, 1, 1, 1, 2, 6, 1, 1, + 101, 3, 2, 4, 1, 4, 1, 1283, 1, 14, + 1, 1, 82, 23, 1, 7, 1, 2, 1, 2, + 20025, 5, 59, 7, 1050, 62, 4, 19722, 2, 1, + 4, 5313, 1, 1, 3, 3, 1, 5, 8, 8, + 2, 7, 30, 4, 148, 3, 1979, 55, 4, 50, + 8, 1, 14, 1, 22, 1424, 2213, 7, 109, 7, + 2203, 26, 264, 1, 53, 1, 52, 1, 17, 1, + 13, 1, 16, 1, 3, 1, 25, 3, 2, 1, + 2, 3, 30, 1, 1, 1, 13, 5, 66, 2, + 2, 11, 21, 4, 4, 1, 1, 9, 3, 1, + 4, 3, 1, 3, 3, 1, 30, 1, 16, 2, + 106, 1, 4, 1, 71, 2, 4, 1, 21, 1, + 4, 2, 81, 1, 92, 3, 3, 5, 48, 1, + 17, 1, 16, 1, 16, 3, 9, 1, 11, 1, + 587, 5, 1, 1, 7, 1, 9, 10, 3, 2, + 788162, 31 + ], + [ + 1, 13, 1, 12, 1, 0, 1, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 3, 0, 2, 0, 1, 0, 2, 0, 2, 0, 2, 3, + 0, 2, 0, 2, 0, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 3, 2, 4, 0, 5, 2, 4, 2, + 0, 4, 2, 4, 6, 4, 0, 2, 5, 0, 2, 0, 5, 0, 2, 4, 0, 5, + 2, 0, 2, 4, 2, 4, 6, 0, 2, 5, 0, 2, 0, 5, 0, 2, 4, 0, + 5, 2, 4, 2, 6, 2, 5, 0, 2, 0, 2, 4, 0, 5, 2, 0, 4, 2, + 4, 6, 0, 2, 0, 2, 4, 0, 5, 2, 0, 2, 4, 2, 4, 6, 2, 5, + 0, 2, 0, 5, 0, 2, 0, 5, 2, 4, 2, 4, 6, 0, 2, 0, 2, 4, + 0, 5, 0, 5, 0, 2, 4, 2, 6, 2, 5, 0, 2, 0, 2, 4, 0, 5, + 2, 0, 4, 2, 4, 2, 4, 2, 4, 2, 6, 2, 5, 0, 2, 0, 2, 4, + 0, 5, 0, 2, 4, 2, 4, 6, 3, 0, 2, 0, 2, 0, 4, 0, 5, 6, + 2, 4, 2, 4, 2, 0, 4, 0, 5, 0, 2, 0, 4, 2, 6, 0, 2, 0, + 5, 0, 2, 0, 4, 2, 0, 2, 0, 5, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 4, 5, 2, 4, 2, 6, 0, 2, 0, 2, 0, 2, 0, 5, 0, 2, 4, + 2, 0, 6, 4, 2, 5, 0, 5, 0, 4, 2, 5, 2, 5, 0, 5, 0, 5, + 2, 5, 2, 0, 4, 2, 0, 2, 5, 0, 2, 0, 7, 8, 9, 0, 2, 0, + 5, 2, 6, 0, 5, 2, 6, 0, 5, 2, 0, 5, 2, 5, 0, 2, 4, 2, + 4, 2, 4, 2, 6, 2, 0, 2, 0, 2, 1, 0, 2, 0, 2, 0, 5, 0, + 2, 4, 2, 4, 2, 4, 2, 0, 5, 0, 5, 0, 5, 2, 4, 2, 0, 5, + 0, 5, 4, 2, 4, 2, 6, 0, 2, 0, 2, 4, 2, 0, 2, 4, 0, 5, + 2, 4, 2, 4, 2, 4, 2, 4, 6, 5, 0, 2, 0, 2, 4, 0, 5, 4, + 2, 4, 2, 6, 2, 5, 0, 5, 0, 5, 0, 2, 4, 2, 4, 2, 4, 2, + 6, 0, 5, 4, 2, 4, 2, 0, 5, 0, 2, 0, 2, 4, 2, 0, 2, 0, + 4, 2, 0, 2, 0, 2, 0, 1, 2, 15, 1, 0, 1, 0, 1, 0, 2, 0, + 16, 0, 17, 0, 17, 0, 17, 0, 16, 0, 17, 0, 16, 0, 17, 0, 2, 0, + 6, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 6, 5, 2, 5, 4, 2, 4, 0, 5, 0, 5, 0, 5, 0, 5, 0, 4, 0, + 5, 4, 6, 2, 0, 2, 0, 5, 0, 2, 0, 5, 2, 4, 6, 0, 7, 2, + 4, 0, 5, 0, 5, 2, 4, 2, 4, 2, 4, 6, 0, 2, 0, 5, 2, 4, + 2, 4, 2, 0, 2, 0, 2, 4, 0, 5, 0, 5, 0, 5, 0, 2, 0, 5, + 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 5, 4, 2, 4, 0, 4, 6, 0, + 5, 0, 5, 0, 5, 0, 4, 2, 4, 2, 4, 0, 4, 6, 0, 11, 8, 9, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 2, 0, 1, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 6, 0, 2, 0, 4, 2, 4, 0, 2, 6, 0, 6, + 2, 4, 0, 4, 2, 4, 6, 2, 0, 3, 0, 2, 0, 2, 4, 2, 6, 0, + 2, 0, 2, 4, 0, 4, 2, 4, 6, 0, 3, 0, 2, 0, 4, 2, 4, 2, + 6, 2, 0, 2, 0, 2, 4, 2, 6, 0, 2, 4, 0, 2, 0, 2, 4, 2, + 4, 6, 0, 2, 0, 4, 2, 0, 4, 2, 4, 6, 2, 4, 2, 0, 2, 4, + 2, 4, 2, 4, 2, 4, 2, 4, 6, 2, 0, 2, 4, 2, 4, 2, 4, 6, + 2, 0, 2, 0, 4, 2, 4, 2, 4, 6, 2, 0, 2, 4, 2, 4, 2, 6, + 2, 0, 2, 4, 2, 4, 2, 6, 0, 4, 2, 4, 6, 0, 2, 4, 2, 4, + 2, 4, 2, 0, 2, 0, 2, 0, 4, 2, 0, 2, 0, 1, 0, 2, 4, 2, + 0, 4, 2, 1, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 2, 0, 14, 0, 17, 0, 17, 0, 17, 0, 16, 0, + 17, 0, 17, 0, 17, 0, 16, 0, 16, 0, 16, 0, 17, 0, 17, 0, 18, 0, + 16, 0, 16, 0, 19, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 17, 0, + 16, 0, 17, 0, 17, 0, 17, 0, 16, 0, 16, 0, 16, 0, 16, 0, 17, 0, + 16, 0, 16, 0, 17, 0, 17, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, + 16, 0, 16, 0, 16, 0, 16, 0, 1, 2 + ], + true); + } + return /** @type {number} */ ( + goog.i18n.GraphemeBreak.inversions_.at(codePoint)); + } +}; + +/** + * Extracts a code point from a string at the specified index. + * + * @param {string} str + * @param {number} index + * @return {number} Extracted code point. + * @private + */ +goog.i18n.GraphemeBreak.getCodePoint_ = function(str, index) { + var codePoint = goog.i18n.uChar.getCodePointAround(str, index); + return (codePoint < 0) ? -codePoint : codePoint; +}; + +/** + * Indicates if there is a grapheme cluster boundary between a and b. + * + * Legacy function. Does not cover cases where a sequence of code points is + * required in order to decide if there is a grapheme cluster boundary, such as + * emoji modifier sequences and emoji flag sequences. To cover all cases please + * use {@code hasGraphemeBreakStrings}. + * + * There are two kinds of grapheme clusters: 1) Legacy 2) Extended. This method + * is to check for both using a boolean flag to switch between them. If no flag + * is provided rules for the extended clusters will be used by default. + * + * @param {number} a The code point value of the first character. + * @param {number} b The code point value of the second character. + * @param {boolean=} opt_extended If true, indicates extended grapheme cluster; + * If false, indicates legacy cluster. Default value is true. + * @return {boolean} True if there is a grapheme cluster boundary between + * a and b; False otherwise. + */ +goog.i18n.GraphemeBreak.hasGraphemeBreak = function(a, b, opt_extended) { + return goog.i18n.GraphemeBreak.applyBreakRules_(a, b, opt_extended !== false); +}; + +/** + * Indicates if there is a grapheme cluster boundary between a and b. + * + * There are two kinds of grapheme clusters: 1) Legacy 2) Extended. This method + * is to check for both using a boolean flag to switch between them. If no flag + * is provided rules for the extended clusters will be used by default. + * + * @param {string} a String with the first sequence of characters. + * @param {string} b String with the second sequence of characters. + * @param {boolean=} opt_extended If true, indicates extended grapheme cluster; + * If false, indicates legacy cluster. Default value is true. + * @return {boolean} True if there is a grapheme cluster boundary between + * a and b; False otherwise. + */ +goog.i18n.GraphemeBreak.hasGraphemeBreakStrings = function(a, b, opt_extended) { + goog.asserts.assert(goog.isDef(a), 'First string should be defined.'); + goog.asserts.assert(goog.isDef(b), 'Second string should be defined.'); + + // Break if any of the strings is empty. + if (a.length === 0 || b.length === 0) { + return true; + } + + return goog.i18n.GraphemeBreak.applyBreakRules_(a, b, opt_extended !== false); +};
diff --git a/third_party/ink/closure/i18n/uchar.js b/third_party/ink/closure/i18n/uchar.js new file mode 100644 index 0000000..6c22c40 --- /dev/null +++ b/third_party/ink/closure/i18n/uchar.js
@@ -0,0 +1,294 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Collection of utility functions for Unicode character. + * + * @author cibu@google.com (Cibu Johny) + * @author burakov@google.com (Danny Burakov) + */ + +goog.provide('goog.i18n.uChar'); + + +// Constants for handling Unicode supplementary characters (surrogate pairs). + + +/** + * The minimum value for Supplementary code points. + * @type {number} + * @private + */ +goog.i18n.uChar.SUPPLEMENTARY_CODE_POINT_MIN_VALUE_ = 0x10000; + + +/** + * The highest Unicode code point value (scalar value) according to the Unicode + * Standard. + * @type {number} + * @private + */ +goog.i18n.uChar.CODE_POINT_MAX_VALUE_ = 0x10FFFF; + + +/** + * Lead surrogate minimum value. + * @type {number} + * @private + */ +goog.i18n.uChar.LEAD_SURROGATE_MIN_VALUE_ = 0xD800; + + +/** + * Lead surrogate maximum value. + * @type {number} + * @private + */ +goog.i18n.uChar.LEAD_SURROGATE_MAX_VALUE_ = 0xDBFF; + + +/** + * Trail surrogate minimum value. + * @type {number} + * @private + */ +goog.i18n.uChar.TRAIL_SURROGATE_MIN_VALUE_ = 0xDC00; + + +/** + * Trail surrogate maximum value. + * @type {number} + * @private + */ +goog.i18n.uChar.TRAIL_SURROGATE_MAX_VALUE_ = 0xDFFF; + + +/** + * The number of least significant bits of a supplementary code point that in + * UTF-16 become the least significant bits of the trail surrogate. The rest of + * the in-use bits of the supplementary code point become the least significant + * bits of the lead surrogate. + * @type {number} + * @private + */ +goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_ = 10; + + +/** + * Gets the U+ notation string of a Unicode character. Ex: 'U+0041' for 'A'. + * @param {string} ch The given character. + * @return {string} The U+ notation of the given character. + */ +goog.i18n.uChar.toHexString = function(ch) { + var chCode = goog.i18n.uChar.toCharCode(ch); + var chCodeStr = 'U+' + + goog.i18n.uChar.padString_(chCode.toString(16).toUpperCase(), 4, '0'); + + return chCodeStr; +}; + + +/** + * Gets a string padded with given character to get given size. + * @param {string} str The given string to be padded. + * @param {number} length The target size of the string. + * @param {string} ch The character to be padded with. + * @return {string} The padded string. + * @private + */ +goog.i18n.uChar.padString_ = function(str, length, ch) { + while (str.length < length) { + str = ch + str; + } + return str; +}; + + +/** + * Gets Unicode value of the given character. + * @param {string} ch The given character, which in the case of a supplementary + * character is actually a surrogate pair. The remainder of the string is + * ignored. + * @return {number} The Unicode value of the character. + */ +goog.i18n.uChar.toCharCode = function(ch) { + return goog.i18n.uChar.getCodePointAround(ch, 0); +}; + + +/** + * Gets a character from the given Unicode value. If the given code point is not + * a valid Unicode code point, null is returned. + * @param {number} code The Unicode value of the character. + * @return {?string} The character corresponding to the given Unicode value. + */ +goog.i18n.uChar.fromCharCode = function(code) { + if (!goog.isDefAndNotNull(code) || + !(code >= 0 && code <= goog.i18n.uChar.CODE_POINT_MAX_VALUE_)) { + return null; + } + if (goog.i18n.uChar.isSupplementaryCodePoint(code)) { + // First, we split the code point into the trail surrogate part (the + // TRAIL_SURROGATE_BIT_COUNT_ least significant bits) and the lead surrogate + // part (the rest of the bits, shifted down; note that for now this includes + // the supplementary offset, also shifted down, to be subtracted off below). + var leadBits = code >> goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_; + var trailBits = code & + // A bit-mask to get the TRAIL_SURROGATE_BIT_COUNT_ (i.e. 10) least + // significant bits. 1 << 10 = 0x0400. 0x0400 - 1 = 0x03FF. + ((1 << goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_) - 1); + + // Now we calculate the code point of each surrogate by adding each offset + // to the corresponding base code point. + var leadCodePoint = leadBits + + (goog.i18n.uChar.LEAD_SURROGATE_MIN_VALUE_ - + // Subtract off the supplementary offset, which had been shifted down + // with the rest of leadBits. We do this here instead of before the + // shift in order to save a separate subtraction step. + (goog.i18n.uChar.SUPPLEMENTARY_CODE_POINT_MIN_VALUE_ >> + goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_)); + var trailCodePoint = trailBits + goog.i18n.uChar.TRAIL_SURROGATE_MIN_VALUE_; + + // Convert the code points into a 2-character long string. + return String.fromCharCode(leadCodePoint) + + String.fromCharCode(trailCodePoint); + } + return String.fromCharCode(code); +}; + + +/** + * Returns the Unicode code point at the specified index. + * + * If the char value specified at the given index is in the leading-surrogate + * range, and the following index is less than the length of {@code string}, and + * the char value at the following index is in the trailing-surrogate range, + * then the supplementary code point corresponding to this surrogate pair is + * returned. + * + * If the char value specified at the given index is in the trailing-surrogate + * range, and the preceding index is not before the start of {@code string}, and + * the char value at the preceding index is in the leading-surrogate range, then + * the negated supplementary code point corresponding to this surrogate pair is + * returned. + * + * The negation allows the caller to differentiate between the case where the + * given index is at the leading surrogate and the one where it is at the + * trailing surrogate, and thus deduce where the next character starts and + * preceding character ends. + * + * Otherwise, the char value at the given index is returned. Thus, a leading + * surrogate is returned when it is not followed by a trailing surrogate, and a + * trailing surrogate is returned when it is not preceded by a leading + * surrogate. + * + * @param {string} string The string. + * @param {number} index The index from which the code point is to be retrieved. + * @return {number} The code point at the given index. If the given index is + * that of the start (i.e. lead surrogate) of a surrogate pair, returns the code + * point encoded by the pair. If the given index is that of the end (i.e. trail + * surrogate) of a surrogate pair, returns the negated code pointed encoded by + * the pair. + */ +goog.i18n.uChar.getCodePointAround = function(string, index) { + var charCode = string.charCodeAt(index); + if (goog.i18n.uChar.isLeadSurrogateCodePoint(charCode) && + index + 1 < string.length) { + var trail = string.charCodeAt(index + 1); + if (goog.i18n.uChar.isTrailSurrogateCodePoint(trail)) { + // Part of a surrogate pair. + return /** @type {number} */ ( + goog.i18n.uChar.buildSupplementaryCodePoint(charCode, trail)); + } + } else if (goog.i18n.uChar.isTrailSurrogateCodePoint(charCode) && index > 0) { + var lead = string.charCodeAt(index - 1); + if (goog.i18n.uChar.isLeadSurrogateCodePoint(lead)) { + // Part of a surrogate pair. + return /** @type {number} */ ( + -goog.i18n.uChar.buildSupplementaryCodePoint(lead, charCode)); + } + } + return charCode; +}; + + +/** + * Determines the length of the string needed to represent the specified + * Unicode code point. + * @param {number} codePoint + * @return {number} 2 if codePoint is a supplementary character, 1 otherwise. + */ +goog.i18n.uChar.charCount = function(codePoint) { + return goog.i18n.uChar.isSupplementaryCodePoint(codePoint) ? 2 : 1; +}; + + +/** + * Determines whether the specified Unicode code point is in the supplementary + * Unicode characters range. + * @param {number} codePoint + * @return {boolean} Whether then given code point is a supplementary character. + */ +goog.i18n.uChar.isSupplementaryCodePoint = function(codePoint) { + return codePoint >= goog.i18n.uChar.SUPPLEMENTARY_CODE_POINT_MIN_VALUE_ && + codePoint <= goog.i18n.uChar.CODE_POINT_MAX_VALUE_; +}; + + +/** + * Gets whether the given code point is a leading surrogate character. + * @param {number} codePoint + * @return {boolean} Whether the given code point is a leading surrogate + * character. + */ +goog.i18n.uChar.isLeadSurrogateCodePoint = function(codePoint) { + return codePoint >= goog.i18n.uChar.LEAD_SURROGATE_MIN_VALUE_ && + codePoint <= goog.i18n.uChar.LEAD_SURROGATE_MAX_VALUE_; +}; + + +/** + * Gets whether the given code point is a trailing surrogate character. + * @param {number} codePoint + * @return {boolean} Whether the given code point is a trailing surrogate + * character. + */ +goog.i18n.uChar.isTrailSurrogateCodePoint = function(codePoint) { + return codePoint >= goog.i18n.uChar.TRAIL_SURROGATE_MIN_VALUE_ && + codePoint <= goog.i18n.uChar.TRAIL_SURROGATE_MAX_VALUE_; +}; + + +/** + * Composes a supplementary Unicode code point from the given UTF-16 surrogate + * pair. If leadSurrogate isn't a leading surrogate code point or trailSurrogate + * isn't a trailing surrogate code point, null is returned. + * @param {number} lead The leading surrogate code point. + * @param {number} trail The trailing surrogate code point. + * @return {?number} The supplementary Unicode code point obtained by decoding + * the given UTF-16 surrogate pair. + */ +goog.i18n.uChar.buildSupplementaryCodePoint = function(lead, trail) { + if (goog.i18n.uChar.isLeadSurrogateCodePoint(lead) && + goog.i18n.uChar.isTrailSurrogateCodePoint(trail)) { + var shiftedLeadOffset = + (lead << goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_) - + (goog.i18n.uChar.LEAD_SURROGATE_MIN_VALUE_ + << goog.i18n.uChar.TRAIL_SURROGATE_BIT_COUNT_); + var trailOffset = trail - goog.i18n.uChar.TRAIL_SURROGATE_MIN_VALUE_ + + goog.i18n.uChar.SUPPLEMENTARY_CODE_POINT_MIN_VALUE_; + return shiftedLeadOffset + trailOffset; + } + return null; +};
diff --git a/third_party/ink/closure/iter/iter.js b/third_party/ink/closure/iter/iter.js new file mode 100644 index 0000000..534d24ad --- /dev/null +++ b/third_party/ink/closure/iter/iter.js
@@ -0,0 +1,1284 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Python style iteration utilities. + * @author arv@google.com (Erik Arvidsson) + */ + + +goog.provide('goog.iter'); +goog.provide('goog.iter.Iterable'); +goog.provide('goog.iter.Iterator'); +goog.provide('goog.iter.StopIteration'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.math'); + + +/** + * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}} + */ +goog.iter.Iterable; + + +/** + * Singleton Error object that is used to terminate iterations. + * @const {!Error} + */ +goog.iter.StopIteration = ('StopIteration' in goog.global) ? + // For script engines that support legacy iterators. + goog.global['StopIteration'] : + {message: 'StopIteration', stack: ''}; + + + +/** + * Class/interface for iterators. An iterator needs to implement a {@code next} + * method and it needs to throw a {@code goog.iter.StopIteration} when the + * iteration passes beyond the end. Iterators have no {@code hasNext} method. + * It is recommended to always use the helper functions to iterate over the + * iterator or in case you are only targeting JavaScript 1.7 for in loops. + * @constructor + * @template VALUE + */ +goog.iter.Iterator = function() {}; + + +/** + * Returns the next value of the iteration. This will throw the object + * {@see goog.iter#StopIteration} when the iteration passes the end. + * @return {VALUE} Any object or value. + */ +goog.iter.Iterator.prototype.next = function() { + throw goog.iter.StopIteration; +}; + + +/** + * Returns the {@code Iterator} object itself. This is used to implement + * the iterator protocol in JavaScript 1.7 + * @param {boolean=} opt_keys Whether to return the keys or values. Default is + * to only return the values. This is being used by the for-in loop (true) + * and the for-each-in loop (false). Even though the param gives a hint + * about what the iterator will return there is no guarantee that it will + * return the keys when true is passed. + * @return {!goog.iter.Iterator<VALUE>} The object itself. + */ +goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) { + return this; +}; + + +/** + * Returns an iterator that knows how to iterate over the values in the object. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable If the + * object is an iterator it will be returned as is. If the object has an + * {@code __iterator__} method that will be called to get the value + * iterator. If the object is an array-like object we create an iterator + * for that. + * @return {!goog.iter.Iterator<VALUE>} An iterator that knows how to iterate + * over the values in {@code iterable}. + * @template VALUE + */ +goog.iter.toIterator = function(iterable) { + if (iterable instanceof goog.iter.Iterator) { + return iterable; + } + if (typeof iterable.__iterator__ == 'function') { + return iterable.__iterator__(false); + } + if (goog.isArrayLike(iterable)) { + var i = 0; + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + if (i >= iterable.length) { + throw goog.iter.StopIteration; + } + // Don't include deleted elements. + if (!(i in iterable)) { + i++; + continue; + } + return iterable[i++]; + } + }; + return newIter; + } + + + // TODO(arv): Should we fall back on goog.structs.getValues()? + throw new Error('Not implemented'); +}; + + +/** + * Calls a function for each element in the iterator with the element of the + * iterator passed as argument. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. If the iterable is an object {@code toIterator} will be + * called on it. + * @param {function(this:THIS,VALUE,?,!goog.iter.Iterator<VALUE>)} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and the return value is + * irrelevant. The reason for passing undefined as the second argument is + * so that the same function can be used in {@see goog.array#forEach} as + * well as others. The third parameter is of type "number" for + * arraylike objects, undefined, otherwise. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @template THIS, VALUE + */ +goog.iter.forEach = function(iterable, f, opt_obj) { + if (goog.isArrayLike(iterable)) { + + try { + // NOTES: this passes the index number to the second parameter + // of the callback contrary to the documentation above. + goog.array.forEach( + /** @type {IArrayLike<?>} */ (iterable), f, opt_obj); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } else { + iterable = goog.iter.toIterator(iterable); + + try { + while (true) { + f.call(opt_obj, iterable.next(), undefined, iterable); + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } +}; + + +/** + * Calls a function for every element in the iterator, and if the function + * returns true adds the element to a new iterator. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is true the element will be included in the returned + * iterator. If it is false the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements + * that passed the test are present. + * @template THIS, VALUE + */ +goog.iter.filter = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } + } + }; + return newIter; +}; + + +/** + * Calls a function for every element in the iterator, and if the function + * returns false adds the element to a new iterator. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is false the element will be included in the returned + * iterator. If it is true the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements + * that did not pass the test are present. + * @template THIS, VALUE + */ +goog.iter.filterFalse = function(iterable, f, opt_obj) { + return goog.iter.filter(iterable, goog.functions.not(f), opt_obj); +}; + + +/** + * Creates a new iterator that returns the values in a range. This function + * can take 1, 2 or 3 arguments: + * <pre> + * range(5) same as range(0, 5, 1) + * range(2, 5) same as range(2, 5, 1) + * </pre> + * + * @param {number} startOrStop The stop value if only one argument is provided. + * The start value if 2 or more arguments are provided. If only one + * argument is used the start value is 0. + * @param {number=} opt_stop The stop value. If left out then the first + * argument is used as the stop value. + * @param {number=} opt_step The number to increment with between each call to + * next. This can be negative. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the values + * in the range. + */ +goog.iter.range = function(startOrStop, opt_stop, opt_step) { + var start = 0; + var stop = startOrStop; + var step = opt_step || 1; + if (arguments.length > 1) { + start = startOrStop; + stop = opt_stop; + } + if (step == 0) { + throw new Error('Range step argument must not be zero'); + } + + var newIter = new goog.iter.Iterator; + newIter.next = function() { + if (step > 0 && start >= stop || step < 0 && start <= stop) { + throw goog.iter.StopIteration; + } + var rv = start; + start += step; + return rv; + }; + return newIter; +}; + + +/** + * Joins the values in a iterator with a delimiter. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to get the values from. + * @param {string} deliminator The text to put between the values. + * @return {string} The joined value string. + * @template VALUE + */ +goog.iter.join = function(iterable, deliminator) { + return goog.iter.toArray(iterable).join(deliminator); +}; + + +/** + * For every element in the iterator call a function and return a new iterator + * with that value. + * + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterator to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):RESULT} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, VALUE, RESULT + */ +goog.iter.map = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + var val = iterator.next(); + return f.call(opt_obj, val, undefined, iterator); + }; + return newIter; +}; + + +/** + * Passes every element of an iterator into a function and accumulates the + * result. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for + * every element. This function takes 2 arguments (the function's previous + * result or the initial value, and the value of the current element). + * function(previousValue, currentElement) : newValue. + * @param {VALUE} val The initial value to pass into the function on the first + * call. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * f. + * @return {VALUE} Result of evaluating f repeatedly across the values of + * the iterator. + * @template THIS, VALUE + */ +goog.iter.reduce = function(iterable, f, val, opt_obj) { + var rval = val; + goog.iter.forEach( + iterable, function(val) { rval = f.call(opt_obj, rval, val); }); + return rval; +}; + + +/** + * Goes through the values in the iterator. Calls f for each of these, and if + * any of them returns true, this returns true (without checking the rest). If + * all return false this will return false. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if any value passes the test. + * @template THIS, VALUE + */ +goog.iter.some = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + + try { + while (true) { + if (f.call(opt_obj, iterable.next(), undefined, iterable)) { + return true; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + return false; +}; + + +/** + * Goes through the values in the iterator. Calls f for each of these and if any + * of them returns false this returns false (without checking the rest). If all + * return true this will return true. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if every value passes the test. + * @template THIS, VALUE + */ +goog.iter.every = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + + try { + while (true) { + if (!f.call(opt_obj, iterable.next(), undefined, iterable)) { + return false; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + return true; +}; + + +/** + * Takes zero or more iterables and returns one iterator that will iterate over + * them in the order chained. + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will + * iterate over all the given iterables' contents. + * @template VALUE + */ +goog.iter.chain = function(var_args) { + return goog.iter.chainFromIterable(arguments); +}; + + +/** + * Takes a single iterable containing zero or more iterables and returns one + * iterator that will iterate over each one in the order given. + * @see https://goo.gl/5NRp5d + * @param {goog.iter.Iterable} iterable The iterable of iterables to chain. + * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will + * iterate over all the contents of the iterables contained within + * {@code iterable}. + * @template VALUE + */ +goog.iter.chainFromIterable = function(iterable) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + var current = null; + + iter.next = function() { + while (true) { + if (current == null) { + var it = iterator.next(); + current = goog.iter.toIterator(it); + } + try { + return current.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + current = null; + } + } + }; + + return iter; +}; + + +/** + * Builds a new iterator that iterates over the original, but skips elements as + * long as a supplied function returns true. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that drops elements from + * the original iterator as long as {@code f} is true. + * @template THIS, VALUE + */ +goog.iter.dropWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + var dropping = true; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (dropping && f.call(opt_obj, val, undefined, iterator)) { + continue; + } else { + dropping = false; + } + return val; + } + }; + return newIter; +}; + + +/** + * Builds a new iterator that iterates over the original, but only as long as a + * supplied function returns true. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj This is used as the 'this' object in f when called. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that keeps elements in + * the original iterator as long as the function is true. + * @template THIS, VALUE + */ +goog.iter.takeWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + iter.next = function() { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } + throw goog.iter.StopIteration; + }; + return iter; +}; + + +/** + * Converts the iterator to an array + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to convert to an array. + * @return {!Array<VALUE>} An array of the elements the iterator iterates over. + * @template VALUE + */ +goog.iter.toArray = function(iterable) { + // Fast path for array-like. + if (goog.isArrayLike(iterable)) { + return goog.array.toArray(/** @type {!IArrayLike<?>} */ (iterable)); + } + iterable = goog.iter.toIterator(iterable); + var array = []; + goog.iter.forEach(iterable, function(val) { array.push(val); }); + return array; +}; + + +/** + * Iterates over two iterables and returns true if they contain the same + * sequence of elements and have the same length. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable1 The first + * iterable object. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable2 The second + * iterable object. + * @param {function(VALUE,VALUE):boolean=} opt_equalsFn Optional comparison + * function. + * Should take two arguments to compare, and return true if the arguments + * are equal. Defaults to {@link goog.array.defaultCompareEquality} which + * compares the elements using the built-in '===' operator. + * @return {boolean} true if the iterables contain the same sequence of elements + * and have the same length. + * @template VALUE + */ +goog.iter.equals = function(iterable1, iterable2, opt_equalsFn) { + var fillValue = {}; + var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2); + var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; + return goog.iter.every( + pairs, function(pair) { return equalsFn(pair[0], pair[1]); }); +}; + + +/** + * Advances the iterator to the next position, returning the given default value + * instead of throwing an exception if the iterator has no more entries. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterable + * object. + * @param {VALUE} defaultValue The value to return if the iterator is empty. + * @return {VALUE} The next item in the iteration, or defaultValue if the + * iterator was empty. + * @template VALUE + */ +goog.iter.nextOrValue = function(iterable, defaultValue) { + try { + return goog.iter.toIterator(iterable).next(); + } catch (e) { + if (e != goog.iter.StopIteration) { + throw e; + } + return defaultValue; + } +}; + + +/** + * Cartesian product of zero or more sets. Gives an iterator that gives every + * combination of one element chosen from each set. For example, + * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]). + * @see http://docs.python.org/library/itertools.html#itertools.product + * @param {...!IArrayLike<VALUE>} var_args Zero or more sets, as + * arrays. + * @return {!goog.iter.Iterator<!Array<VALUE>>} An iterator that gives each + * n-tuple (as an array). + * @template VALUE + */ +goog.iter.product = function(var_args) { + var someArrayEmpty = + goog.array.some(arguments, function(arr) { return !arr.length; }); + + // An empty set in a cartesian product gives an empty set. + if (someArrayEmpty || !arguments.length) { + return new goog.iter.Iterator(); + } + + var iter = new goog.iter.Iterator(); + var arrays = arguments; + + // The first indices are [0, 0, ...] + var indicies = goog.array.repeat(0, arrays.length); + + iter.next = function() { + + if (indicies) { + var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) { + return arrays[arrayIndex][valueIndex]; + }); + + // Generate the next-largest indices for the next call. + // Increase the rightmost index. If it goes over, increase the next + // rightmost (like carry-over addition). + for (var i = indicies.length - 1; i >= 0; i--) { + // Assertion prevents compiler warning below. + goog.asserts.assert(indicies); + if (indicies[i] < arrays[i].length - 1) { + indicies[i]++; + break; + } + + // We're at the last indices (the last element of every array), so + // the iteration is over on the next call. + if (i == 0) { + indicies = null; + break; + } + // Reset the index in this column and loop back to increment the + // next one. + indicies[i] = 0; + } + return retVal; + } + + throw goog.iter.StopIteration; + }; + + return iter; +}; + + +/** + * Create an iterator to cycle over the iterable's elements indefinitely. + * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ... + * @see: http://docs.python.org/library/itertools.html#itertools.cycle. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable object. + * @return {!goog.iter.Iterator<VALUE>} An iterator that iterates indefinitely + * over the values in {@code iterable}. + * @template VALUE + */ +goog.iter.cycle = function(iterable) { + var baseIterator = goog.iter.toIterator(iterable); + + // We maintain a cache to store the iterable elements as we iterate + // over them. The cache is used to return elements once we have + // iterated over the iterable once. + var cache = []; + var cacheIndex = 0; + + var iter = new goog.iter.Iterator(); + + // This flag is set after the iterable is iterated over once + var useCache = false; + + iter.next = function() { + var returnElement = null; + + // Pull elements off the original iterator if not using cache + if (!useCache) { + try { + // Return the element from the iterable + returnElement = baseIterator.next(); + cache.push(returnElement); + return returnElement; + } catch (e) { + // If an exception other than StopIteration is thrown + // or if there are no elements to iterate over (the iterable was empty) + // throw an exception + if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) { + throw e; + } + // set useCache to true after we know that a 'StopIteration' exception + // was thrown and the cache is not empty (to handle the 'empty iterable' + // use case) + useCache = true; + } + } + + returnElement = cache[cacheIndex]; + cacheIndex = (cacheIndex + 1) % cache.length; + + return returnElement; + }; + + return iter; +}; + + +/** + * Creates an iterator that counts indefinitely from a starting value. + * @see http://docs.python.org/2/library/itertools.html#itertools.count + * @param {number=} opt_start The starting value. Default is 0. + * @param {number=} opt_step The number to increment with between each call to + * next. Negative and floating point numbers are allowed. Default is 1. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the values + * in the series. + */ +goog.iter.count = function(opt_start, opt_step) { + var counter = opt_start || 0; + var step = goog.isDef(opt_step) ? opt_step : 1; + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var returnValue = counter; + counter += step; + return returnValue; + }; + + return iter; +}; + + +/** + * Creates an iterator that returns the same object or value repeatedly. + * @param {VALUE} value Any object or value to repeat. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the + * repeated value. + * @template VALUE + */ +goog.iter.repeat = function(value) { + var iter = new goog.iter.Iterator(); + + iter.next = goog.functions.constant(value); + + return iter; +}; + + +/** + * Creates an iterator that returns running totals from the numbers in + * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields + * {@code 1 -> 3 -> 6 -> 10 -> 15}. + * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate + * @param {!goog.iter.Iterable} iterable The iterable of numbers to + * accumulate. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the + * numbers in the series. + */ +goog.iter.accumulate = function(iterable) { + var iterator = goog.iter.toIterator(iterable); + var total = 0; + var iter = new goog.iter.Iterator(); + + iter.next = function() { + total += iterator.next(); + return total; + }; + + return iter; +}; + + +/** + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Once the shortest iterable is + * exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE + */ +goog.iter.zip = function(var_args) { + var args = arguments; + var iter = new goog.iter.Iterator(); + + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + iter.next = function() { + var arr = goog.array.map(iterators, function(it) { return it.next(); }); + return arr; + }; + } + + return iter; +}; + + +/** + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Shorter iterables will be extended + * with {@code fillValue}. Once the longest iterable is exhausted, subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest + * @param {VALUE} fillValue The object or value used to fill shorter iterables. + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE + */ +goog.iter.zipLongest = function(fillValue, var_args) { + var args = goog.array.slice(arguments, 1); + var iter = new goog.iter.Iterator(); + + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + + iter.next = function() { + var iteratorsHaveValues = false; // false when all iterators are empty. + var arr = goog.array.map(iterators, function(it) { + var returnValue; + try { + returnValue = it.next(); + // Iterator had a value, so we've not exhausted the iterators. + // Set flag accordingly. + iteratorsHaveValues = true; + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + returnValue = fillValue; + } + return returnValue; + }); + + if (!iteratorsHaveValues) { + throw goog.iter.StopIteration; + } + return arr; + }; + } + + return iter; +}; + + +/** + * Creates an iterator that filters {@code iterable} based on a series of + * {@code selectors}. On each call to {@code next()}, one item is taken from + * both the {@code iterable} and {@code selectors} iterators. If the item from + * {@code selectors} evaluates to true, the item from {@code iterable} is given. + * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors} + * is exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.compress + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to filter. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} selectors An + * iterable of items to be evaluated in a boolean context to determine if + * the corresponding element in {@code iterable} should be included in the + * result. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the + * filtered values. + * @template VALUE + */ +goog.iter.compress = function(iterable, selectors) { + var selectorIterator = goog.iter.toIterator(selectors); + + return goog.iter.filter( + iterable, function() { return !!selectorIterator.next(); }); +}; + + + +/** + * Implements the {@code goog.iter.groupBy} iterator. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(VALUE): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @constructor + * @extends {goog.iter.Iterator<!Array<?>>} + * @template KEY, VALUE + * @private + */ +goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) { + + /** + * The iterable to group, coerced to an iterator. + * @type {!goog.iter.Iterator} + */ + this.iterator = goog.iter.toIterator(iterable); + + /** + * A function for determining the key value for each element in the iterable. + * If no function is provided, the identity function is used and returns the + * element unchanged. + * @type {function(VALUE): KEY} + */ + this.keyFunc = opt_keyFunc || goog.functions.identity; + + /** + * The target key for determining the start of a group. + * @type {KEY} + */ + this.targetKey; + + /** + * The current key visited during iteration. + * @type {KEY} + */ + this.currentKey; + + /** + * The current value being added to the group. + * @type {VALUE} + */ + this.currentValue; +}; +goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator); + + +/** @override */ +goog.iter.GroupByIterator_.prototype.next = function() { + while (this.currentKey == this.targetKey) { + this.currentValue = this.iterator.next(); // Exits on StopIteration + this.currentKey = this.keyFunc(this.currentValue); + } + this.targetKey = this.currentKey; + return [this.currentKey, this.groupItems_(this.targetKey)]; +}; + + +/** + * Performs the grouping of objects using the given key. + * @param {KEY} targetKey The target key object for the group. + * @return {!Array<VALUE>} An array of grouped objects. + * @private + */ +goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) { + var arr = []; + while (this.currentKey == targetKey) { + arr.push(this.currentValue); + try { + this.currentValue = this.iterator.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + break; + } + this.currentKey = this.keyFunc(this.currentValue); + } + return arr; +}; + + +/** + * Creates an iterator that returns arrays containing elements from the + * {@code iterable} grouped by a key value. For iterables with repeated + * elements (i.e. sorted according to a particular key function), this function + * has a {@code uniq}-like effect. For example, grouping the array: + * {@code [A, B, B, C, C, A]} produces + * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.groupby + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(VALUE): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @return {!goog.iter.Iterator<!Array<?>>} A new iterator that returns + * arrays of consecutive key and groups. + * @template KEY, VALUE + */ +goog.iter.groupBy = function(iterable, opt_keyFunc) { + return new goog.iter.GroupByIterator_(iterable, opt_keyFunc); +}; + + +/** + * Gives an iterator that gives the result of calling the given function + * <code>f</code> with the arguments taken from the next element from + * <code>iterable</code> (the elements are expected to also be iterables). + * + * Similar to {@see goog.iter#map} but allows the function to accept multiple + * arguments from the iterable. + * + * @param {!goog.iter.Iterable} iterable The iterable of + * iterables to iterate over. + * @param {function(this:THIS,...*):RESULT} f The function to call for every + * element. This function takes N+2 arguments, where N represents the + * number of items from the next element of the iterable. The two + * additional arguments passed to the function are undefined and the + * iterator itself. The function should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, RESULT + */ +goog.iter.starMap = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var args = goog.iter.toArray(iterator.next()); + return f.apply(opt_obj, goog.array.concat(args, undefined, iterator)); + }; + + return iter; +}; + + +/** + * Returns an array of iterators each of which can iterate over the values in + * {@code iterable} without advancing the others. + * @see http://docs.python.org/2/library/itertools.html#itertools.tee + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to tee. + * @param {number=} opt_num The number of iterators to create. Default is 2. + * @return {!Array<goog.iter.Iterator<VALUE>>} An array of iterators. + * @template VALUE + */ +goog.iter.tee = function(iterable, opt_num) { + var iterator = goog.iter.toIterator(iterable); + var num = goog.isNumber(opt_num) ? opt_num : 2; + var buffers = + goog.array.map(goog.array.range(num), function() { return []; }); + + var addNextIteratorValueToBuffers = function() { + var val = iterator.next(); + goog.array.forEach(buffers, function(buffer) { buffer.push(val); }); + }; + + var createIterator = function(buffer) { + // Each tee'd iterator has an associated buffer (initially empty). When a + // tee'd iterator's buffer is empty, it calls + // addNextIteratorValueToBuffers(), adding the next value to all tee'd + // iterators' buffers, and then returns that value. This allows each + // iterator to be advanced independently. + var iter = new goog.iter.Iterator(); + + iter.next = function() { + if (goog.array.isEmpty(buffer)) { + addNextIteratorValueToBuffers(); + } + goog.asserts.assert(!goog.array.isEmpty(buffer)); + return buffer.shift(); + }; + + return iter; + }; + + return goog.array.map(buffers, createIterator); +}; + + +/** + * Creates an iterator that returns arrays containing a count and an element + * obtained from the given {@code iterable}. + * @see http://docs.python.org/2/library/functions.html#enumerate + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to enumerate. + * @param {number=} opt_start Optional starting value. Default is 0. + * @return {!goog.iter.Iterator<!Array<?>>} A new iterator containing + * count/item pairs. + * @template VALUE + */ +goog.iter.enumerate = function(iterable, opt_start) { + return goog.iter.zip(goog.iter.count(opt_start), iterable); +}; + + +/** + * Creates an iterator that returns the first {@code limitSize} elements from an + * iterable. If this number is greater than the number of elements in the + * iterable, all the elements are returned. + * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to limit. + * @param {number} limitSize The maximum number of elements to return. + * @return {!goog.iter.Iterator<VALUE>} A new iterator containing + * {@code limitSize} elements. + * @template VALUE + */ +goog.iter.limit = function(iterable, limitSize) { + goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0); + + var iterator = goog.iter.toIterator(iterable); + + var iter = new goog.iter.Iterator(); + var remaining = limitSize; + + iter.next = function() { + if (remaining-- > 0) { + return iterator.next(); + } + throw goog.iter.StopIteration; + }; + + return iter; +}; + + +/** + * Creates an iterator that is advanced {@code count} steps ahead. Consumed + * values are silently discarded. If {@code count} is greater than the number + * of elements in {@code iterable}, an empty iterator is returned. Subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to consume. + * @param {number} count The number of elements to consume from the iterator. + * @return {!goog.iter.Iterator<VALUE>} An iterator advanced zero or more steps + * ahead. + * @template VALUE + */ +goog.iter.consume = function(iterable, count) { + goog.asserts.assert(goog.math.isInt(count) && count >= 0); + + var iterator = goog.iter.toIterator(iterable); + + while (count-- > 0) { + goog.iter.nextOrValue(iterator, null); + } + + return iterator; +}; + + +/** + * Creates an iterator that returns a range of elements from an iterable. + * Similar to {@see goog.array#slice} but does not support negative indexes. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to slice. + * @param {number} start The index of the first element to return. + * @param {number=} opt_end The index after the last element to return. If + * defined, must be greater than or equal to {@code start}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator containing a slice of + * the original. + * @template VALUE + */ +goog.iter.slice = function(iterable, start, opt_end) { + goog.asserts.assert(goog.math.isInt(start) && start >= 0); + + var iterator = goog.iter.consume(iterable, start); + + if (goog.isNumber(opt_end)) { + goog.asserts.assert(goog.math.isInt(opt_end) && opt_end >= start); + iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */); + } + + return iterator; +}; + + +/** + * Checks an array for duplicate elements. + * @param {?IArrayLike<VALUE>} arr The array to check for + * duplicates. + * @return {boolean} True, if the array contains duplicates, false otherwise. + * @private + * @template VALUE + */ +// TODO(dlindquist): Consider moving this into goog.array as a public function. +goog.iter.hasDuplicates_ = function(arr) { + var deduped = []; + goog.array.removeDuplicates(arr, deduped); + return arr.length != deduped.length; +}; + + +/** + * Creates an iterator that returns permutations of elements in + * {@code iterable}. + * + * Permutations are obtained by taking the Cartesian product of + * {@code opt_length} iterables and filtering out those with repeated + * elements. For example, the permutations of {@code [1,2,3]} are + * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.permutations + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate permutations. + * @param {number=} opt_length Length of each permutation. If omitted, defaults + * to the length of {@code iterable}. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing the + * permutations of {@code iterable}. + * @template VALUE + */ +goog.iter.permutations = function(iterable, opt_length) { + var elements = goog.iter.toArray(iterable); + var length = goog.isNumber(opt_length) ? opt_length : elements.length; + + var sets = goog.array.repeat(elements, length); + var product = goog.iter.product.apply(undefined, sets); + + return goog.iter.filter( + product, function(arr) { return !goog.iter.hasDuplicates_(arr); }); +}; + + +/** + * Creates an iterator that returns combinations of elements from + * {@code iterable}. + * + * Combinations are obtained by taking the {@see goog.iter#permutations} of + * {@code iterable} and filtering those whose elements appear in the order they + * are encountered in {@code iterable}. For example, the 3-length combinations + * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.combinations + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate combinations. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE + */ +goog.iter.combinations = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.iter.range(elements.length); + var indexIterator = goog.iter.permutations(indexes, length); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter( + indexIterator, function(arr) { return goog.array.isSorted(arr); }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { return elements[index]; } + + iter.next = function() { + return goog.array.map(sortedIndexIterator.next(), getIndexFromElements); + }; + + return iter; +}; + + +/** + * Creates an iterator that returns combinations of elements from + * {@code iterable}, with repeated elements possible. + * + * Combinations are obtained by taking the Cartesian product of {@code length} + * iterables and filtering those whose elements appear in the order they are + * encountered in {@code iterable}. For example, the 2-length combinations of + * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}. + * @see https://goo.gl/C0yXe4 + * @see https://goo.gl/djOCsk + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to combine. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE + */ +goog.iter.combinationsWithReplacement = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.array.range(elements.length); + var sets = goog.array.repeat(indexes, length); + var indexIterator = goog.iter.product.apply(undefined, sets); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter( + indexIterator, function(arr) { return goog.array.isSorted(arr); }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { return elements[index]; } + + iter.next = function() { + return goog.array.map( + /** @type {!Array<number>} */ + (sortedIndexIterator.next()), getIndexFromElements); + }; + + return iter; +};
diff --git a/third_party/ink/closure/labs/useragent/browser.js b/third_party/ink/closure/labs/useragent/browser.js new file mode 100644 index 0000000..78578e4 --- /dev/null +++ b/third_party/ink/closure/labs/useragent/browser.js
@@ -0,0 +1,339 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Closure user agent detection (Browser). + * @see <a href="http://www.useragentstring.com/">User agent strings</a> + * For more information on rendering engine, platform, or device see the other + * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform, + * goog.labs.userAgent.device respectively.) + * + * @author vbhasin@google.com (Vipul Bhasin) + * @author martone@google.com (Andy Martone) + */ + +goog.provide('goog.labs.userAgent.browser'); + +goog.require('goog.array'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.object'); +goog.require('goog.string'); + + +// TODO(nnaze): Refactor to remove excessive exclusion logic in matching +// functions. + + +/** + * @return {boolean} Whether the user's browser is Opera. Note: Chromium + * based Opera (Opera 15+) is detected as Chrome to avoid unnecessary + * special casing. + * @private + */ +goog.labs.userAgent.browser.matchOpera_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Opera'); +}; + + +/** + * @return {boolean} Whether the user's browser is IE. + * @private + */ +goog.labs.userAgent.browser.matchIE_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Trident') || + goog.labs.userAgent.util.matchUserAgent('MSIE'); +}; + + +/** + * @return {boolean} Whether the user's browser is Edge. + * @private + */ +goog.labs.userAgent.browser.matchEdge_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Edge'); +}; + + +/** + * @return {boolean} Whether the user's browser is Firefox. + * @private + */ +goog.labs.userAgent.browser.matchFirefox_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Firefox'); +}; + + +/** + * @return {boolean} Whether the user's browser is Safari. + * @private + */ +goog.labs.userAgent.browser.matchSafari_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Safari') && + !(goog.labs.userAgent.browser.matchChrome_() || + goog.labs.userAgent.browser.matchCoast_() || + goog.labs.userAgent.browser.matchOpera_() || + goog.labs.userAgent.browser.matchEdge_() || + goog.labs.userAgent.browser.isSilk() || + goog.labs.userAgent.util.matchUserAgent('Android')); +}; + + +/** + * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based + * iOS browser). + * @private + */ +goog.labs.userAgent.browser.matchCoast_ = function() { + return goog.labs.userAgent.util.matchUserAgent('Coast'); +}; + + +/** + * @return {boolean} Whether the user's browser is iOS Webview. + * @private + */ +goog.labs.userAgent.browser.matchIosWebview_ = function() { + // iOS Webview does not show up as Chrome or Safari. Also check for Opera's + // WebKit-based iOS browser, Coast. + return (goog.labs.userAgent.util.matchUserAgent('iPad') || + goog.labs.userAgent.util.matchUserAgent('iPhone')) && + !goog.labs.userAgent.browser.matchSafari_() && + !goog.labs.userAgent.browser.matchChrome_() && + !goog.labs.userAgent.browser.matchCoast_() && + goog.labs.userAgent.util.matchUserAgent('AppleWebKit'); +}; + + +/** + * @return {boolean} Whether the user's browser is Chrome. + * @private + */ +goog.labs.userAgent.browser.matchChrome_ = function() { + return (goog.labs.userAgent.util.matchUserAgent('Chrome') || + goog.labs.userAgent.util.matchUserAgent('CriOS')) && + !goog.labs.userAgent.browser.matchEdge_(); +}; + + +/** + * @return {boolean} Whether the user's browser is the Android browser. + * @private + */ +goog.labs.userAgent.browser.matchAndroidBrowser_ = function() { + // Android can appear in the user agent string for Chrome on Android. + // This is not the Android standalone browser if it does. + return goog.labs.userAgent.util.matchUserAgent('Android') && + !(goog.labs.userAgent.browser.isChrome() || + goog.labs.userAgent.browser.isFirefox() || + goog.labs.userAgent.browser.isOpera() || + goog.labs.userAgent.browser.isSilk()); +}; + + +/** + * @return {boolean} Whether the user's browser is Opera. + */ +goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_; + + +/** + * @return {boolean} Whether the user's browser is IE. + */ +goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_; + + +/** + * @return {boolean} Whether the user's browser is Edge. + */ +goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_; + + +/** + * @return {boolean} Whether the user's browser is Firefox. + */ +goog.labs.userAgent.browser.isFirefox = + goog.labs.userAgent.browser.matchFirefox_; + + +/** + * @return {boolean} Whether the user's browser is Safari. + */ +goog.labs.userAgent.browser.isSafari = goog.labs.userAgent.browser.matchSafari_; + + +/** + * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based + * iOS browser). + */ +goog.labs.userAgent.browser.isCoast = goog.labs.userAgent.browser.matchCoast_; + + +/** + * @return {boolean} Whether the user's browser is iOS Webview. + */ +goog.labs.userAgent.browser.isIosWebview = + goog.labs.userAgent.browser.matchIosWebview_; + + +/** + * @return {boolean} Whether the user's browser is Chrome. + */ +goog.labs.userAgent.browser.isChrome = goog.labs.userAgent.browser.matchChrome_; + + +/** + * @return {boolean} Whether the user's browser is the Android browser. + */ +goog.labs.userAgent.browser.isAndroidBrowser = + goog.labs.userAgent.browser.matchAndroidBrowser_; + + +/** + * For more information, see: + * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html + * @return {boolean} Whether the user's browser is Silk. + */ +goog.labs.userAgent.browser.isSilk = function() { + return goog.labs.userAgent.util.matchUserAgent('Silk'); +}; + + +/** + * @return {string} The browser version or empty string if version cannot be + * determined. Note that for Internet Explorer, this returns the version of + * the browser, not the version of the rendering engine. (IE 8 in + * compatibility mode will return 8.0 rather than 7.0. To determine the + * rendering engine version, look at document.documentMode instead. See + * http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more + * details.) + */ +goog.labs.userAgent.browser.getVersion = function() { + var userAgentString = goog.labs.userAgent.util.getUserAgent(); + // Special case IE since IE's version is inside the parenthesis and + // without the '/'. + if (goog.labs.userAgent.browser.isIE()) { + return goog.labs.userAgent.browser.getIEVersion_(userAgentString); + } + + var versionTuples = + goog.labs.userAgent.util.extractVersionTuples(userAgentString); + + // Construct a map for easy lookup. + var versionMap = {}; + goog.array.forEach(versionTuples, function(tuple) { + // Note that the tuple is of length three, but we only care about the + // first two. + var key = tuple[0]; + var value = tuple[1]; + versionMap[key] = value; + }); + + var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap); + + // Gives the value with the first key it finds, otherwise empty string. + function lookUpValueWithKeys(keys) { + var key = goog.array.find(keys, versionMapHasKey); + return versionMap[key] || ''; + } + + // Check Opera before Chrome since Opera 15+ has "Chrome" in the string. + // See + // http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond + if (goog.labs.userAgent.browser.isOpera()) { + // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first. + // Opera uses 'OPR' for more recent UAs. + return lookUpValueWithKeys(['Version', 'Opera']); + } + + // Check Edge before Chrome since it has Chrome in the string. + if (goog.labs.userAgent.browser.isEdge()) { + return lookUpValueWithKeys(['Edge']); + } + + if (goog.labs.userAgent.browser.isChrome()) { + return lookUpValueWithKeys(['Chrome', 'CriOS']); + } + + // Usually products browser versions are in the third tuple after "Mozilla" + // and the engine. + var tuple = versionTuples[2]; + return tuple && tuple[1] || ''; +}; + + +/** + * @param {string|number} version The version to check. + * @return {boolean} Whether the browser version is higher or the same as the + * given version. + */ +goog.labs.userAgent.browser.isVersionOrHigher = function(version) { + return goog.string.compareVersions( + goog.labs.userAgent.browser.getVersion(), version) >= 0; +}; + + +/** + * Determines IE version. More information: + * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString + * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx + * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx + * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx + * + * @param {string} userAgent the User-Agent. + * @return {string} + * @private + */ +goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) { + // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade + // bug. Example UA: + // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) + // like Gecko. + // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments. + var rv = /rv: *([\d\.]*)/.exec(userAgent); + if (rv && rv[1]) { + return rv[1]; + } + + var version = ''; + var msie = /MSIE +([\d\.]+)/.exec(userAgent); + if (msie && msie[1]) { + // IE in compatibility mode usually identifies itself as MSIE 7.0; in this + // case, use the Trident version to determine the version of IE. For more + // details, see the links above. + var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent); + if (msie[1] == '7.0') { + if (tridentVersion && tridentVersion[1]) { + switch (tridentVersion[1]) { + case '4.0': + version = '8.0'; + break; + case '5.0': + version = '9.0'; + break; + case '6.0': + version = '10.0'; + break; + case '7.0': + version = '11.0'; + break; + } + } else { + version = '7.0'; + } + } else { + version = msie[1]; + } + } + return version; +};
diff --git a/third_party/ink/closure/labs/useragent/engine.js b/third_party/ink/closure/labs/useragent/engine.js new file mode 100644 index 0000000..4de0ff33 --- /dev/null +++ b/third_party/ink/closure/labs/useragent/engine.js
@@ -0,0 +1,157 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Closure user agent detection. + * @see http://en.wikipedia.org/wiki/User_agent + * For more information on browser brand, platform, or device see the other + * sub-namespaces in goog.labs.userAgent (browser, platform, and device). + * + * @author vbhasin@google.com (Vipul Bhasin) + */ + +goog.provide('goog.labs.userAgent.engine'); + +goog.require('goog.array'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.string'); + + +/** + * @return {boolean} Whether the rendering engine is Presto. + */ +goog.labs.userAgent.engine.isPresto = function() { + return goog.labs.userAgent.util.matchUserAgent('Presto'); +}; + + +/** + * @return {boolean} Whether the rendering engine is Trident. + */ +goog.labs.userAgent.engine.isTrident = function() { + // IE only started including the Trident token in IE8. + return goog.labs.userAgent.util.matchUserAgent('Trident') || + goog.labs.userAgent.util.matchUserAgent('MSIE'); +}; + + +/** + * @return {boolean} Whether the rendering engine is Edge. + */ +goog.labs.userAgent.engine.isEdge = function() { + return goog.labs.userAgent.util.matchUserAgent('Edge'); +}; + + +/** + * @return {boolean} Whether the rendering engine is WebKit. + */ +goog.labs.userAgent.engine.isWebKit = function() { + return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') && + !goog.labs.userAgent.engine.isEdge(); +}; + + +/** + * @return {boolean} Whether the rendering engine is Gecko. + */ +goog.labs.userAgent.engine.isGecko = function() { + return goog.labs.userAgent.util.matchUserAgent('Gecko') && + !goog.labs.userAgent.engine.isWebKit() && + !goog.labs.userAgent.engine.isTrident() && + !goog.labs.userAgent.engine.isEdge(); +}; + + +/** + * @return {string} The rendering engine's version or empty string if version + * can't be determined. + */ +goog.labs.userAgent.engine.getVersion = function() { + var userAgentString = goog.labs.userAgent.util.getUserAgent(); + if (userAgentString) { + var tuples = goog.labs.userAgent.util.extractVersionTuples(userAgentString); + + var engineTuple = goog.labs.userAgent.engine.getEngineTuple_(tuples); + if (engineTuple) { + // In Gecko, the version string is either in the browser info or the + // Firefox version. See Gecko user agent string reference: + // http://goo.gl/mULqa + if (engineTuple[0] == 'Gecko') { + return goog.labs.userAgent.engine.getVersionForKey_(tuples, 'Firefox'); + } + + return engineTuple[1]; + } + + // MSIE has only one version identifier, and the Trident version is + // specified in the parenthetical. IE Edge is covered in the engine tuple + // detection. + var browserTuple = tuples[0]; + var info; + if (browserTuple && (info = browserTuple[2])) { + var match = /Trident\/([^\s;]+)/.exec(info); + if (match) { + return match[1]; + } + } + } + return ''; +}; + + +/** + * @param {!Array<!Array<string>>} tuples Extracted version tuples. + * @return {!Array<string>|undefined} The engine tuple or undefined if not + * found. + * @private + */ +goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) { + if (!goog.labs.userAgent.engine.isEdge()) { + return tuples[1]; + } + for (var i = 0; i < tuples.length; i++) { + var tuple = tuples[i]; + if (tuple[0] == 'Edge') { + return tuple; + } + } +}; + + +/** + * @param {string|number} version The version to check. + * @return {boolean} Whether the rendering engine version is higher or the same + * as the given version. + */ +goog.labs.userAgent.engine.isVersionOrHigher = function(version) { + return goog.string.compareVersions( + goog.labs.userAgent.engine.getVersion(), version) >= 0; +}; + + +/** + * @param {!Array<!Array<string>>} tuples Version tuples. + * @param {string} key The key to look for. + * @return {string} The version string of the given key, if present. + * Otherwise, the empty string. + * @private + */ +goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) { + // TODO(nnaze): Move to util if useful elsewhere. + + var pair = goog.array.find(tuples, function(pair) { return key == pair[0]; }); + + return pair && pair[1] || ''; +};
diff --git a/third_party/ink/closure/labs/useragent/platform.js b/third_party/ink/closure/labs/useragent/platform.js new file mode 100644 index 0000000..d5c3537 --- /dev/null +++ b/third_party/ink/closure/labs/useragent/platform.js
@@ -0,0 +1,161 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Closure user agent platform detection. + * @see <a href="http://www.useragentstring.com/">User agent strings</a> + * For more information on browser brand, rendering engine, or device see the + * other sub-namespaces in goog.labs.userAgent (browser, engine, and device + * respectively). + * + * @author vbhasin@google.com (Vipul Bhasin) + */ + +goog.provide('goog.labs.userAgent.platform'); + +goog.require('goog.labs.userAgent.util'); +goog.require('goog.string'); + + +/** + * @return {boolean} Whether the platform is Android. + */ +goog.labs.userAgent.platform.isAndroid = function() { + return goog.labs.userAgent.util.matchUserAgent('Android'); +}; + + +/** + * @return {boolean} Whether the platform is iPod. + */ +goog.labs.userAgent.platform.isIpod = function() { + return goog.labs.userAgent.util.matchUserAgent('iPod'); +}; + + +/** + * @return {boolean} Whether the platform is iPhone. + */ +goog.labs.userAgent.platform.isIphone = function() { + return goog.labs.userAgent.util.matchUserAgent('iPhone') && + !goog.labs.userAgent.util.matchUserAgent('iPod') && + !goog.labs.userAgent.util.matchUserAgent('iPad'); +}; + + +/** + * @return {boolean} Whether the platform is iPad. + */ +goog.labs.userAgent.platform.isIpad = function() { + return goog.labs.userAgent.util.matchUserAgent('iPad'); +}; + + +/** + * @return {boolean} Whether the platform is iOS. + */ +goog.labs.userAgent.platform.isIos = function() { + return goog.labs.userAgent.platform.isIphone() || + goog.labs.userAgent.platform.isIpad() || + goog.labs.userAgent.platform.isIpod(); +}; + + +/** + * @return {boolean} Whether the platform is Mac. + */ +goog.labs.userAgent.platform.isMacintosh = function() { + return goog.labs.userAgent.util.matchUserAgent('Macintosh'); +}; + + +/** + * Note: ChromeOS is not considered to be Linux as it does not report itself + * as Linux in the user agent string. + * @return {boolean} Whether the platform is Linux. + */ +goog.labs.userAgent.platform.isLinux = function() { + return goog.labs.userAgent.util.matchUserAgent('Linux'); +}; + + +/** + * @return {boolean} Whether the platform is Windows. + */ +goog.labs.userAgent.platform.isWindows = function() { + return goog.labs.userAgent.util.matchUserAgent('Windows'); +}; + + +/** + * @return {boolean} Whether the platform is ChromeOS. + */ +goog.labs.userAgent.platform.isChromeOS = function() { + return goog.labs.userAgent.util.matchUserAgent('CrOS'); +}; + + +/** + * The version of the platform. We only determine the version for Windows, + * Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only + * look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given + * version 0.0. + * + * @return {string} The platform version or empty string if version cannot be + * determined. + */ +goog.labs.userAgent.platform.getVersion = function() { + var userAgentString = goog.labs.userAgent.util.getUserAgent(); + var version = '', re; + if (goog.labs.userAgent.platform.isWindows()) { + re = /Windows (?:NT|Phone) ([0-9.]+)/; + var match = re.exec(userAgentString); + if (match) { + version = match[1]; + } else { + version = '0.0'; + } + } else if (goog.labs.userAgent.platform.isIos()) { + re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/; + var match = re.exec(userAgentString); + // Report the version as x.y.z and not x_y_z + version = match && match[1].replace(/_/g, '.'); + } else if (goog.labs.userAgent.platform.isMacintosh()) { + re = /Mac OS X ([0-9_.]+)/; + var match = re.exec(userAgentString); + // Note: some old versions of Camino do not report an OSX version. + // Default to 10. + version = match ? match[1].replace(/_/g, '.') : '10'; + } else if (goog.labs.userAgent.platform.isAndroid()) { + re = /Android\s+([^\);]+)(\)|;)/; + var match = re.exec(userAgentString); + version = match && match[1]; + } else if (goog.labs.userAgent.platform.isChromeOS()) { + re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/; + var match = re.exec(userAgentString); + version = match && match[1]; + } + return version || ''; +}; + + +/** + * @param {string|number} version The version to check. + * @return {boolean} Whether the browser version is higher or the same as the + * given version. + */ +goog.labs.userAgent.platform.isVersionOrHigher = function(version) { + return goog.string.compareVersions( + goog.labs.userAgent.platform.getVersion(), version) >= 0; +};
diff --git a/third_party/ink/closure/labs/useragent/util.js b/third_party/ink/closure/labs/useragent/util.js new file mode 100644 index 0000000..a57e5d8d --- /dev/null +++ b/third_party/ink/closure/labs/useragent/util.js
@@ -0,0 +1,157 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities used by goog.labs.userAgent tools. These functions + * should not be used outside of goog.labs.userAgent.*. + * + * MOE:begin_intracomment_strip + * @visibility {//javascript/abc/libs/objects3d:__subpackages__} + * @visibility {//javascript/closure/bin/sizetests:__pkg__} + * @visibility {//javascript/closure/dom:__subpackages__} + * @visibility {//javascript/closure/style:__pkg__} + * @visibility {//javascript/closure/testing:__pkg__} + * @visibility {//javascript/closure/useragent:__subpackages__} + * @visibility {//testing/puppet/modules:__pkg__} + * @visibility {:util_legacy_users} + * MOE:end_intracomment_strip + * + * @author nnaze@google.com (Nathan Naze) + */ + +goog.provide('goog.labs.userAgent.util'); + +goog.require('goog.string'); + + +/** + * Gets the native userAgent string from navigator if it exists. + * If navigator or navigator.userAgent string is missing, returns an empty + * string. + * @return {string} + * @private + */ +goog.labs.userAgent.util.getNativeUserAgentString_ = function() { + var navigator = goog.labs.userAgent.util.getNavigator_(); + if (navigator) { + var userAgent = navigator.userAgent; + if (userAgent) { + return userAgent; + } + } + return ''; +}; + + +/** + * Getter for the native navigator. + * This is a separate function so it can be stubbed out in testing. + * @return {Navigator} + * @private + */ +goog.labs.userAgent.util.getNavigator_ = function() { + return goog.global.navigator; +}; + + +/** + * A possible override for applications which wish to not check + * navigator.userAgent but use a specified value for detection instead. + * @private {string} + */ +goog.labs.userAgent.util.userAgent_ = + goog.labs.userAgent.util.getNativeUserAgentString_(); + + +/** + * Applications may override browser detection on the built in + * navigator.userAgent object by setting this string. Set to null to use the + * browser object instead. + * @param {?string=} opt_userAgent The User-Agent override. + */ +goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) { + goog.labs.userAgent.util.userAgent_ = + opt_userAgent || goog.labs.userAgent.util.getNativeUserAgentString_(); +}; + + +/** + * @return {string} The user agent string. + */ +goog.labs.userAgent.util.getUserAgent = function() { + return goog.labs.userAgent.util.userAgent_; +}; + + +/** + * @param {string} str + * @return {boolean} Whether the user agent contains the given string. + */ +goog.labs.userAgent.util.matchUserAgent = function(str) { + var userAgent = goog.labs.userAgent.util.getUserAgent(); + return goog.string.contains(userAgent, str); +}; + + +/** + * @param {string} str + * @return {boolean} Whether the user agent contains the given string, ignoring + * case. + */ +goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) { + var userAgent = goog.labs.userAgent.util.getUserAgent(); + return goog.string.caseInsensitiveContains(userAgent, str); +}; + + +/** + * Parses the user agent into tuples for each section. + * @param {string} userAgent + * @return {!Array<!Array<string>>} Tuples of key, version, and the contents + * of the parenthetical. + */ +goog.labs.userAgent.util.extractVersionTuples = function(userAgent) { + // Matches each section of a user agent string. + // Example UA: + // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) + // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405 + // This has three version tuples: Mozilla, AppleWebKit, and Mobile. + + var versionRegExp = new RegExp( + // Key. Note that a key may have a space. + // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0') + '(\\w[\\w ]+)' + + + '/' + // slash + '([^\\s]+)' + // version (i.e. '5.0b') + '\\s*' + // whitespace + '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched. + 'g'); + + var data = []; + var match; + + // Iterate and collect the version tuples. Each iteration will be the + // next regex match. + while (match = versionRegExp.exec(userAgent)) { + data.push([ + match[1], // key + match[2], // value + // || undefined as this is not undefined in IE7 and IE8 + match[3] || undefined // info + ]); + } + + return data; +};
diff --git a/third_party/ink/closure/math/box.js b/third_party/ink/closure/math/box.js new file mode 100644 index 0000000..108e6f7 --- /dev/null +++ b/third_party/ink/closure/math/box.js
@@ -0,0 +1,403 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing a numeric box. + * @author pupius@google.com (Daniel Pupius) + */ + + +goog.provide('goog.math.Box'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + + +/** + * Class for representing a box. A box is specified as a top, right, bottom, + * and left. A box is useful for representing margins and padding. + * + * This class assumes 'screen coordinates': larger Y coordinates are further + * from the top of the screen. + * + * @param {number} top Top. + * @param {number} right Right. + * @param {number} bottom Bottom. + * @param {number} left Left. + * @struct + * @constructor + */ +goog.math.Box = function(top, right, bottom, left) { + /** + * Top + * @type {number} + */ + this.top = top; + + /** + * Right + * @type {number} + */ + this.right = right; + + /** + * Bottom + * @type {number} + */ + this.bottom = bottom; + + /** + * Left + * @type {number} + */ + this.left = left; +}; + + +/** + * Creates a Box by bounding a collection of goog.math.Coordinate objects + * @param {...goog.math.Coordinate} var_args Coordinates to be included inside + * the box. + * @return {!goog.math.Box} A Box containing all the specified Coordinates. + */ +goog.math.Box.boundingBox = function(var_args) { + var box = new goog.math.Box( + arguments[0].y, arguments[0].x, arguments[0].y, arguments[0].x); + for (var i = 1; i < arguments.length; i++) { + box.expandToIncludeCoordinate(arguments[i]); + } + return box; +}; + + +/** + * @return {number} width The width of this Box. + */ +goog.math.Box.prototype.getWidth = function() { + return this.right - this.left; +}; + + +/** + * @return {number} height The height of this Box. + */ +goog.math.Box.prototype.getHeight = function() { + return this.bottom - this.top; +}; + + +/** + * Creates a copy of the box with the same dimensions. + * @return {!goog.math.Box} A clone of this Box. + */ +goog.math.Box.prototype.clone = function() { + return new goog.math.Box(this.top, this.right, this.bottom, this.left); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing the box. + * @return {string} In the form (50t, 73r, 24b, 13l). + * @override + */ + goog.math.Box.prototype.toString = function() { + return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + + this.left + 'l)'; + }; +} + + +/** + * Returns whether the box contains a coordinate or another box. + * + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. + */ +goog.math.Box.prototype.contains = function(other) { + return goog.math.Box.contains(this, other); +}; + + +/** + * Expands box with the given margins. + * + * @param {number|goog.math.Box} top Top margin or box with all margins. + * @param {number=} opt_right Right margin. + * @param {number=} opt_bottom Bottom margin. + * @param {number=} opt_left Left margin. + * @return {!goog.math.Box} A reference to this Box. + */ +goog.math.Box.prototype.expand = function( + top, opt_right, opt_bottom, opt_left) { + if (goog.isObject(top)) { + this.top -= top.top; + this.right += top.right; + this.bottom += top.bottom; + this.left -= top.left; + } else { + this.top -= /** @type {number} */ (top); + this.right += Number(opt_right); + this.bottom += Number(opt_bottom); + this.left -= Number(opt_left); + } + + return this; +}; + + +/** + * Expand this box to include another box. + * NOTE(bcornell): This is used in code that needs to be very fast, please don't + * add functionality to this function at the expense of speed (variable + * arguments, accepting multiple argument types, etc). + * @param {goog.math.Box} box The box to include in this one. + */ +goog.math.Box.prototype.expandToInclude = function(box) { + this.left = Math.min(this.left, box.left); + this.top = Math.min(this.top, box.top); + this.right = Math.max(this.right, box.right); + this.bottom = Math.max(this.bottom, box.bottom); +}; + + +/** + * Expand this box to include the coordinate. + * @param {!goog.math.Coordinate} coord The coordinate to be included + * inside the box. + */ +goog.math.Box.prototype.expandToIncludeCoordinate = function(coord) { + this.top = Math.min(this.top, coord.y); + this.right = Math.max(this.right, coord.x); + this.bottom = Math.max(this.bottom, coord.y); + this.left = Math.min(this.left, coord.x); +}; + + +/** + * Compares boxes for equality. + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A Box. + * @return {boolean} True iff the boxes are equal, or if both are null. + */ +goog.math.Box.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.top == b.top && a.right == b.right && a.bottom == b.bottom && + a.left == b.left; +}; + + +/** + * Returns whether a box contains a coordinate or another box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. + */ +goog.math.Box.contains = function(box, other) { + if (!box || !other) { + return false; + } + + if (other instanceof goog.math.Box) { + return other.left >= box.left && other.right <= box.right && + other.top >= box.top && other.bottom <= box.bottom; + } + + // other is a Coordinate. + return other.x >= box.left && other.x <= box.right && other.y >= box.top && + other.y <= box.bottom; +}; + + +/** + * Returns the relative x position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The x position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. + */ +goog.math.Box.relativePositionX = function(box, coord) { + if (coord.x < box.left) { + return coord.x - box.left; + } else if (coord.x > box.right) { + return coord.x - box.right; + } + return 0; +}; + + +/** + * Returns the relative y position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The y position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. + */ +goog.math.Box.relativePositionY = function(box, coord) { + if (coord.y < box.top) { + return coord.y - box.top; + } else if (coord.y > box.bottom) { + return coord.y - box.bottom; + } + return 0; +}; + + +/** + * Returns the distance between a coordinate and the nearest corner/side of a + * box. Returns zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The distance between {@code coord} and the nearest + * corner/side of {@code box}, or zero if {@code coord} is inside + * {@code box}. + */ +goog.math.Box.distance = function(box, coord) { + var x = goog.math.Box.relativePositionX(box, coord); + var y = goog.math.Box.relativePositionY(box, coord); + return Math.sqrt(x * x + y * y); +}; + + +/** + * Returns whether two boxes intersect. + * + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @return {boolean} Whether the boxes intersect. + */ +goog.math.Box.intersects = function(a, b) { + return ( + a.left <= b.right && b.left <= a.right && a.top <= b.bottom && + b.top <= a.bottom); +}; + + +/** + * Returns whether two boxes would intersect with additional padding. + * + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @param {number} padding The additional padding. + * @return {boolean} Whether the boxes intersect. + */ +goog.math.Box.intersectsWithPadding = function(a, b, padding) { + return ( + a.left <= b.right + padding && b.left <= a.right + padding && + a.top <= b.bottom + padding && b.top <= a.bottom + padding); +}; + + +/** + * Rounds the fields to the next larger integer values. + * + * @return {!goog.math.Box} This box with ceil'd fields. + */ +goog.math.Box.prototype.ceil = function() { + this.top = Math.ceil(this.top); + this.right = Math.ceil(this.right); + this.bottom = Math.ceil(this.bottom); + this.left = Math.ceil(this.left); + return this; +}; + + +/** + * Rounds the fields to the next smaller integer values. + * + * @return {!goog.math.Box} This box with floored fields. + */ +goog.math.Box.prototype.floor = function() { + this.top = Math.floor(this.top); + this.right = Math.floor(this.right); + this.bottom = Math.floor(this.bottom); + this.left = Math.floor(this.left); + return this; +}; + + +/** + * Rounds the fields to nearest integer values. + * + * @return {!goog.math.Box} This box with rounded fields. + */ +goog.math.Box.prototype.round = function() { + this.top = Math.round(this.top); + this.right = Math.round(this.right); + this.bottom = Math.round(this.bottom); + this.left = Math.round(this.left); + return this; +}; + + +/** + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the left and right values are translated by the coordinate's + * x value and the top and bottom values are translated by the coordinate's y + * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x + * and y dimension values. + * + * @param {number|goog.math.Coordinate} tx The value to translate the x + * dimension values by or the the coordinate to translate this box by. + * @param {number=} opt_ty The value to translate y dimension values by. + * @return {!goog.math.Box} This box after translating. + */ +goog.math.Box.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.right += tx.x; + this.top += tx.y; + this.bottom += tx.y; + } else { + goog.asserts.assertNumber(tx); + this.left += tx; + this.right += tx; + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + this.bottom += opt_ty; + } + } + return this; +}; + + +/** + * Scales this coordinate by the given scale factors. The x and y dimension + * values are scaled by {@code sx} and {@code opt_sy} respectively. + * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. + * + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Box} This box after scaling. + */ +goog.math.Box.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.right *= sx; + this.top *= sy; + this.bottom *= sy; + return this; +};
diff --git a/third_party/ink/closure/math/coordinate.js b/third_party/ink/closure/math/coordinate.js new file mode 100644 index 0000000..6966451d --- /dev/null +++ b/third_party/ink/closure/math/coordinate.js
@@ -0,0 +1,280 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing two-dimensional positions. + * @author pupius@google.com (Daniel Pupius) + */ + + +goog.provide('goog.math.Coordinate'); + +goog.require('goog.math'); + + + +/** + * Class for representing coordinates and positions. + * @param {number=} opt_x Left, defaults to 0. + * @param {number=} opt_y Top, defaults to 0. + * @struct + * @constructor + */ +goog.math.Coordinate = function(opt_x, opt_y) { + /** + * X-value + * @type {number} + */ + this.x = goog.isDef(opt_x) ? opt_x : 0; + + /** + * Y-value + * @type {number} + */ + this.y = goog.isDef(opt_y) ? opt_y : 0; +}; + + +/** + * Returns a new copy of the coordinate. + * @return {!goog.math.Coordinate} A clone of this coordinate. + */ +goog.math.Coordinate.prototype.clone = function() { + return new goog.math.Coordinate(this.x, this.y); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing the coordinate. + * @return {string} In the form (50, 73). + * @override + */ + goog.math.Coordinate.prototype.toString = function() { + return '(' + this.x + ', ' + this.y + ')'; + }; +} + + +/** + * Returns whether the specified value is equal to this coordinate. + * @param {*} other Some other value. + * @return {boolean} Whether the specified value is equal to this coordinate. + */ +goog.math.Coordinate.prototype.equals = function(other) { + return other instanceof goog.math.Coordinate && + goog.math.Coordinate.equals(this, other); +}; + + +/** + * Compares coordinates for equality. + * @param {goog.math.Coordinate} a A Coordinate. + * @param {goog.math.Coordinate} b A Coordinate. + * @return {boolean} True iff the coordinates are equal, or if both are null. + */ +goog.math.Coordinate.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.x == b.x && a.y == b.y; +}; + + +/** + * Returns the distance between two coordinates. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.distance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy); +}; + + +/** + * Returns the magnitude of a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The distance between the origin and {@code a}. + */ +goog.math.Coordinate.magnitude = function(a) { + return Math.sqrt(a.x * a.x + a.y * a.y); +}; + + +/** + * Returns the angle from the origin to a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The angle, in degrees, clockwise from the positive X + * axis to {@code a}. + */ +goog.math.Coordinate.azimuth = function(a) { + return goog.math.angle(0, 0, a.x, a.y); +}; + + +/** + * Returns the squared distance between two coordinates. Squared distances can + * be used for comparisons when the actual value is not required. + * + * Performance note: eliminating the square root is an optimization often used + * in lower-level languages, but the speed difference is not nearly as + * pronounced in JavaScript (only a few percent.) + * + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The squared distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.squaredDistance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return dx * dx + dy * dy; +}; + + +/** + * Returns the difference between two coordinates as a new + * goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the difference + * between {@code a} and {@code b}. + */ +goog.math.Coordinate.difference = function(a, b) { + return new goog.math.Coordinate(a.x - b.x, a.y - b.y); +}; + + +/** + * Returns the sum of two coordinates as a new goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two + * coordinates. + */ +goog.math.Coordinate.sum = function(a, b) { + return new goog.math.Coordinate(a.x + b.x, a.y + b.y); +}; + + +/** + * Rounds the x and y fields to the next larger integer values. + * @return {!goog.math.Coordinate} This coordinate with ceil'd fields. + */ +goog.math.Coordinate.prototype.ceil = function() { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + return this; +}; + + +/** + * Rounds the x and y fields to the next smaller integer values. + * @return {!goog.math.Coordinate} This coordinate with floored fields. + */ +goog.math.Coordinate.prototype.floor = function() { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; +}; + + +/** + * Rounds the x and y fields to the nearest integer values. + * @return {!goog.math.Coordinate} This coordinate with rounded fields. + */ +goog.math.Coordinate.prototype.round = function() { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; +}; + + +/** + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the x and y values are translated by the coordinate's x and y. + * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} + * respectively. + * @param {number|goog.math.Coordinate} tx The value to translate x by or the + * the coordinate to translate this coordinate by. + * @param {number=} opt_ty The value to translate y by. + * @return {!goog.math.Coordinate} This coordinate after translating. + */ +goog.math.Coordinate.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.x += tx.x; + this.y += tx.y; + } else { + this.x += Number(tx); + if (goog.isNumber(opt_ty)) { + this.y += opt_ty; + } + } + return this; +}; + + +/** + * Scales this coordinate by the given scale factors. The x and y values are + * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} + * is not given, then {@code sx} is used for both x and y. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Coordinate} This coordinate after scaling. + */ +goog.math.Coordinate.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.x *= sx; + this.y *= sy; + return this; +}; + + +/** + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in radians. + * @param {number} radians The angle by which to rotate this coordinate + * clockwise about the given center, in radians. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. + */ +goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) { + var center = opt_center || new goog.math.Coordinate(0, 0); + + var x = this.x; + var y = this.y; + var cos = Math.cos(radians); + var sin = Math.sin(radians); + + this.x = (x - center.x) * cos - (y - center.y) * sin + center.x; + this.y = (x - center.x) * sin + (y - center.y) * cos + center.y; +}; + + +/** + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in degrees. + * @param {number} degrees The angle by which to rotate this coordinate + * clockwise about the given center, in degrees. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. + */ +goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) { + this.rotateRadians(goog.math.toRadians(degrees), opt_center); +};
diff --git a/third_party/ink/closure/math/irect.js b/third_party/ink/closure/math/irect.js new file mode 100644 index 0000000..db7cee1 --- /dev/null +++ b/third_party/ink/closure/math/irect.js
@@ -0,0 +1,45 @@ +// Copyright 2016 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A record declaration to allow ClientRect and other rectangle + * like objects to be used with goog.math.Rect. + */ + +goog.provide('goog.math.IRect'); + + +/** + * Record for representing rectangular regions, allows compatibility between + * things like ClientRect and goog.math.Rect. + * + * @record + */ +goog.math.IRect = function() {}; + + +/** @type {number} */ +goog.math.IRect.prototype.left; + + +/** @type {number} */ +goog.math.IRect.prototype.top; + + +/** @type {number} */ +goog.math.IRect.prototype.width; + + +/** @type {number} */ +goog.math.IRect.prototype.height;
diff --git a/third_party/ink/closure/math/long.js b/third_party/ink/closure/math/long.js new file mode 100644 index 0000000..60ddef0 --- /dev/null +++ b/third_party/ink/closure/math/long.js
@@ -0,0 +1,966 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines a Long class for representing a 64-bit two's-complement + * integer value, which faithfully simulates the behavior of a Java "long". This + * implementation is derived from LongLib in GWT. + * + * @author kevinz@google.com (Kevin Zatloukal) + */ + +goog.provide('goog.math.Long'); + +goog.require('goog.asserts'); +goog.require('goog.reflect'); + + + +/** + * Constructs a 64-bit two's-complement integer, given its low and high 32-bit + * values as *signed* integers. See the from* functions below for more + * convenient ways of constructing Longs. + * + * The internal representation of a long is the two given signed, 32-bit values. + * We use 32-bit pieces because these are the size of integers on which + * Javascript performs bit-operations. For operations like addition and + * multiplication, we split each number into 16-bit pieces, which can easily be + * multiplied within Javascript's floating-point representation without overflow + * or change in sign. + * + * In the algorithms below, we frequently reduce the negative case to the + * positive case by negating the input(s) and then post-processing the result. + * Note that we must ALWAYS check specially whether those values are MIN_VALUE + * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as + * a positive number, it overflows back into a negative). Not handling this + * case would often result in infinite recursion. + * + * @param {number} low The low (signed) 32 bits of the long. + * @param {number} high The high (signed) 32 bits of the long. + * @struct + * @constructor + * @final + */ +goog.math.Long = function(low, high) { + /** + * @type {number} + * @private + */ + this.low_ = low | 0; // force into 32 signed bits. + + /** + * @type {number} + * @private + */ + this.high_ = high | 0; // force into 32 signed bits. +}; + + +// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the +// from* methods on which they depend. + + +/** + * A cache of the Long representations of small integer values. + * @type {!Object<number, !goog.math.Long>} + * @private + */ +goog.math.Long.IntCache_ = {}; + + +/** + * A cache of the Long representations of common values. + * @type {!Object<goog.math.Long.ValueCacheId_, !goog.math.Long>} + * @private + */ +goog.math.Long.valueCache_ = {}; + +/** + * Returns a cached long number representing the given (32-bit) integer value. + * @param {number} value The 32-bit integer in question. + * @return {!goog.math.Long} The corresponding Long value. + * @private + */ +goog.math.Long.getCachedIntValue_ = function(value) { + return goog.reflect.cache(goog.math.Long.IntCache_, value, function(val) { + return new goog.math.Long(val, val < 0 ? -1 : 0); + }); +}; + +/** + * The array of maximum values of a Long in string representation for a given + * radix between 2 and 36, inclusive. + * @private @const {!Array<string>} + */ +goog.math.Long.MAX_VALUE_FOR_RADIX_ = [ + '', '', // unused + '111111111111111111111111111111111111111111111111111111111111111', + // base 2 + '2021110011022210012102010021220101220221', // base 3 + '13333333333333333333333333333333', // base 4 + '1104332401304422434310311212', // base 5 + '1540241003031030222122211', // base 6 + '22341010611245052052300', // base 7 + '777777777777777777777', // base 8 + '67404283172107811827', // base 9 + '9223372036854775807', // base 10 + '1728002635214590697', // base 11 + '41a792678515120367', // base 12 + '10b269549075433c37', // base 13 + '4340724c6c71dc7a7', // base 14 + '160e2ad3246366807', // base 15 + '7fffffffffffffff', // base 16 + '33d3d8307b214008', // base 17 + '16agh595df825fa7', // base 18 + 'ba643dci0ffeehh', // base 19 + '5cbfjia3fh26ja7', // base 20 + '2heiciiie82dh97', // base 21 + '1adaibb21dckfa7', // base 22 + 'i6k448cf4192c2', // base 23 + 'acd772jnc9l0l7', // base 24 + '64ie1focnn5g77', // base 25 + '3igoecjbmca687', // base 26 + '27c48l5b37oaop', // base 27 + '1bk39f3ah3dmq7', // base 28 + 'q1se8f0m04isb', // base 29 + 'hajppbc1fc207', // base 30 + 'bm03i95hia437', // base 31 + '7vvvvvvvvvvvv', // base 32 + '5hg4ck9jd4u37', // base 33 + '3tdtk1v8j6tpp', // base 34 + '2pijmikexrxp7', // base 35 + '1y2p0ij32e8e7' // base 36 +]; + + +/** + * The array of minimum values of a Long in string representation for a given + * radix between 2 and 36, inclusive. + * @private @const {!Array<string>} + */ +goog.math.Long.MIN_VALUE_FOR_RADIX_ = [ + '', '', // unused + '-1000000000000000000000000000000000000000000000000000000000000000', + // base 2 + '-2021110011022210012102010021220101220222', // base 3 + '-20000000000000000000000000000000', // base 4 + '-1104332401304422434310311213', // base 5 + '-1540241003031030222122212', // base 6 + '-22341010611245052052301', // base 7 + '-1000000000000000000000', // base 8 + '-67404283172107811828', // base 9 + '-9223372036854775808', // base 10 + '-1728002635214590698', // base 11 + '-41a792678515120368', // base 12 + '-10b269549075433c38', // base 13 + '-4340724c6c71dc7a8', // base 14 + '-160e2ad3246366808', // base 15 + '-8000000000000000', // base 16 + '-33d3d8307b214009', // base 17 + '-16agh595df825fa8', // base 18 + '-ba643dci0ffeehi', // base 19 + '-5cbfjia3fh26ja8', // base 20 + '-2heiciiie82dh98', // base 21 + '-1adaibb21dckfa8', // base 22 + '-i6k448cf4192c3', // base 23 + '-acd772jnc9l0l8', // base 24 + '-64ie1focnn5g78', // base 25 + '-3igoecjbmca688', // base 26 + '-27c48l5b37oaoq', // base 27 + '-1bk39f3ah3dmq8', // base 28 + '-q1se8f0m04isc', // base 29 + '-hajppbc1fc208', // base 30 + '-bm03i95hia438', // base 31 + '-8000000000000', // base 32 + '-5hg4ck9jd4u38', // base 33 + '-3tdtk1v8j6tpq', // base 34 + '-2pijmikexrxp8', // base 35 + '-1y2p0ij32e8e8' // base 36 +]; + + +/** + * Returns a Long representing the given (32-bit) integer value. + * @param {number} value The 32-bit integer in question. + * @return {!goog.math.Long} The corresponding Long value. + */ +goog.math.Long.fromInt = function(value) { + var intValue = value | 0; + goog.asserts.assert(value === intValue, 'value should be a 32-bit integer'); + + if (-128 <= intValue && intValue < 128) { + return goog.math.Long.getCachedIntValue_(intValue); + } else { + return new goog.math.Long(intValue, intValue < 0 ? -1 : 0); + } +}; + + +/** + * Returns a Long representing the given value. + * NaN will be returned as zero. Infinity is converted to max value and + * -Infinity to min value. + * @param {number} value The number in question. + * @return {!goog.math.Long} The corresponding Long value. + */ +goog.math.Long.fromNumber = function(value) { + if (isNaN(value)) { + return goog.math.Long.getZero(); + } else if (value <= -goog.math.Long.TWO_PWR_63_DBL_) { + return goog.math.Long.getMinValue(); + } else if (value + 1 >= goog.math.Long.TWO_PWR_63_DBL_) { + return goog.math.Long.getMaxValue(); + } else if (value < 0) { + return goog.math.Long.fromNumber(-value).negate(); + } else { + return new goog.math.Long( + (value % goog.math.Long.TWO_PWR_32_DBL_) | 0, + (value / goog.math.Long.TWO_PWR_32_DBL_) | 0); + } +}; + + +/** + * Returns a Long representing the 64-bit integer that comes by concatenating + * the given high and low bits. Each is assumed to use 32 bits. + * @param {number} lowBits The low 32-bits. + * @param {number} highBits The high 32-bits. + * @return {!goog.math.Long} The corresponding Long value. + */ +goog.math.Long.fromBits = function(lowBits, highBits) { + return new goog.math.Long(lowBits, highBits); +}; + + +/** + * Returns a Long representation of the given string, written using the given + * radix. + * @param {string} str The textual representation of the Long. + * @param {number=} opt_radix The radix in which the text is written. + * @return {!goog.math.Long} The corresponding Long value. + */ +goog.math.Long.fromString = function(str, opt_radix) { + if (str.length == 0) { + throw new Error('number format error: empty string'); + } + + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw new Error('radix out of range: ' + radix); + } + + if (str.charAt(0) == '-') { + return goog.math.Long.fromString(str.substring(1), radix).negate(); + } else if (str.indexOf('-') >= 0) { + throw new Error('number format error: interior "-" character: ' + str); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 8)); + + var result = goog.math.Long.getZero(); + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i); + var value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = goog.math.Long.fromNumber(Math.pow(radix, size)); + result = result.multiply(power).add(goog.math.Long.fromNumber(value)); + } else { + result = result.multiply(radixToPower); + result = result.add(goog.math.Long.fromNumber(value)); + } + } + return result; +}; + +/** + * Returns the boolean value of whether the input string is within a Long's + * range. Assumes an input string containing only numeric characters with an + * optional preceding '-'. + * @param {string} str The textual representation of the Long. + * @param {number=} opt_radix The radix in which the text is written. + * @return {boolean} Whether the string is within the range of a Long. + */ +goog.math.Long.isStringInRange = function(str, opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw new Error('radix out of range: ' + radix); + } + + var extremeValue = (str.charAt(0) == '-') ? + goog.math.Long.MIN_VALUE_FOR_RADIX_[radix] : + goog.math.Long.MAX_VALUE_FOR_RADIX_[radix]; + + if (str.length < extremeValue.length) { + return true; + } else if (str.length == extremeValue.length && str <= extremeValue) { + return true; + } else { + return false; + } +}; + +// NOTE: the compiler should inline these constant values below and then remove +// these variables, so there should be no runtime penalty for these. + + +/** + * Number used repeated below in calculations. This must appear before the + * first call to any from* function below. + * @type {number} + * @private + */ +goog.math.Long.TWO_PWR_16_DBL_ = 1 << 16; + + +/** + * @type {number} + * @private + */ +goog.math.Long.TWO_PWR_32_DBL_ = + goog.math.Long.TWO_PWR_16_DBL_ * goog.math.Long.TWO_PWR_16_DBL_; + + +/** + * @type {number} + * @private + */ +goog.math.Long.TWO_PWR_64_DBL_ = + goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_32_DBL_; + + +/** + * @type {number} + * @private + */ +goog.math.Long.TWO_PWR_63_DBL_ = goog.math.Long.TWO_PWR_64_DBL_ / 2; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getZero = function() { + return goog.math.Long.getCachedIntValue_(0); +}; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getOne = function() { + return goog.math.Long.getCachedIntValue_(1); +}; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getNegOne = function() { + return goog.math.Long.getCachedIntValue_(-1); +}; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getMaxValue = function() { + return goog.reflect.cache( + goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MAX_VALUE, + function() { + return goog.math.Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); + }); +}; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getMinValue = function() { + return goog.reflect.cache( + goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MIN_VALUE, + function() { return goog.math.Long.fromBits(0, 0x80000000 | 0); }); +}; + + +/** + * @return {!goog.math.Long} + * @public + */ +goog.math.Long.getTwoPwr24 = function() { + return goog.reflect.cache( + goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.TWO_PWR_24, + function() { return goog.math.Long.fromInt(1 << 24); }); +}; + + +/** @return {number} The value, assuming it is a 32-bit integer. */ +goog.math.Long.prototype.toInt = function() { + return this.low_; +}; + + +/** @return {number} The closest floating-point representation to this value. */ +goog.math.Long.prototype.toNumber = function() { + return this.high_ * goog.math.Long.TWO_PWR_32_DBL_ + + this.getLowBitsUnsigned(); +}; + + +/** + * @param {number=} opt_radix The radix in which the text should be written. + * @return {string} The textual representation of this value. + * @override + */ +goog.math.Long.prototype.toString = function(opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw new Error('radix out of range: ' + radix); + } + + if (this.isZero()) { + return '0'; + } + + if (this.isNegative()) { + if (this.equals(goog.math.Long.getMinValue())) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = goog.math.Long.fromNumber(radix); + var div = this.div(radixLong); + var rem = div.multiply(radixLong).subtract(this); + return div.toString(radix) + rem.toInt().toString(radix); + } else { + return '-' + this.negate().toString(radix); + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 6)); + + var rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower); + // The right shifting fixes negative values in the case when + // intval >= 2^31; for more details see + // https://github.com/google/closure-library/pull/498 + var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() >>> 0; + var digits = intval.toString(radix); + + rem = remDiv; + if (rem.isZero()) { + return digits + result; + } else { + while (digits.length < 6) { + digits = '0' + digits; + } + result = '' + digits + result; + } + } +}; + + +/** @return {number} The high 32-bits as a signed value. */ +goog.math.Long.prototype.getHighBits = function() { + return this.high_; +}; + + +/** @return {number} The low 32-bits as a signed value. */ +goog.math.Long.prototype.getLowBits = function() { + return this.low_; +}; + + +/** @return {number} The low 32-bits as an unsigned value. */ +goog.math.Long.prototype.getLowBitsUnsigned = function() { + return (this.low_ >= 0) ? this.low_ : + goog.math.Long.TWO_PWR_32_DBL_ + this.low_; +}; + + +/** + * @return {number} Returns the number of bits needed to represent the absolute + * value of this Long. + */ +goog.math.Long.prototype.getNumBitsAbs = function() { + if (this.isNegative()) { + if (this.equals(goog.math.Long.getMinValue())) { + return 64; + } else { + return this.negate().getNumBitsAbs(); + } + } else { + var val = this.high_ != 0 ? this.high_ : this.low_; + for (var bit = 31; bit > 0; bit--) { + if ((val & (1 << bit)) != 0) { + break; + } + } + return this.high_ != 0 ? bit + 33 : bit + 1; + } +}; + + +/** @return {boolean} Whether this value is zero. */ +goog.math.Long.prototype.isZero = function() { + return this.high_ == 0 && this.low_ == 0; +}; + + +/** @return {boolean} Whether this value is negative. */ +goog.math.Long.prototype.isNegative = function() { + return this.high_ < 0; +}; + + +/** @return {boolean} Whether this value is odd. */ +goog.math.Long.prototype.isOdd = function() { + return (this.low_ & 1) == 1; +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long equals the other. + */ +goog.math.Long.prototype.equals = function(other) { + return (this.high_ == other.high_) && (this.low_ == other.low_); +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long does not equal the other. + */ +goog.math.Long.prototype.notEquals = function(other) { + return (this.high_ != other.high_) || (this.low_ != other.low_); +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is less than the other. + */ +goog.math.Long.prototype.lessThan = function(other) { + return this.compare(other) < 0; +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is less than or equal to the other. + */ +goog.math.Long.prototype.lessThanOrEqual = function(other) { + return this.compare(other) <= 0; +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is greater than the other. + */ +goog.math.Long.prototype.greaterThan = function(other) { + return this.compare(other) > 0; +}; + + +/** + * @param {goog.math.Long} other Long to compare against. + * @return {boolean} Whether this Long is greater than or equal to the other. + */ +goog.math.Long.prototype.greaterThanOrEqual = function(other) { + return this.compare(other) >= 0; +}; + + +/** + * Compares this Long with the given one. + * @param {goog.math.Long} other Long to compare against. + * @return {number} 0 if they are the same, 1 if the this is greater, and -1 + * if the given one is greater. + */ +goog.math.Long.prototype.compare = function(other) { + if (this.equals(other)) { + return 0; + } + + var thisNeg = this.isNegative(); + var otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) { + return -1; + } + if (!thisNeg && otherNeg) { + return 1; + } + + // at this point, the signs are the same, so subtraction will not overflow + if (this.subtract(other).isNegative()) { + return -1; + } else { + return 1; + } +}; + + +/** @return {!goog.math.Long} The negation of this value. */ +goog.math.Long.prototype.negate = function() { + if (this.equals(goog.math.Long.getMinValue())) { + return goog.math.Long.getMinValue(); + } else { + return this.not().add(goog.math.Long.getOne()); + } +}; + + +/** + * Returns the sum of this and the given Long. + * @param {goog.math.Long} other Long to add to this one. + * @return {!goog.math.Long} The sum of this and the given Long. + */ +goog.math.Long.prototype.add = function(other) { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + + +/** + * Returns the difference of this and the given Long. + * @param {goog.math.Long} other Long to subtract from this. + * @return {!goog.math.Long} The difference of this and the given Long. + */ +goog.math.Long.prototype.subtract = function(other) { + return this.add(other.negate()); +}; + + +/** + * Returns the product of this and the given long. + * @param {goog.math.Long} other Long to multiply with this. + * @return {!goog.math.Long} The product of this and the other. + */ +goog.math.Long.prototype.multiply = function(other) { + if (this.isZero()) { + return goog.math.Long.getZero(); + } else if (other.isZero()) { + return goog.math.Long.getZero(); + } + + if (this.equals(goog.math.Long.getMinValue())) { + return other.isOdd() ? goog.math.Long.getMinValue() : + goog.math.Long.getZero(); + } else if (other.equals(goog.math.Long.getMinValue())) { + return this.isOdd() ? goog.math.Long.getMinValue() : + goog.math.Long.getZero(); + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().multiply(other.negate()); + } else { + return this.negate().multiply(other).negate(); + } + } else if (other.isNegative()) { + return this.multiply(other.negate()).negate(); + } + + // If both longs are small, use float multiplication + if (this.lessThan(goog.math.Long.getTwoPwr24()) && + other.lessThan(goog.math.Long.getTwoPwr24())) { + return goog.math.Long.fromNumber(this.toNumber() * other.toNumber()); + } + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + + +/** + * Returns this Long divided by the given one. + * @param {goog.math.Long} other Long by which to divide. + * @return {!goog.math.Long} This Long divided by the given one. + */ +goog.math.Long.prototype.div = function(other) { + if (other.isZero()) { + throw new Error('division by zero'); + } else if (this.isZero()) { + return goog.math.Long.getZero(); + } + + if (this.equals(goog.math.Long.getMinValue())) { + if (other.equals(goog.math.Long.getOne()) || + other.equals(goog.math.Long.getNegOne())) { + return goog.math.Long.getMinValue(); // recall -MIN_VALUE == MIN_VALUE + } else if (other.equals(goog.math.Long.getMinValue())) { + return goog.math.Long.getOne(); + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shiftRight(1); + var approx = halfThis.div(other).shiftLeft(1); + if (approx.equals(goog.math.Long.getZero())) { + return other.isNegative() ? goog.math.Long.getOne() : + goog.math.Long.getNegOne(); + } else { + var rem = this.subtract(other.multiply(approx)); + var result = approx.add(rem.div(other)); + return result; + } + } + } else if (other.equals(goog.math.Long.getMinValue())) { + return goog.math.Long.getZero(); + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().div(other.negate()); + } else { + return this.negate().div(other).negate(); + } + } else if (other.isNegative()) { + return this.div(other.negate()).negate(); + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = goog.math.Long.getZero(); + var rem = this; + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2); + var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = goog.math.Long.fromNumber(approx); + var approxRem = approxRes.multiply(other); + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta; + approxRes = goog.math.Long.fromNumber(approx); + approxRem = approxRes.multiply(other); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = goog.math.Long.getOne(); + } + + res = res.add(approxRes); + rem = rem.subtract(approxRem); + } + return res; +}; + + +/** + * Returns this Long modulo the given one. + * @param {goog.math.Long} other Long by which to mod. + * @return {!goog.math.Long} This Long modulo the given one. + */ +goog.math.Long.prototype.modulo = function(other) { + return this.subtract(this.div(other).multiply(other)); +}; + + +/** @return {!goog.math.Long} The bitwise-NOT of this value. */ +goog.math.Long.prototype.not = function() { + return goog.math.Long.fromBits(~this.low_, ~this.high_); +}; + + +/** + * Returns the bitwise-AND of this Long and the given one. + * @param {goog.math.Long} other The Long with which to AND. + * @return {!goog.math.Long} The bitwise-AND of this and the other. + */ +goog.math.Long.prototype.and = function(other) { + return goog.math.Long.fromBits( + this.low_ & other.low_, this.high_ & other.high_); +}; + + +/** + * Returns the bitwise-OR of this Long and the given one. + * @param {goog.math.Long} other The Long with which to OR. + * @return {!goog.math.Long} The bitwise-OR of this and the other. + */ +goog.math.Long.prototype.or = function(other) { + return goog.math.Long.fromBits( + this.low_ | other.low_, this.high_ | other.high_); +}; + + +/** + * Returns the bitwise-XOR of this Long and the given one. + * @param {goog.math.Long} other The Long with which to XOR. + * @return {!goog.math.Long} The bitwise-XOR of this and the other. + */ +goog.math.Long.prototype.xor = function(other) { + return goog.math.Long.fromBits( + this.low_ ^ other.low_, this.high_ ^ other.high_); +}; + + +/** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the left by the given amount. + */ +goog.math.Long.prototype.shiftLeft = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var low = this.low_; + if (numBits < 32) { + var high = this.high_; + return goog.math.Long.fromBits( + low << numBits, (high << numBits) | (low >>> (32 - numBits))); + } else { + return goog.math.Long.fromBits(0, low << (numBits - 32)); + } + } +}; + + +/** + * Returns this Long with bits shifted to the right by the given amount. + * The new leading bits match the current sign bit. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the right by the given amount. + */ +goog.math.Long.prototype.shiftRight = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return goog.math.Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), high >> numBits); + } else { + return goog.math.Long.fromBits( + high >> (numBits - 32), high >= 0 ? 0 : -1); + } + } +}; + + +/** + * Returns this Long with bits shifted to the right by the given amount, with + * zeros placed into the new leading bits. + * @param {number} numBits The number of bits by which to shift. + * @return {!goog.math.Long} This shifted to the right by the given amount, with + * zeros placed into the new leading bits. + */ +goog.math.Long.prototype.shiftRightUnsigned = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return goog.math.Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), high >>> numBits); + } else if (numBits == 32) { + return goog.math.Long.fromBits(high, 0); + } else { + return goog.math.Long.fromBits(high >>> (numBits - 32), 0); + } + } +}; + + +/** + * @enum {number} Ids of commonly requested Long instances. + * @private + */ +goog.math.Long.ValueCacheId_ = { + MAX_VALUE: 1, + MIN_VALUE: 2, + TWO_PWR_24: 6 +};
diff --git a/third_party/ink/closure/math/math.js b/third_party/ink/closure/math/math.js new file mode 100644 index 0000000..a251005b --- /dev/null +++ b/third_party/ink/closure/math/math.js
@@ -0,0 +1,449 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Additional mathematical functions. + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.math'); + +goog.require('goog.array'); +goog.require('goog.asserts'); + + +/** + * Returns a random integer greater than or equal to 0 and less than {@code a}. + * @param {number} a The upper bound for the random integer (exclusive). + * @return {number} A random integer N such that 0 <= N < a. + */ +goog.math.randomInt = function(a) { + return Math.floor(Math.random() * a); +}; + + +/** + * Returns a random number greater than or equal to {@code a} and less than + * {@code b}. + * @param {number} a The lower bound for the random number (inclusive). + * @param {number} b The upper bound for the random number (exclusive). + * @return {number} A random number N such that a <= N < b. + */ +goog.math.uniformRandom = function(a, b) { + return a + Math.random() * (b - a); +}; + + +/** + * Takes a number and clamps it to within the provided bounds. + * @param {number} value The input number. + * @param {number} min The minimum value to return. + * @param {number} max The maximum value to return. + * @return {number} The input number if it is within bounds, or the nearest + * number within the bounds. + */ +goog.math.clamp = function(value, min, max) { + return Math.min(Math.max(value, min), max); +}; + + +/** + * The % operator in JavaScript returns the remainder of a / b, but differs from + * some other languages in that the result will have the same sign as the + * dividend. For example, -1 % 8 == -1, whereas in some other languages + * (such as Python) the result would be 7. This function emulates the more + * correct modulo behavior, which is useful for certain applications such as + * calculating an offset index in a circular list. + * + * @param {number} a The dividend. + * @param {number} b The divisor. + * @return {number} a % b where the result is between 0 and b (either 0 <= x < b + * or b < x <= 0, depending on the sign of b). + */ +goog.math.modulo = function(a, b) { + var r = a % b; + // If r and b differ in sign, add b to wrap the result to the correct sign. + return (r * b < 0) ? r + b : r; +}; + + +/** + * Performs linear interpolation between values a and b. Returns the value + * between a and b proportional to x (when x is between 0 and 1. When x is + * outside this range, the return value is a linear extrapolation). + * @param {number} a A number. + * @param {number} b A number. + * @param {number} x The proportion between a and b. + * @return {number} The interpolated value between a and b. + */ +goog.math.lerp = function(a, b, x) { + return a + x * (b - a); +}; + + +/** + * Tests whether the two values are equal to each other, within a certain + * tolerance to adjust for floating point errors. + * @param {number} a A number. + * @param {number} b A number. + * @param {number=} opt_tolerance Optional tolerance range. Defaults + * to 0.000001. If specified, should be greater than 0. + * @return {boolean} Whether {@code a} and {@code b} are nearly equal. + */ +goog.math.nearlyEquals = function(a, b, opt_tolerance) { + return Math.abs(a - b) <= (opt_tolerance || 0.000001); +}; + + +// TODO(jrajeshwar): Rename to normalizeAngle, retaining old name as deprecated +// alias. +/** + * Normalizes an angle to be in range [0-360). Angles outside this range will + * be normalized to be the equivalent angle with that range. + * @param {number} angle Angle in degrees. + * @return {number} Standardized angle. + */ +goog.math.standardAngle = function(angle) { + return goog.math.modulo(angle, 360); +}; + + +/** + * Normalizes an angle to be in range [0-2*PI). Angles outside this range will + * be normalized to be the equivalent angle with that range. + * @param {number} angle Angle in radians. + * @return {number} Standardized angle. + */ +goog.math.standardAngleInRadians = function(angle) { + return goog.math.modulo(angle, 2 * Math.PI); +}; + + +/** + * Converts degrees to radians. + * @param {number} angleDegrees Angle in degrees. + * @return {number} Angle in radians. + */ +goog.math.toRadians = function(angleDegrees) { + return angleDegrees * Math.PI / 180; +}; + + +/** + * Converts radians to degrees. + * @param {number} angleRadians Angle in radians. + * @return {number} Angle in degrees. + */ +goog.math.toDegrees = function(angleRadians) { + return angleRadians * 180 / Math.PI; +}; + + +/** + * For a given angle and radius, finds the X portion of the offset. + * @param {number} degrees Angle in degrees (zero points in +X direction). + * @param {number} radius Radius. + * @return {number} The x-distance for the angle and radius. + */ +goog.math.angleDx = function(degrees, radius) { + return radius * Math.cos(goog.math.toRadians(degrees)); +}; + + +/** + * For a given angle and radius, finds the Y portion of the offset. + * @param {number} degrees Angle in degrees (zero points in +X direction). + * @param {number} radius Radius. + * @return {number} The y-distance for the angle and radius. + */ +goog.math.angleDy = function(degrees, radius) { + return radius * Math.sin(goog.math.toRadians(degrees)); +}; + + +/** + * Computes the angle between two points (x1,y1) and (x2,y2). + * Angle zero points in the +X direction, 90 degrees points in the +Y + * direction (down) and from there we grow clockwise towards 360 degrees. + * @param {number} x1 x of first point. + * @param {number} y1 y of first point. + * @param {number} x2 x of second point. + * @param {number} y2 y of second point. + * @return {number} Standardized angle in degrees of the vector from + * x1,y1 to x2,y2. + */ +goog.math.angle = function(x1, y1, x2, y2) { + return goog.math.standardAngle( + goog.math.toDegrees(Math.atan2(y2 - y1, x2 - x1))); +}; + + +/** + * Computes the difference between startAngle and endAngle (angles in degrees). + * @param {number} startAngle Start angle in degrees. + * @param {number} endAngle End angle in degrees. + * @return {number} The number of degrees that when added to + * startAngle will result in endAngle. Positive numbers mean that the + * direction is clockwise. Negative numbers indicate a counter-clockwise + * direction. + * The shortest route (clockwise vs counter-clockwise) between the angles + * is used. + * When the difference is 180 degrees, the function returns 180 (not -180) + * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10. + * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20. + */ +goog.math.angleDifference = function(startAngle, endAngle) { + var d = + goog.math.standardAngle(endAngle) - goog.math.standardAngle(startAngle); + if (d > 180) { + d = d - 360; + } else if (d <= -180) { + d = 360 + d; + } + return d; +}; + + +/** + * Returns the sign of a number as per the "sign" or "signum" function. + * @param {number} x The number to take the sign of. + * @return {number} -1 when negative, 1 when positive, 0 when 0. Preserves + * signed zeros and NaN. + */ +goog.math.sign = function(x) { + if (x > 0) { + return 1; + } + if (x < 0) { + return -1; + } + return x; // Preserves signed zeros and NaN. +}; + + +/** + * JavaScript implementation of Longest Common Subsequence problem. + * http://en.wikipedia.org/wiki/Longest_common_subsequence + * + * Returns the longest possible array that is subarray of both of given arrays. + * + * @param {IArrayLike<S>} array1 First array of objects. + * @param {IArrayLike<T>} array2 Second array of objects. + * @param {Function=} opt_compareFn Function that acts as a custom comparator + * for the array ojects. Function should return true if objects are equal, + * otherwise false. + * @param {Function=} opt_collectorFn Function used to decide what to return + * as a result subsequence. It accepts 2 arguments: index of common element + * in the first array and index in the second. The default function returns + * element from the first array. + * @return {!Array<S|T>} A list of objects that are common to both arrays + * such that there is no common subsequence with size greater than the + * length of the list. + * @template S,T + */ +goog.math.longestCommonSubsequence = function( + array1, array2, opt_compareFn, opt_collectorFn) { + + var compare = opt_compareFn || function(a, b) { return a == b; }; + + var collect = opt_collectorFn || function(i1, i2) { return array1[i1]; }; + + var length1 = array1.length; + var length2 = array2.length; + + var arr = []; + for (var i = 0; i < length1 + 1; i++) { + arr[i] = []; + arr[i][0] = 0; + } + + for (var j = 0; j < length2 + 1; j++) { + arr[0][j] = 0; + } + + for (i = 1; i <= length1; i++) { + for (j = 1; j <= length2; j++) { + if (compare(array1[i - 1], array2[j - 1])) { + arr[i][j] = arr[i - 1][j - 1] + 1; + } else { + arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]); + } + } + } + + // Backtracking + var result = []; + var i = length1, j = length2; + while (i > 0 && j > 0) { + if (compare(array1[i - 1], array2[j - 1])) { + result.unshift(collect(i - 1, j - 1)); + i--; + j--; + } else { + if (arr[i - 1][j] > arr[i][j - 1]) { + i--; + } else { + j--; + } + } + } + + return result; +}; + + +/** + * Returns the sum of the arguments. + * @param {...number} var_args Numbers to add. + * @return {number} The sum of the arguments (0 if no arguments were provided, + * {@code NaN} if any of the arguments is not a valid number). + */ +goog.math.sum = function(var_args) { + return /** @type {number} */ ( + goog.array.reduce( + arguments, function(sum, value) { return sum + value; }, 0)); +}; + + +/** + * Returns the arithmetic mean of the arguments. + * @param {...number} var_args Numbers to average. + * @return {number} The average of the arguments ({@code NaN} if no arguments + * were provided or any of the arguments is not a valid number). + */ +goog.math.average = function(var_args) { + return goog.math.sum.apply(null, arguments) / arguments.length; +}; + + +/** + * Returns the unbiased sample variance of the arguments. For a definition, + * see e.g. http://en.wikipedia.org/wiki/Variance + * @param {...number} var_args Number samples to analyze. + * @return {number} The unbiased sample variance of the arguments (0 if fewer + * than two samples were provided, or {@code NaN} if any of the samples is + * not a valid number). + */ +goog.math.sampleVariance = function(var_args) { + var sampleSize = arguments.length; + if (sampleSize < 2) { + return 0; + } + + var mean = goog.math.average.apply(null, arguments); + var variance = + goog.math.sum.apply(null, goog.array.map(arguments, function(val) { + return Math.pow(val - mean, 2); + })) / (sampleSize - 1); + + return variance; +}; + + +/** + * Returns the sample standard deviation of the arguments. For a definition of + * sample standard deviation, see e.g. + * http://en.wikipedia.org/wiki/Standard_deviation + * @param {...number} var_args Number samples to analyze. + * @return {number} The sample standard deviation of the arguments (0 if fewer + * than two samples were provided, or {@code NaN} if any of the samples is + * not a valid number). + */ +goog.math.standardDeviation = function(var_args) { + return Math.sqrt(goog.math.sampleVariance.apply(null, arguments)); +}; + + +/** + * Returns whether the supplied number represents an integer, i.e. that is has + * no fractional component. No range-checking is performed on the number. + * @param {number} num The number to test. + * @return {boolean} Whether {@code num} is an integer. + */ +goog.math.isInt = function(num) { + return isFinite(num) && num % 1 == 0; +}; + + +/** + * Returns whether the supplied number is finite and not NaN. + * @param {number} num The number to test. + * @return {boolean} Whether {@code num} is a finite number. + * @deprecated Use {@link isFinite} instead. + */ +goog.math.isFiniteNumber = function(num) { + return isFinite(num); +}; + + +/** + * @param {number} num The number to test. + * @return {boolean} Whether it is negative zero. + */ +goog.math.isNegativeZero = function(num) { + return num == 0 && 1 / num < 0; +}; + + +/** + * Returns the precise value of floor(log10(num)). + * Simpler implementations didn't work because of floating point rounding + * errors. For example + * <ul> + * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3. + * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15. + * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1. + * </ul> + * @param {number} num A floating point number. + * @return {number} Its logarithm to base 10 rounded down to the nearest + * integer if num > 0. -Infinity if num == 0. NaN if num < 0. + */ +goog.math.log10Floor = function(num) { + if (num > 0) { + var x = Math.round(Math.log(num) * Math.LOG10E); + return x - (parseFloat('1e' + x) > num ? 1 : 0); + } + return num == 0 ? -Infinity : NaN; +}; + + +/** + * A tweaked variant of {@code Math.floor} which tolerates if the passed number + * is infinitesimally smaller than the closest integer. It often happens with + * the results of floating point calculations because of the finite precision + * of the intermediate results. For example {@code Math.floor(Math.log(1000) / + * Math.LN10) == 2}, not 3 as one would expect. + * @param {number} num A number. + * @param {number=} opt_epsilon An infinitesimally small positive number, the + * rounding error to tolerate. + * @return {number} The largest integer less than or equal to {@code num}. + */ +goog.math.safeFloor = function(num, opt_epsilon) { + goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); + return Math.floor(num + (opt_epsilon || 2e-15)); +}; + + +/** + * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for + * details. + * @param {number} num A number. + * @param {number=} opt_epsilon An infinitesimally small positive number, the + * rounding error to tolerate. + * @return {number} The smallest integer greater than or equal to {@code num}. + */ +goog.math.safeCeil = function(num, opt_epsilon) { + goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0); + return Math.ceil(num - (opt_epsilon || 2e-15)); +};
diff --git a/third_party/ink/closure/math/rect.js b/third_party/ink/closure/math/rect.js new file mode 100644 index 0000000..28bf840 --- /dev/null +++ b/third_party/ink/closure/math/rect.js
@@ -0,0 +1,478 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing rectangles. Some of these + * functions should be migrated over to non-nullable params. + * @author pupius@google.com (Daniel Pupius) + */ + +goog.provide('goog.math.Rect'); + +goog.require('goog.asserts'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.IRect'); +goog.require('goog.math.Size'); + + + +/** + * Class for representing rectangular regions. + * @param {number} x Left. + * @param {number} y Top. + * @param {number} w Width. + * @param {number} h Height. + * @struct + * @constructor + * @implements {goog.math.IRect} + */ +goog.math.Rect = function(x, y, w, h) { + /** @type {number} */ + this.left = x; + + /** @type {number} */ + this.top = y; + + /** @type {number} */ + this.width = w; + + /** @type {number} */ + this.height = h; +}; + + +/** + * @return {!goog.math.Rect} A new copy of this Rectangle. + */ +goog.math.Rect.prototype.clone = function() { + return new goog.math.Rect(this.left, this.top, this.width, this.height); +}; + + +/** + * Returns a new Box object with the same position and dimensions as this + * rectangle. + * @return {!goog.math.Box} A new Box representation of this Rectangle. + */ +goog.math.Rect.prototype.toBox = function() { + var right = this.left + this.width; + var bottom = this.top + this.height; + return new goog.math.Box(this.top, right, bottom, this.left); +}; + + +/** + * Creates a new Rect object with the position and size given. + * @param {!goog.math.Coordinate} position The top-left coordinate of the Rect + * @param {!goog.math.Size} size The size of the Rect + * @return {!goog.math.Rect} A new Rect initialized with the given position and + * size. + */ +goog.math.Rect.createFromPositionAndSize = function(position, size) { + return new goog.math.Rect(position.x, position.y, size.width, size.height); +}; + + +/** + * Creates a new Rect object with the same position and dimensions as a given + * Box. Note that this is only the inverse of toBox if left/top are defined. + * @param {goog.math.Box} box A box. + * @return {!goog.math.Rect} A new Rect initialized with the box's position + * and size. + */ +goog.math.Rect.createFromBox = function(box) { + return new goog.math.Rect( + box.left, box.top, box.right - box.left, box.bottom - box.top); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing size and dimensions of rectangle. + * @return {string} In the form (50, 73 - 75w x 25h). + * @override + */ + goog.math.Rect.prototype.toString = function() { + return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' + + this.height + 'h)'; + }; +} + + +/** + * Compares rectangles for equality. + * @param {goog.math.IRect} a A Rectangle. + * @param {goog.math.IRect} b A Rectangle. + * @return {boolean} True iff the rectangles have the same left, top, width, + * and height, or if both are null. + */ +goog.math.Rect.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.left == b.left && a.width == b.width && a.top == b.top && + a.height == b.height; +}; + + +/** + * Computes the intersection of this rectangle and the rectangle parameter. If + * there is no intersection, returns false and leaves this rectangle as is. + * @param {goog.math.IRect} rect A Rectangle. + * @return {boolean} True iff this rectangle intersects with the parameter. + */ +goog.math.Rect.prototype.intersection = function(rect) { + var x0 = Math.max(this.left, rect.left); + var x1 = Math.min(this.left + this.width, rect.left + rect.width); + + if (x0 <= x1) { + var y0 = Math.max(this.top, rect.top); + var y1 = Math.min(this.top + this.height, rect.top + rect.height); + + if (y0 <= y1) { + this.left = x0; + this.top = y0; + this.width = x1 - x0; + this.height = y1 - y0; + + return true; + } + } + return false; +}; + + +/** + * Returns the intersection of two rectangles. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.IRect} a A Rectangle. + * @param {goog.math.IRect} b A Rectangle. + * @return {goog.math.Rect} A new intersection rect (even if width and height + * are 0), or null if there is no intersection. + */ +goog.math.Rect.intersection = function(a, b) { + // There is no nice way to do intersection via a clone, because any such + // clone might be unnecessary if this function returns null. So, we duplicate + // code from above. + + var x0 = Math.max(a.left, b.left); + var x1 = Math.min(a.left + a.width, b.left + b.width); + + if (x0 <= x1) { + var y0 = Math.max(a.top, b.top); + var y1 = Math.min(a.top + a.height, b.top + b.height); + + if (y0 <= y1) { + return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0); + } + } + return null; +}; + + +/** + * Returns whether two rectangles intersect. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.IRect} a A Rectangle. + * @param {goog.math.IRect} b A Rectangle. + * @return {boolean} Whether a and b intersect. + */ +goog.math.Rect.intersects = function(a, b) { + return ( + a.left <= b.left + b.width && b.left <= a.left + a.width && + a.top <= b.top + b.height && b.top <= a.top + a.height); +}; + + +/** + * Returns whether a rectangle intersects this rectangle. + * @param {goog.math.IRect} rect A rectangle. + * @return {boolean} Whether rect intersects this rectangle. + */ +goog.math.Rect.prototype.intersects = function(rect) { + return goog.math.Rect.intersects(this, rect); +}; + + +/** + * Computes the difference regions between two rectangles. The return value is + * an array of 0 to 4 rectangles defining the remaining regions of the first + * rectangle after the second has been subtracted. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.IRect} b A Rectangle. + * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. + */ +goog.math.Rect.difference = function(a, b) { + var intersection = goog.math.Rect.intersection(a, b); + if (!intersection || !intersection.height || !intersection.width) { + return [a.clone()]; + } + + var result = []; + + var top = a.top; + var height = a.height; + + var ar = a.left + a.width; + var ab = a.top + a.height; + + var br = b.left + b.width; + var bb = b.top + b.height; + + // Subtract off any area on top where A extends past B + if (b.top > a.top) { + result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top)); + top = b.top; + // If we're moving the top down, we also need to subtract the height diff. + height -= b.top - a.top; + } + // Subtract off any area on bottom where A extends past B + if (bb < ab) { + result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb)); + height = bb - top; + } + // Subtract any area on left where A extends past B + if (b.left > a.left) { + result.push(new goog.math.Rect(a.left, top, b.left - a.left, height)); + } + // Subtract any area on right where A extends past B + if (br < ar) { + result.push(new goog.math.Rect(br, top, ar - br, height)); + } + + return result; +}; + + +/** + * Computes the difference regions between this rectangle and {@code rect}. The + * return value is an array of 0 to 4 rectangles defining the remaining regions + * of this rectangle after the other has been subtracted. + * @param {goog.math.IRect} rect A Rectangle. + * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. + */ +goog.math.Rect.prototype.difference = function(rect) { + return goog.math.Rect.difference(this, rect); +}; + + +/** + * Expand this rectangle to also include the area of the given rectangle. + * @param {goog.math.IRect} rect The other rectangle. + */ +goog.math.Rect.prototype.boundingRect = function(rect) { + // We compute right and bottom before we change left and top below. + var right = Math.max(this.left + this.width, rect.left + rect.width); + var bottom = Math.max(this.top + this.height, rect.top + rect.height); + + this.left = Math.min(this.left, rect.left); + this.top = Math.min(this.top, rect.top); + + this.width = right - this.left; + this.height = bottom - this.top; +}; + + +/** + * Returns a new rectangle which completely contains both input rectangles. + * @param {goog.math.IRect} a A rectangle. + * @param {goog.math.IRect} b A rectangle. + * @return {goog.math.Rect} A new bounding rect, or null if either rect is + * null. + */ +goog.math.Rect.boundingRect = function(a, b) { + if (!a || !b) { + return null; + } + + var newRect = new goog.math.Rect(a.left, a.top, a.width, a.height); + newRect.boundingRect(b); + + return newRect; +}; + + +/** + * Tests whether this rectangle entirely contains another rectangle or + * coordinate. + * + * @param {goog.math.IRect|goog.math.Coordinate} another The rectangle or + * coordinate to test for containment. + * @return {boolean} Whether this rectangle contains given rectangle or + * coordinate. + */ +goog.math.Rect.prototype.contains = function(another) { + if (another instanceof goog.math.Coordinate) { + return another.x >= this.left && another.x <= this.left + this.width && + another.y >= this.top && another.y <= this.top + this.height; + } else { // (another instanceof goog.math.IRect) + return this.left <= another.left && + this.left + this.width >= another.left + another.width && + this.top <= another.top && + this.top + this.height >= another.top + another.height; + } +}; + + +/** + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The squared distance between the point and the closest + * point inside the rectangle. Returns 0 if the point is inside the + * rectangle. + */ +goog.math.Rect.prototype.squaredDistance = function(point) { + var dx = point.x < this.left ? + this.left - point.x : + Math.max(point.x - (this.left + this.width), 0); + var dy = point.y < this.top ? this.top - point.y : + Math.max(point.y - (this.top + this.height), 0); + return dx * dx + dy * dy; +}; + + +/** + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The distance between the point and the closest point + * inside the rectangle. Returns 0 if the point is inside the rectangle. + */ +goog.math.Rect.prototype.distance = function(point) { + return Math.sqrt(this.squaredDistance(point)); +}; + + +/** + * @return {!goog.math.Size} The size of this rectangle. + */ +goog.math.Rect.prototype.getSize = function() { + return new goog.math.Size(this.width, this.height); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of + * the rectangle. + */ +goog.math.Rect.prototype.getTopLeft = function() { + return new goog.math.Coordinate(this.left, this.top); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the center of the + * rectangle. + */ +goog.math.Rect.prototype.getCenter = function() { + return new goog.math.Coordinate( + this.left + this.width / 2, this.top + this.height / 2); +}; + + +/** + * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner + * of the rectangle. + */ +goog.math.Rect.prototype.getBottomRight = function() { + return new goog.math.Coordinate( + this.left + this.width, this.top + this.height); +}; + + +/** + * Rounds the fields to the next larger integer values. + * @return {!goog.math.Rect} This rectangle with ceil'd fields. + */ +goog.math.Rect.prototype.ceil = function() { + this.left = Math.ceil(this.left); + this.top = Math.ceil(this.top); + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; +}; + + +/** + * Rounds the fields to the next smaller integer values. + * @return {!goog.math.Rect} This rectangle with floored fields. + */ +goog.math.Rect.prototype.floor = function() { + this.left = Math.floor(this.left); + this.top = Math.floor(this.top); + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; +}; + + +/** + * Rounds the fields to nearest integer values. + * @return {!goog.math.Rect} This rectangle with rounded fields. + */ +goog.math.Rect.prototype.round = function() { + this.left = Math.round(this.left); + this.top = Math.round(this.top); + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; +}; + + +/** + * Translates this rectangle by the given offsets. If a + * {@code goog.math.Coordinate} is given, then the left and top values are + * translated by the coordinate's x and y values. Otherwise, top and left are + * translated by {@code tx} and {@code opt_ty} respectively. + * @param {number|goog.math.Coordinate} tx The value to translate left by or the + * the coordinate to translate this rect by. + * @param {number=} opt_ty The value to translate top by. + * @return {!goog.math.Rect} This rectangle after translating. + */ +goog.math.Rect.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.top += tx.y; + } else { + this.left += goog.asserts.assertNumber(tx); + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + } + } + return this; +}; + + +/** + * Scales this rectangle by the given scale factors. The left and width values + * are scaled by {@code sx} and the top and height values are scaled by + * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled + * by {@code sx}. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Rect} This rectangle after scaling. + */ +goog.math.Rect.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.width *= sx; + this.top *= sy; + this.height *= sy; + return this; +};
diff --git a/third_party/ink/closure/math/size.js b/third_party/ink/closure/math/size.js new file mode 100644 index 0000000..b539d0c --- /dev/null +++ b/third_party/ink/closure/math/size.js
@@ -0,0 +1,228 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing two-dimensional sizes. + * @author pupius@google.com (Dan Pupius) + * @author brenneman@google.com (Shawn Brenneman) + */ + + +goog.provide('goog.math.Size'); + + + +/** + * Class for representing sizes consisting of a width and height. Undefined + * width and height support is deprecated and results in compiler warning. + * @param {number} width Width. + * @param {number} height Height. + * @struct + * @constructor + */ +goog.math.Size = function(width, height) { + /** + * Width + * @type {number} + */ + this.width = width; + + /** + * Height + * @type {number} + */ + this.height = height; +}; + + +/** + * Compares sizes for equality. + * @param {goog.math.Size} a A Size. + * @param {goog.math.Size} b A Size. + * @return {boolean} True iff the sizes have equal widths and equal + * heights, or if both are null. + */ +goog.math.Size.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.width == b.width && a.height == b.height; +}; + + +/** + * @return {!goog.math.Size} A new copy of the Size. + */ +goog.math.Size.prototype.clone = function() { + return new goog.math.Size(this.width, this.height); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing size. + * @return {string} In the form (50 x 73). + * @override + */ + goog.math.Size.prototype.toString = function() { + return '(' + this.width + ' x ' + this.height + ')'; + }; +} + + +/** + * @return {number} The longer of the two dimensions in the size. + */ +goog.math.Size.prototype.getLongest = function() { + return Math.max(this.width, this.height); +}; + + +/** + * @return {number} The shorter of the two dimensions in the size. + */ +goog.math.Size.prototype.getShortest = function() { + return Math.min(this.width, this.height); +}; + + +/** + * @return {number} The area of the size (width * height). + */ +goog.math.Size.prototype.area = function() { + return this.width * this.height; +}; + + +/** + * @return {number} The perimeter of the size (width + height) * 2. + */ +goog.math.Size.prototype.perimeter = function() { + return (this.width + this.height) * 2; +}; + + +/** + * @return {number} The ratio of the size's width to its height. + */ +goog.math.Size.prototype.aspectRatio = function() { + return this.width / this.height; +}; + + +/** + * @return {boolean} True if the size has zero area, false if both dimensions + * are non-zero numbers. + */ +goog.math.Size.prototype.isEmpty = function() { + return !this.area(); +}; + + +/** + * Clamps the width and height parameters upward to integer values. + * @return {!goog.math.Size} This size with ceil'd components. + */ +goog.math.Size.prototype.ceil = function() { + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; +}; + + +/** + * @param {!goog.math.Size} target The target size. + * @return {boolean} True if this Size is the same size or smaller than the + * target size in both dimensions. + */ +goog.math.Size.prototype.fitsInside = function(target) { + return this.width <= target.width && this.height <= target.height; +}; + + +/** + * Clamps the width and height parameters downward to integer values. + * @return {!goog.math.Size} This size with floored components. + */ +goog.math.Size.prototype.floor = function() { + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; +}; + + +/** + * Rounds the width and height parameters to integer values. + * @return {!goog.math.Size} This size with rounded components. + */ +goog.math.Size.prototype.round = function() { + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; +}; + + +/** + * Scales this size by the given scale factors. The width and height are scaled + * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not + * given, then {@code sx} is used for both the width and height. + * @param {number} sx The scale factor to use for the width. + * @param {number=} opt_sy The scale factor to use for the height. + * @return {!goog.math.Size} This Size object after scaling. + */ +goog.math.Size.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.width *= sx; + this.height *= sy; + return this; +}; + + +/** + * Uniformly scales the size to perfectly cover the dimensions of a given size. + * If the size is already larger than the target, it will be scaled down to the + * minimum size at which it still covers the entire target. The original aspect + * ratio will be preserved. + * + * This function assumes that both Sizes contain strictly positive dimensions. + * @param {!goog.math.Size} target The target size. + * @return {!goog.math.Size} This Size object, after optional scaling. + */ +goog.math.Size.prototype.scaleToCover = function(target) { + var s = this.aspectRatio() <= target.aspectRatio() ? + target.width / this.width : + target.height / this.height; + + return this.scale(s); +}; + + +/** + * Uniformly scales the size to fit inside the dimensions of a given size. The + * original aspect ratio will be preserved. + * + * This function assumes that both Sizes contain strictly positive dimensions. + * @param {!goog.math.Size} target The target size. + * @return {!goog.math.Size} This Size object, after optional scaling. + */ +goog.math.Size.prototype.scaleToFit = function(target) { + var s = this.aspectRatio() > target.aspectRatio() ? + target.width / this.width : + target.height / this.height; + + return this.scale(s); +};
diff --git a/third_party/ink/closure/object/object.js b/third_party/ink/closure/object/object.js new file mode 100644 index 0000000..286d24e --- /dev/null +++ b/third_party/ink/closure/object/object.js
@@ -0,0 +1,751 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for manipulating objects/maps/hashes. + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + * @author pallosp@google.com (Peter Pallos) + */ + +goog.provide('goog.object'); + + +/** + * Whether two values are not observably distinguishable. This + * correctly detects that 0 is not the same as -0 and two NaNs are + * practically equivalent. + * + * The implementation is as suggested by harmony:egal proposal. + * + * @param {*} v The first value to compare. + * @param {*} v2 The second value to compare. + * @return {boolean} Whether two values are not observably distinguishable. + * @see http://wiki.ecmascript.org/doku.php?id=harmony:egal + */ +goog.object.is = function(v, v2) { + if (v === v2) { + // 0 === -0, but they are not identical. + // We need the cast because the compiler requires that v2 is a + // number (although 1/v2 works with non-number). We cast to ? to + // stop the compiler from type-checking this statement. + return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2); + } + + // NaN is non-reflexive: NaN !== NaN, although they are identical. + return v !== v && v2 !== v2; +}; + + +/** + * Calls a function for each element in an object/map/hash. + * + * @param {Object<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object<K,V>):?} f The function to call + * for every element. This function takes 3 arguments (the value, the + * key and the object) and the return value is ignored. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @template T,K,V + */ +goog.object.forEach = function(obj, f, opt_obj) { + for (var key in obj) { + f.call(/** @type {?} */ (opt_obj), obj[key], key, obj); + } +}; + + +/** + * Calls a function for each element in an object/map/hash. If that call returns + * true, adds the element to a new object. + * + * @param {Object<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call + * for every element. This + * function takes 3 arguments (the value, the key and the object) + * and should return a boolean. If the return value is true the + * element is added to the result object. If it is false the + * element is not included. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object<K,V>} a new object in which only elements that passed the + * test are present. + * @template T,K,V + */ +goog.object.filter = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) { + res[key] = obj[key]; + } + } + return res; +}; + + +/** + * For every element in an object/map/hash calls a function and inserts the + * result into a new object. + * + * @param {Object<K,V>} obj The object over which to iterate. + * @param {function(this:T,V,?,Object<K,V>):R} f The function to call + * for every element. This function + * takes 3 arguments (the value, the key and the object) + * and should return something. The result will be inserted + * into a new object. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object<K,R>} a new object with the results from f. + * @template T,K,V,R + */ +goog.object.map = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj); + } + return res; +}; + + +/** + * Calls a function for each element in an object/map/hash. If any + * call returns true, returns true (without checking the rest). If + * all calls return false, returns false. + * + * @param {Object<K,V>} obj The object to check. + * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the value, the key and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} true if any element passes the test. + * @template T,K,V + */ +goog.object.some = function(obj, f, opt_obj) { + for (var key in obj) { + if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) { + return true; + } + } + return false; +}; + + +/** + * Calls a function for each element in an object/map/hash. If + * all calls return true, returns true. If any call returns false, returns + * false at this point and does not continue to check the remaining elements. + * + * @param {Object<K,V>} obj The object to check. + * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the value, the key and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} false if any element fails the test. + * @template T,K,V + */ +goog.object.every = function(obj, f, opt_obj) { + for (var key in obj) { + if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) { + return false; + } + } + return true; +}; + + +/** + * Returns the number of key-value pairs in the object map. + * + * @param {Object} obj The object for which to get the number of key-value + * pairs. + * @return {number} The number of key-value pairs in the object map. + */ +goog.object.getCount = function(obj) { + var rv = 0; + for (var key in obj) { + rv++; + } + return rv; +}; + + +/** + * Returns one key from the object map, if any exists. + * For map literals the returned key will be the first one in most of the + * browsers (a know exception is Konqueror). + * + * @param {Object} obj The object to pick a key from. + * @return {string|undefined} The key or undefined if the object is empty. + */ +goog.object.getAnyKey = function(obj) { + for (var key in obj) { + return key; + } +}; + + +/** + * Returns one value from the object map, if any exists. + * For map literals the returned value will be the first one in most of the + * browsers (a know exception is Konqueror). + * + * @param {Object<K,V>} obj The object to pick a value from. + * @return {V|undefined} The value or undefined if the object is empty. + * @template K,V + */ +goog.object.getAnyValue = function(obj) { + for (var key in obj) { + return obj[key]; + } +}; + + +/** + * Whether the object/hash/map contains the given object as a value. + * An alias for goog.object.containsValue(obj, val). + * + * @param {Object<K,V>} obj The object in which to look for val. + * @param {V} val The object for which to check. + * @return {boolean} true if val is present. + * @template K,V + */ +goog.object.contains = function(obj, val) { + return goog.object.containsValue(obj, val); +}; + + +/** + * Returns the values of the object/map/hash. + * + * @param {Object<K,V>} obj The object from which to get the values. + * @return {!Array<V>} The values in the object/map/hash. + * @template K,V + */ +goog.object.getValues = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = obj[key]; + } + return res; +}; + + +/** + * Returns the keys of the object/map/hash. + * + * @param {Object} obj The object from which to get the keys. + * @return {!Array<string>} Array of property keys. + */ +goog.object.getKeys = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = key; + } + return res; +}; + + +/** + * Get a value from an object multiple levels deep. This is useful for + * pulling values from deeply nested objects, such as JSON responses. + * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3) + * + * @param {!Object} obj An object to get the value from. Can be array-like. + * @param {...(string|number|!IArrayLike<number|string>)} + * var_args A number of keys + * (as strings, or numbers, for array-like objects). Can also be + * specified as a single array of keys. + * @return {*} The resulting value. If, at any point, the value for a key + * in the current object is null or undefined, returns undefined. + */ +goog.object.getValueByKeys = function(obj, var_args) { + var isArrayLike = goog.isArrayLike(var_args); + var keys = isArrayLike ? var_args : arguments; + + // Start with the 2nd parameter for the variable parameters syntax. + for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { + if (obj == null) return undefined; + obj = obj[keys[i]]; + } + + return obj; +}; + + +/** + * Whether the object/map/hash contains the given key. + * + * @param {Object} obj The object in which to look for key. + * @param {?} key The key for which to check. + * @return {boolean} true If the map contains the key. + */ +goog.object.containsKey = function(obj, key) { + return obj !== null && key in obj; +}; + + +/** + * Whether the object/map/hash contains the given value. This is O(n). + * + * @param {Object<K,V>} obj The object in which to look for val. + * @param {V} val The value for which to check. + * @return {boolean} true If the map contains the value. + * @template K,V + */ +goog.object.containsValue = function(obj, val) { + for (var key in obj) { + if (obj[key] == val) { + return true; + } + } + return false; +}; + + +/** + * Searches an object for an element that satisfies the given condition and + * returns its key. + * @param {Object<K,V>} obj The object to search in. + * @param {function(this:T,V,string,Object<K,V>):boolean} f The + * function to call for every element. Takes 3 arguments (the value, + * the key and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {string|undefined} The key of an element for which the function + * returns true or undefined if no such element is found. + * @template T,K,V + */ +goog.object.findKey = function(obj, f, opt_this) { + for (var key in obj) { + if (f.call(/** @type {?} */ (opt_this), obj[key], key, obj)) { + return key; + } + } + return undefined; +}; + + +/** + * Searches an object for an element that satisfies the given condition and + * returns its value. + * @param {Object<K,V>} obj The object to search in. + * @param {function(this:T,V,string,Object<K,V>):boolean} f The function + * to call for every element. Takes 3 arguments (the value, the key + * and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {V} The value of an element for which the function returns true or + * undefined if no such element is found. + * @template T,K,V + */ +goog.object.findValue = function(obj, f, opt_this) { + var key = goog.object.findKey(obj, f, opt_this); + return key && obj[key]; +}; + + +/** + * Whether the object/map/hash is empty. + * + * @param {Object} obj The object to test. + * @return {boolean} true if obj is empty. + */ +goog.object.isEmpty = function(obj) { + for (var key in obj) { + return false; + } + return true; +}; + + +/** + * Removes all key value pairs from the object/map/hash. + * + * @param {Object} obj The object to clear. + */ +goog.object.clear = function(obj) { + for (var i in obj) { + delete obj[i]; + } +}; + + +/** + * Removes a key-value pair based on the key. + * + * @param {Object} obj The object from which to remove the key. + * @param {?} key The key to remove. + * @return {boolean} Whether an element was removed. + */ +goog.object.remove = function(obj, key) { + var rv; + if (rv = key in /** @type {!Object} */ (obj)) { + delete obj[key]; + } + return rv; +}; + + +/** + * Adds a key-value pair to the object. Throws an exception if the key is + * already in use. Use set if you want to change an existing pair. + * + * @param {Object<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} val The value to add. + * @template K,V + */ +goog.object.add = function(obj, key, val) { + if (obj !== null && key in obj) { + throw new Error('The object already contains the key "' + key + '"'); + } + goog.object.set(obj, key, val); +}; + + +/** + * Returns the value for the given key. + * + * @param {Object<K,V>} obj The object from which to get the value. + * @param {string} key The key for which to get the value. + * @param {R=} opt_val The value to return if no item is found for the given + * key (default is undefined). + * @return {V|R|undefined} The value for the given key. + * @template K,V,R + */ +goog.object.get = function(obj, key, opt_val) { + if (obj !== null && key in obj) { + return obj[key]; + } + return opt_val; +}; + + +/** + * Adds a key-value pair to the object/map/hash. + * + * @param {Object<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add. + * @template K,V + */ +goog.object.set = function(obj, key, value) { + obj[key] = value; +}; + + +/** + * Adds a key-value pair to the object/map/hash if it doesn't exist yet. + * + * @param {Object<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add if the key wasn't present. + * @return {V} The value of the entry at the end of the function. + * @template K,V + */ +goog.object.setIfUndefined = function(obj, key, value) { + return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value); +}; + + +/** + * Sets a key and value to an object if the key is not set. The value will be + * the return value of the given function. If the key already exists, the + * object will not be changed and the function will not be called (the function + * will be lazily evaluated -- only called if necessary). + * + * This function is particularly useful for use with a map used a as a cache. + * + * @param {!Object<K,V>} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {function():V} f The value to add if the key wasn't present. + * @return {V} The value of the entry at the end of the function. + * @template K,V + */ +goog.object.setWithReturnValueIfNotSet = function(obj, key, f) { + if (key in obj) { + return obj[key]; + } + + var val = f(); + obj[key] = val; + return val; +}; + + +/** + * Compares two objects for equality using === on the values. + * + * @param {!Object<K,V>} a + * @param {!Object<K,V>} b + * @return {boolean} + * @template K,V + */ +goog.object.equals = function(a, b) { + for (var k in a) { + if (!(k in b) || a[k] !== b[k]) { + return false; + } + } + for (var k in b) { + if (!(k in a)) { + return false; + } + } + return true; +}; + + +/** + * Returns a shallow clone of the object. + * + * @param {Object<K,V>} obj Object to clone. + * @return {!Object<K,V>} Clone of the input object. + * @template K,V + */ +goog.object.clone = function(obj) { + // We cannot use the prototype trick because a lot of methods depend on where + // the actual key is set. + + var res = {}; + for (var key in obj) { + res[key] = obj[key]; + } + return res; + // We could also use goog.mixin but I wanted this to be independent from that. +}; + + +/** + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects + * that refer to themselves will cause infinite recursion. + * + * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and + * copies UIDs created by <code>getUid</code> into cloned results. + * + * @param {T} obj The value to clone. + * @return {T} A clone of the input value. + * @template T + */ +goog.object.unsafeClone = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (goog.isFunction(obj.clone)) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.object.unsafeClone(obj[key]); + } + return clone; + } + + return obj; +}; + + +/** + * Returns a new object in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. + * + * @param {Object} obj The object to transpose. + * @return {!Object} The transposed object. + */ +goog.object.transpose = function(obj) { + var transposed = {}; + for (var key in obj) { + transposed[obj[key]] = key; + } + return transposed; +}; + + +/** + * The names of the fields that are defined on Object.prototype. + * @type {Array<string>} + * @private + */ +goog.object.PROTOTYPE_FIELDS_ = [ + 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', + 'toLocaleString', 'toString', 'valueOf' +]; + + +/** + * Extends an object with another object. + * This operates 'in-place'; it does not create a new Object. + * + * Example: + * var o = {}; + * goog.object.extend(o, {a: 0, b: 1}); + * o; // {a: 0, b: 1} + * goog.object.extend(o, {b: 2, c: 3}); + * o; // {a: 0, b: 2, c: 3} + * + * @param {Object} target The object to modify. Existing properties will be + * overwritten if they are also present in one of the objects in + * {@code var_args}. + * @param {...Object} var_args The objects from which values will be copied. + */ +goog.object.extend = function(target, var_args) { + var key, source; + for (var i = 1; i < arguments.length; i++) { + source = arguments[i]; + for (key in source) { + target[key] = source[key]; + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). + + for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) { + key = goog.object.PROTOTYPE_FIELDS_[j]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } +}; + + +/** + * Creates a new object built from the key-value pairs provided as arguments. + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise even arguments are used as + * the property names and odd arguments are used as the property values. + * @return {!Object} The new object. + * @throws {Error} If there are uneven number of arguments or there is only one + * non array argument. + */ +goog.object.create = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.create.apply(null, arguments[0]); + } + + if (argLength % 2) { + throw new Error('Uneven number of arguments'); + } + + var rv = {}; + for (var i = 0; i < argLength; i += 2) { + rv[arguments[i]] = arguments[i + 1]; + } + return rv; +}; + + +/** + * Creates a new object where the property names come from the arguments but + * the value is always set to true + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise the arguments are used + * as the property names. + * @return {!Object} The new object. + */ +goog.object.createSet = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.createSet.apply(null, arguments[0]); + } + + var rv = {}; + for (var i = 0; i < argLength; i++) { + rv[arguments[i]] = true; + } + return rv; +}; + + +/** + * Creates an immutable view of the underlying object, if the browser + * supports immutable objects. + * + * In default mode, writes to this view will fail silently. In strict mode, + * they will throw an error. + * + * @param {!Object<K,V>} obj An object. + * @return {!Object<K,V>} An immutable view of that object, or the + * original object if this browser does not support immutables. + * @template K,V + */ +goog.object.createImmutableView = function(obj) { + var result = obj; + if (Object.isFrozen && !Object.isFrozen(obj)) { + result = Object.create(obj); + Object.freeze(result); + } + return result; +}; + + +/** + * @param {!Object} obj An object. + * @return {boolean} Whether this is an immutable view of the object. + */ +goog.object.isImmutableView = function(obj) { + return !!Object.isFrozen && Object.isFrozen(obj); +}; + + +/** + * Get all properties names on a given Object regardless of enumerability. + * + * <p> If the browser does not support {@code Object.getOwnPropertyNames} nor + * {@code Object.getPrototypeOf} then this is equivalent to using {@code + * goog.object.getKeys} + * + * @param {?Object} obj The object to get the properties of. + * @param {boolean=} opt_includeObjectPrototype Whether properties defined on + * {@code Object.prototype} should be included in the result. + * @param {boolean=} opt_includeFunctionPrototype Whether properties defined on + * {@code Function.prototype} should be included in the result. + * @return {!Array<string>} + * @public + */ +goog.object.getAllPropertyNames = function( + obj, opt_includeObjectPrototype, opt_includeFunctionPrototype) { + if (!obj) { + return []; + } + + // Naively use a for..in loop to get the property names if the browser doesn't + // support any other APIs for getting it. + if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) { + return goog.object.getKeys(obj); + } + + var visitedSet = {}; + + // Traverse the prototype chain and add all properties to the visited set. + var proto = obj; + while (proto && + (proto !== Object.prototype || !!opt_includeObjectPrototype) && + (proto !== Function.prototype || !!opt_includeFunctionPrototype)) { + var names = Object.getOwnPropertyNames(proto); + for (var i = 0; i < names.length; i++) { + visitedSet[names[i]] = true; + } + proto = Object.getPrototypeOf(proto); + } + + return goog.object.getKeys(visitedSet); +};
diff --git a/third_party/ink/closure/proto2/descriptor.js b/third_party/ink/closure/proto2/descriptor.js new file mode 100644 index 0000000..4abc3a35 --- /dev/null +++ b/third_party/ink/closure/proto2/descriptor.js
@@ -0,0 +1,202 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Protocol Buffer (Message) Descriptor class. + * @author jschorr@google.com (Joseph Schorr) + */ + +goog.provide('goog.proto2.Descriptor'); +goog.provide('goog.proto2.Metadata'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('goog.string'); + + +/** + * @typedef {{name: (string|undefined), + * fullName: (string|undefined), + * containingType: (goog.proto2.Message|undefined)}} + */ +goog.proto2.Metadata; + + + +/** + * A class which describes a Protocol Buffer 2 Message. + * + * @param {function(new:goog.proto2.Message)} messageType Constructor for + * the message class that this descriptor describes. + * @param {!goog.proto2.Metadata} metadata The metadata about the message that + * will be used to construct this descriptor. + * @param {Array<!goog.proto2.FieldDescriptor>} fields The fields of the + * message described by this descriptor. + * + * @constructor + * @final + */ +goog.proto2.Descriptor = function(messageType, metadata, fields) { + + /** + * @type {function(new:goog.proto2.Message)} + * @private + */ + this.messageType_ = messageType; + + /** + * @type {?string} + * @private + */ + this.name_ = metadata.name || null; + + /** + * @type {?string} + * @private + */ + this.fullName_ = metadata.fullName || null; + + /** + * @type {goog.proto2.Message|undefined} + * @private + */ + this.containingType_ = metadata.containingType; + + /** + * The fields of the message described by this descriptor. + * @type {!Object<number, !goog.proto2.FieldDescriptor>} + * @private + */ + this.fields_ = {}; + + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + this.fields_[field.getTag()] = field; + } +}; + + +/** + * Returns the name of the message, if any. + * + * @return {?string} The name. + */ +goog.proto2.Descriptor.prototype.getName = function() { + return this.name_; +}; + + +/** + * Returns the full name of the message, if any. + * + * @return {?string} The name. + */ +goog.proto2.Descriptor.prototype.getFullName = function() { + return this.fullName_; +}; + + +/** + * Returns the descriptor of the containing message type or null if none. + * + * @return {goog.proto2.Descriptor} The descriptor. + */ +goog.proto2.Descriptor.prototype.getContainingType = function() { + if (!this.containingType_) { + return null; + } + + return this.containingType_.getDescriptor(); +}; + + +/** + * Returns the fields in the message described by this descriptor ordered by + * tag. + * + * @return {!Array<!goog.proto2.FieldDescriptor>} The array of field + * descriptors. + */ +goog.proto2.Descriptor.prototype.getFields = function() { + /** + * @param {!goog.proto2.FieldDescriptor} fieldA First field. + * @param {!goog.proto2.FieldDescriptor} fieldB Second field. + * @return {number} Negative if fieldA's tag number is smaller, positive + * if greater, zero if the same. + */ + function tagComparator(fieldA, fieldB) { + return fieldA.getTag() - fieldB.getTag(); + } + + var fields = goog.object.getValues(this.fields_); + goog.array.sort(fields, tagComparator); + + return fields; +}; + + +/** + * Returns the fields in the message as a key/value map, where the key is + * the tag number of the field. DO NOT MODIFY THE RETURNED OBJECT. We return + * the actual, internal, fields map for performance reasons, and changing the + * map can result in undefined behavior of this library. + * + * @return {!Object<number, !goog.proto2.FieldDescriptor>} The field map. + */ +goog.proto2.Descriptor.prototype.getFieldsMap = function() { + return this.fields_; +}; + + +/** + * Returns the field matching the given name, if any. Note that + * this method searches over the *original* name of the field, + * not the camelCase version. + * + * @param {string} name The field name for which to search. + * + * @return {goog.proto2.FieldDescriptor} The field found, if any. + */ +goog.proto2.Descriptor.prototype.findFieldByName = function(name) { + var valueFound = goog.object.findValue( + this.fields_, + function(field, key, obj) { return field.getName() == name; }); + + return /** @type {goog.proto2.FieldDescriptor} */ (valueFound) || null; +}; + + +/** + * Returns the field matching the given tag number, if any. + * + * @param {number|string} tag The field tag number for which to search. + * + * @return {goog.proto2.FieldDescriptor} The field found, if any. + */ +goog.proto2.Descriptor.prototype.findFieldByTag = function(tag) { + goog.asserts.assert(goog.string.isNumeric(tag)); + return this.fields_[parseInt(tag, 10)] || null; +}; + + +/** + * Creates an instance of the message type that this descriptor + * describes. + * + * @return {!goog.proto2.Message} The instance of the message. + */ +goog.proto2.Descriptor.prototype.createMessageInstance = function() { + return new this.messageType_; +};
diff --git a/third_party/ink/closure/proto2/fielddescriptor.js b/third_party/ink/closure/proto2/fielddescriptor.js new file mode 100644 index 0000000..7fbef98 --- /dev/null +++ b/third_party/ink/closure/proto2/fielddescriptor.js
@@ -0,0 +1,313 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Protocol Buffer Field Descriptor class. + * @author jschorr@google.com (Joseph Schorr) + */ + +goog.provide('goog.proto2.FieldDescriptor'); + +goog.require('goog.asserts'); +goog.require('goog.string'); + + + +/** + * A class which describes a field in a Protocol Buffer 2 Message. + * + * @param {function(new:goog.proto2.Message)} messageType Constructor for the + * message class to which the field described by this class belongs. + * @param {number|string} tag The field's tag index. + * @param {Object} metadata The metadata about this field that will be used + * to construct this descriptor. + * + * @constructor + * @final + */ +goog.proto2.FieldDescriptor = function(messageType, tag, metadata) { + /** + * The message type that contains the field that this + * descriptor describes. + * @private {function(new:goog.proto2.Message)} + */ + this.parent_ = messageType; + + // Ensure that the tag is numeric. + goog.asserts.assert(goog.string.isNumeric(tag)); + + /** + * The field's tag number. + * @private {number} + */ + this.tag_ = /** @type {number} */ (tag); + + /** + * The field's name. + * @private {string} + */ + this.name_ = metadata.name; + + /** @type {goog.proto2.FieldDescriptor.FieldType} */ + metadata.fieldType; + + /** @type {*} */ + metadata.repeated; + + /** @type {*} */ + metadata.required; + + /** @type {*} */ + metadata.packed; + + /** + * If true, this field is a packed field. + * @private {boolean} + */ + this.isPacked_ = !!metadata.packed; + + /** + * If true, this field is a repeating field. + * @private {boolean} + */ + this.isRepeated_ = !!metadata.repeated; + + /** + * If true, this field is required. + * @private {boolean} + */ + this.isRequired_ = !!metadata.required; + + /** + * The field type of this field. + * @private {goog.proto2.FieldDescriptor.FieldType} + */ + this.fieldType_ = metadata.fieldType; + + /** + * If this field is a primitive: The native (ECMAScript) type of this field. + * If an enumeration: The enumeration object. + * If a message or group field: The Message function. + * @private {Function} + */ + this.nativeType_ = metadata.type; + + /** + * Is it permissible on deserialization to convert between numbers and + * well-formed strings? Is true for 64-bit integral field types and float and + * double types, false for all other field types. + * @private {boolean} + */ + this.deserializationConversionPermitted_ = false; + + switch (this.fieldType_) { + case goog.proto2.FieldDescriptor.FieldType.INT64: + case goog.proto2.FieldDescriptor.FieldType.UINT64: + case goog.proto2.FieldDescriptor.FieldType.FIXED64: + case goog.proto2.FieldDescriptor.FieldType.SFIXED64: + case goog.proto2.FieldDescriptor.FieldType.SINT64: + case goog.proto2.FieldDescriptor.FieldType.FLOAT: + case goog.proto2.FieldDescriptor.FieldType.DOUBLE: + this.deserializationConversionPermitted_ = true; + break; + } + + /** + * The default value of this field, if different from the default, default + * value. + * @private {*} + */ + this.defaultValue_ = metadata.defaultValue; +}; + + +/** + * An enumeration defining the possible field types. + * Should be a mirror of that defined in descriptor.h. + * + * @enum {number} + */ +goog.proto2.FieldDescriptor.FieldType = { + DOUBLE: 1, + FLOAT: 2, + INT64: 3, + UINT64: 4, + INT32: 5, + FIXED64: 6, + FIXED32: 7, + BOOL: 8, + STRING: 9, + GROUP: 10, + MESSAGE: 11, + BYTES: 12, + UINT32: 13, + ENUM: 14, + SFIXED32: 15, + SFIXED64: 16, + SINT32: 17, + SINT64: 18 +}; + + +/** + * Returns the tag of the field that this descriptor represents. + * + * @return {number} The tag number. + */ +goog.proto2.FieldDescriptor.prototype.getTag = function() { + return this.tag_; +}; + + +/** + * Returns the descriptor describing the message that defined this field. + * @return {!goog.proto2.Descriptor} The descriptor. + */ +goog.proto2.FieldDescriptor.prototype.getContainingType = function() { + // Generated JS proto_library messages have getDescriptor() method which can + // be called with or without an instance. + return this.parent_.prototype.getDescriptor(); +}; + + +/** + * Returns the name of the field that this descriptor represents. + * @return {string} The name. + */ +goog.proto2.FieldDescriptor.prototype.getName = function() { + return this.name_; +}; + + +/** + * Returns the default value of this field. + * @return {*} The default value. + */ +goog.proto2.FieldDescriptor.prototype.getDefaultValue = function() { + if (this.defaultValue_ === undefined) { + // Set the default value based on a new instance of the native type. + // This will be (0, false, "") for (number, boolean, string) and will + // be a new instance of a group/message if the field is a message type. + var nativeType = this.nativeType_; + if (nativeType === Boolean) { + this.defaultValue_ = false; + } else if (nativeType === Number) { + this.defaultValue_ = 0; + } else if (nativeType === String) { + if (this.deserializationConversionPermitted_) { + // This field is a 64 bit integer represented as a string. + this.defaultValue_ = '0'; + } else { + this.defaultValue_ = ''; + } + } else { + return new nativeType; + } + } + + return this.defaultValue_; +}; + + +/** + * Returns the field type of the field described by this descriptor. + * @return {goog.proto2.FieldDescriptor.FieldType} The field type. + */ +goog.proto2.FieldDescriptor.prototype.getFieldType = function() { + return this.fieldType_; +}; + + +/** + * Returns the native (i.e. ECMAScript) type of the field described by this + * descriptor. + * + * @return {Object} The native type. + */ +goog.proto2.FieldDescriptor.prototype.getNativeType = function() { + return this.nativeType_; +}; + + +/** + * Returns true if simple conversions between numbers and strings are permitted + * during deserialization for this field. + * + * @return {boolean} Whether conversion is permitted. + */ +goog.proto2.FieldDescriptor.prototype.deserializationConversionPermitted = + function() { + return this.deserializationConversionPermitted_; +}; + + +/** + * Returns the descriptor of the message type of this field. Only valid + * for fields of type GROUP and MESSAGE. + * + * @return {!goog.proto2.Descriptor} The message descriptor. + */ +goog.proto2.FieldDescriptor.prototype.getFieldMessageType = function() { + // Generated JS proto_library messages have getDescriptor() method which can + // be called with or without an instance. + var messageClass = + /** @type {function(new:goog.proto2.Message)} */ (this.nativeType_); + return messageClass.prototype.getDescriptor(); +}; + + +/** + * @return {boolean} True if the field stores composite data or repeated + * composite data (message or group). + */ +goog.proto2.FieldDescriptor.prototype.isCompositeType = function() { + return this.fieldType_ == goog.proto2.FieldDescriptor.FieldType.MESSAGE || + this.fieldType_ == goog.proto2.FieldDescriptor.FieldType.GROUP; +}; + + +/** + * Returns whether the field described by this descriptor is packed. + * @return {boolean} Whether the field is packed. + */ +goog.proto2.FieldDescriptor.prototype.isPacked = function() { + return this.isPacked_; +}; + + +/** + * Returns whether the field described by this descriptor is repeating. + * @return {boolean} Whether the field is repeated. + */ +goog.proto2.FieldDescriptor.prototype.isRepeated = function() { + return this.isRepeated_; +}; + + +/** + * Returns whether the field described by this descriptor is required. + * @return {boolean} Whether the field is required. + */ +goog.proto2.FieldDescriptor.prototype.isRequired = function() { + return this.isRequired_; +}; + + +/** + * Returns whether the field described by this descriptor is optional. + * @return {boolean} Whether the field is optional. + */ +goog.proto2.FieldDescriptor.prototype.isOptional = function() { + return !this.isRepeated_ && !this.isRequired_; +};
diff --git a/third_party/ink/closure/proto2/message.js b/third_party/ink/closure/proto2/message.js new file mode 100644 index 0000000..0315680 --- /dev/null +++ b/third_party/ink/closure/proto2/message.js
@@ -0,0 +1,733 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Protocol Buffer Message base class. + * @author jschorr@google.com (Joseph Schorr) + * @author pallosp@google.com (Peter Pallos) + * @suppress {unusedPrivateMembers} For descriptor_ declaration. + */ + +goog.provide('goog.proto2.Message'); + +goog.require('goog.asserts'); +goog.require('goog.proto2.Descriptor'); +goog.require('goog.proto2.FieldDescriptor'); + +goog.forwardDeclare('goog.proto2.LazyDeserializer'); // circular reference + + + +/** + * Abstract base class for all Protocol Buffer 2 messages. It will be + * subclassed in the code generated by the Protocol Compiler. Any other + * subclasses are prohibited. + * @constructor + */ +goog.proto2.Message = function() { + /** + * Stores the field values in this message. Keyed by the tag of the fields. + * @type {!Object} + * @private + */ + this.values_ = {}; + + /** + * Stores the field information (i.e. metadata) about this message. + * @type {Object<number, !goog.proto2.FieldDescriptor>} + * @private + */ + this.fields_ = this.getDescriptor().getFieldsMap(); + + /** + * The lazy deserializer for this message instance, if any. + * @type {goog.proto2.LazyDeserializer} + * @private + */ + this.lazyDeserializer_ = null; + + /** + * A map of those fields deserialized, from tag number to their deserialized + * value. + * @type {Object} + * @private + */ + this.deserializedFields_ = null; +}; + + +/** + * An enumeration defining the possible field types. + * Should be a mirror of that defined in descriptor.h. + * + * TODO(sra): Remove this alias. The code generator generates code that + * references this enum, so it needs to exist until the code generator is + * changed. The enum was moved to from Message to FieldDescriptor to avoid a + * dependency cycle. + * + * Use goog.proto2.FieldDescriptor.FieldType instead. + * + * @enum {number} + */ +goog.proto2.Message.FieldType = { + DOUBLE: 1, + FLOAT: 2, + INT64: 3, + UINT64: 4, + INT32: 5, + FIXED64: 6, + FIXED32: 7, + BOOL: 8, + STRING: 9, + GROUP: 10, + MESSAGE: 11, + BYTES: 12, + UINT32: 13, + ENUM: 14, + SFIXED32: 15, + SFIXED64: 16, + SINT32: 17, + SINT64: 18 +}; + + +/** + * All instances of goog.proto2.Message should have a static descriptor_ + * property. The Descriptor will be deserialized lazily in the getDescriptor() + * method. + * + * This declaration is just here for documentation purposes. + * goog.proto2.Message does not have its own descriptor. + * + * @type {undefined} + * @private + */ +goog.proto2.Message.descriptor_; + + +/** + * Initializes the message with a lazy deserializer and its associated data. + * This method should be called by internal methods ONLY. + * + * @param {goog.proto2.LazyDeserializer} deserializer The lazy deserializer to + * use to decode the data on the fly. + * + * @param {?} data The data to decode/deserialize. + */ +goog.proto2.Message.prototype.initializeForLazyDeserializer = function( + deserializer, data) { + + this.lazyDeserializer_ = deserializer; + this.values_ = data; + this.deserializedFields_ = {}; +}; + + +/** + * Sets the value of an unknown field, by tag. + * + * @param {number} tag The tag of an unknown field (must be >= 1). + * @param {*} value The value for that unknown field. + */ +goog.proto2.Message.prototype.setUnknown = function(tag, value) { + goog.asserts.assert( + !this.fields_[tag], 'Field is not unknown in this message'); + goog.asserts.assert( + tag >= 1, 'Tag ' + tag + ' has value "' + value + '" in descriptor ' + + this.getDescriptor().getName()); + + goog.asserts.assert(value !== null, 'Value cannot be null'); + + this.values_[tag] = value; + if (this.deserializedFields_) { + delete this.deserializedFields_[tag]; + } +}; + + +/** + * Iterates over all the unknown fields in the message. + * + * @param {function(this:T, number, *)} callback A callback method + * which gets invoked for each unknown field. + * @param {T=} opt_scope The scope under which to execute the callback. + * If not given, the current message will be used. + * @template T + */ +goog.proto2.Message.prototype.forEachUnknown = function(callback, opt_scope) { + var scope = opt_scope || this; + for (var key in this.values_) { + var keyNum = Number(key); + if (!this.fields_[keyNum]) { + callback.call(scope, keyNum, this.values_[key]); + } + } +}; + + +/** + * Returns the descriptor which describes the current message. + * + * This only works if we assume people never subclass protobufs. + * + * @return {!goog.proto2.Descriptor} The descriptor. + */ +goog.proto2.Message.prototype.getDescriptor = goog.abstractMethod; + + +/** + * Returns whether there is a value stored at the field specified by the + * given field descriptor. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to check + * if there is a value. + * + * @return {boolean} True if a value was found. + */ +goog.proto2.Message.prototype.has = function(field) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + return this.has$Value(field.getTag()); +}; + + +/** + * Returns the array of values found for the given repeated field. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to + * return the values. + * + * @return {!Array<?>} The values found. + */ +goog.proto2.Message.prototype.arrayOf = function(field) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + return this.array$Values(field.getTag()); +}; + + +/** + * Returns the number of values stored in the given field. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to count + * the number of values. + * + * @return {number} The count of the values in the given field. + */ +goog.proto2.Message.prototype.countOf = function(field) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + return this.count$Values(field.getTag()); +}; + + +/** + * Returns the value stored at the field specified by the + * given field descriptor. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to get the + * value. + * @param {number=} opt_index If the field is repeated, the index to use when + * looking up the value. + * + * @return {?} The value found or null if none. + */ +goog.proto2.Message.prototype.get = function(field, opt_index) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + return this.get$Value(field.getTag(), opt_index); +}; + + +/** + * Returns the value stored at the field specified by the + * given field descriptor or the default value if none exists. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to get the + * value. + * @param {number=} opt_index If the field is repeated, the index to use when + * looking up the value. + * + * @return {?} The value found or the default if none. + */ +goog.proto2.Message.prototype.getOrDefault = function(field, opt_index) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + return this.get$ValueOrDefault(field.getTag(), opt_index); +}; + + +/** + * Stores the given value to the field specified by the + * given field descriptor. Note that the field must not be repeated. + * + * @param {goog.proto2.FieldDescriptor} field The field for which to set + * the value. + * @param {*} value The new value for the field. + */ +goog.proto2.Message.prototype.set = function(field, value) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + this.set$Value(field.getTag(), value); +}; + + +/** + * Adds the given value to the field specified by the + * given field descriptor. Note that the field must be repeated. + * + * @param {goog.proto2.FieldDescriptor} field The field in which to add the + * the value. + * @param {*} value The new value to add to the field. + */ +goog.proto2.Message.prototype.add = function(field, value) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + this.add$Value(field.getTag(), value); +}; + + +/** + * Clears the field specified. + * + * @param {goog.proto2.FieldDescriptor} field The field to clear. + */ +goog.proto2.Message.prototype.clear = function(field) { + goog.asserts.assert( + field.getContainingType() == this.getDescriptor(), + 'The current message does not contain the given field'); + + this.clear$Field(field.getTag()); +}; + + +/** + * Compares this message with another one ignoring the unknown fields. + * @param {?} other The other message. + * @return {boolean} Whether they are equal. Returns false if the {@code other} + * argument is a different type of message or not a message. + */ +goog.proto2.Message.prototype.equals = function(other) { + if (!other || this.constructor != other.constructor) { + return false; + } + + var fields = this.getDescriptor().getFields(); + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var tag = field.getTag(); + if (this.has$Value(tag) != other.has$Value(tag)) { + return false; + } + + if (this.has$Value(tag)) { + var isComposite = field.isCompositeType(); + + var fieldsEqual = function(value1, value2) { + return isComposite ? value1.equals(value2) : value1 == value2; + }; + + var thisValue = this.getValueForTag_(tag); + var otherValue = other.getValueForTag_(tag); + + if (field.isRepeated()) { + // In this case thisValue and otherValue are arrays. + if (thisValue.length != otherValue.length) { + return false; + } + for (var j = 0; j < thisValue.length; j++) { + if (!fieldsEqual(thisValue[j], otherValue[j])) { + return false; + } + } + } else if (!fieldsEqual(thisValue, otherValue)) { + return false; + } + } + } + + return true; +}; + + +/** + * Recursively copies the known fields from the given message to this message. + * Removes the fields which are not present in the source message. + * @param {!goog.proto2.Message} message The source message. + */ +goog.proto2.Message.prototype.copyFrom = function(message) { + goog.asserts.assert( + this.constructor == message.constructor, + 'The source message must have the same type.'); + + if (this != message) { + this.values_ = {}; + if (this.deserializedFields_) { + this.deserializedFields_ = {}; + } + this.mergeFrom(message); + } +}; + + +/** + * Merges the given message into this message. + * + * Singular fields will be overwritten, except for embedded messages which will + * be merged. Repeated fields will be concatenated. + * @param {!goog.proto2.Message} message The source message. + */ +goog.proto2.Message.prototype.mergeFrom = function(message) { + goog.asserts.assert( + this.constructor == message.constructor, + 'The source message must have the same type.'); + var fields = this.getDescriptor().getFields(); + + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var tag = field.getTag(); + if (message.has$Value(tag)) { + if (this.deserializedFields_) { + delete this.deserializedFields_[field.getTag()]; + } + + var isComposite = field.isCompositeType(); + if (field.isRepeated()) { + var values = message.array$Values(tag); + for (var j = 0; j < values.length; j++) { + this.add$Value(tag, isComposite ? values[j].clone() : values[j]); + } + } else { + var value = message.getValueForTag_(tag); + if (isComposite) { + var child = this.getValueForTag_(tag); + if (child) { + child.mergeFrom(value); + } else { + this.set$Value(tag, value.clone()); + } + } else { + this.set$Value(tag, value); + } + } + } + } +}; + + +/** + * @return {!goog.proto2.Message} Recursive clone of the message only including + * the known fields. + */ +goog.proto2.Message.prototype.clone = function() { + /** @type {!goog.proto2.Message} */ + var clone = new this.constructor; + clone.copyFrom(this); + return clone; +}; + + +/** + * Fills in the protocol buffer with default values. Any fields that are + * already set will not be overridden. + * @param {boolean} simpleFieldsToo If true, all fields will be initialized; + * if false, only the nested messages and groups. + */ +goog.proto2.Message.prototype.initDefaults = function(simpleFieldsToo) { + var fields = this.getDescriptor().getFields(); + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var tag = field.getTag(); + var isComposite = field.isCompositeType(); + + // Initialize missing fields. + if (!this.has$Value(tag) && !field.isRepeated()) { + if (isComposite) { + this.values_[tag] = new /** @type {Function} */ (field.getNativeType()); + } else if (simpleFieldsToo) { + this.values_[tag] = field.getDefaultValue(); + } + } + + // Fill in the existing composite fields recursively. + if (isComposite) { + if (field.isRepeated()) { + var values = this.array$Values(tag); + for (var j = 0; j < values.length; j++) { + values[j].initDefaults(simpleFieldsToo); + } + } else { + this.get$Value(tag).initDefaults(simpleFieldsToo); + } + } + } +}; + + +/** + * Returns the whether or not the field indicated by the given tag + * has a value. + * + * GENERATED CODE USE ONLY. Basis of the has{Field} methods. + * + * @param {number} tag The tag. + * + * @return {boolean} Whether the message has a value for the field. + */ +goog.proto2.Message.prototype.has$Value = function(tag) { + return this.values_[tag] != null; +}; + + +/** + * Returns the value for the given tag number. If a lazy deserializer is + * instantiated, lazily deserializes the field if required before returning the + * value. + * + * @param {number} tag The tag number. + * @return {?} The corresponding value, if any. + * @private + */ +goog.proto2.Message.prototype.getValueForTag_ = function(tag) { + // Retrieve the current value, which may still be serialized. + var value = this.values_[tag]; + if (!goog.isDefAndNotNull(value)) { + return null; + } + + // If we have a lazy deserializer, then ensure that the field is + // properly deserialized. + if (this.lazyDeserializer_) { + // If the tag is not deserialized, then we must do so now. Deserialize + // the field's value via the deserializer. + if (!(tag in /** @type {!Object} */ (this.deserializedFields_))) { + var deserializedValue = this.lazyDeserializer_.deserializeField( + this, this.fields_[tag], value); + this.deserializedFields_[tag] = deserializedValue; + return deserializedValue; + } + + return this.deserializedFields_[tag]; + } + + // Otherwise, just return the value. + return value; +}; + + +/** + * Gets the value at the field indicated by the given tag. + * + * GENERATED CODE USE ONLY. Basis of the get{Field} methods. + * + * @param {number} tag The field's tag index. + * @param {number=} opt_index If the field is a repeated field, the index + * at which to get the value. + * + * @return {?} The value found or null for none. + * @protected + */ +goog.proto2.Message.prototype.get$Value = function(tag, opt_index) { + var value = this.getValueForTag_(tag); + + if (this.fields_[tag].isRepeated()) { + var index = opt_index || 0; + goog.asserts.assert( + index >= 0 && index < value.length, + 'Given index %s is out of bounds. Repeated field length: %s', index, + value.length); + return value[index]; + } + + return value; +}; + + +/** + * Gets the value at the field indicated by the given tag or the default value + * if none. + * + * GENERATED CODE USE ONLY. Basis of the get{Field} methods. + * + * @param {number} tag The field's tag index. + * @param {number=} opt_index If the field is a repeated field, the index + * at which to get the value. + * + * @return {?} The value found or the default value if none set. + * @protected + */ +goog.proto2.Message.prototype.get$ValueOrDefault = function(tag, opt_index) { + if (!this.has$Value(tag)) { + // Return the default value. + var field = this.fields_[tag]; + return field.getDefaultValue(); + } + + return this.get$Value(tag, opt_index); +}; + + +/** + * Gets the values at the field indicated by the given tag. + * + * GENERATED CODE USE ONLY. Basis of the {field}Array methods. + * + * @param {number} tag The field's tag index. + * + * @return {!Array<?>} The values found. If none, returns an empty array. + * @protected + */ +goog.proto2.Message.prototype.array$Values = function(tag) { + var value = this.getValueForTag_(tag); + return value || []; +}; + + +/** + * Returns the number of values stored in the field by the given tag. + * + * GENERATED CODE USE ONLY. Basis of the {field}Count methods. + * + * @param {number} tag The tag. + * + * @return {number} The number of values. + * @protected + */ +goog.proto2.Message.prototype.count$Values = function(tag) { + var field = this.fields_[tag]; + if (field.isRepeated()) { + return this.has$Value(tag) ? this.values_[tag].length : 0; + } else { + return this.has$Value(tag) ? 1 : 0; + } +}; + + +/** + * Sets the value of the *non-repeating* field indicated by the given tag. + * + * GENERATED CODE USE ONLY. Basis of the set{Field} methods. + * + * @param {number} tag The field's tag index. + * @param {*} value The field's value. + * @protected + */ +goog.proto2.Message.prototype.set$Value = function(tag, value) { + if (goog.asserts.ENABLE_ASSERTS) { + var field = this.fields_[tag]; + this.checkFieldType_(field, value); + } + + this.values_[tag] = value; + if (this.deserializedFields_) { + this.deserializedFields_[tag] = value; + } +}; + + +/** + * Adds the value to the *repeating* field indicated by the given tag. + * + * GENERATED CODE USE ONLY. Basis of the add{Field} methods. + * + * @param {number} tag The field's tag index. + * @param {*} value The value to add. + * @protected + */ +goog.proto2.Message.prototype.add$Value = function(tag, value) { + if (goog.asserts.ENABLE_ASSERTS) { + var field = this.fields_[tag]; + this.checkFieldType_(field, value); + } + + if (!this.values_[tag]) { + this.values_[tag] = []; + } + + this.values_[tag].push(value); + if (this.deserializedFields_) { + delete this.deserializedFields_[tag]; + } +}; + + +/** + * Ensures that the value being assigned to the given field + * is valid. + * + * @param {!goog.proto2.FieldDescriptor} field The field being assigned. + * @param {*} value The value being assigned. + * @private + */ +goog.proto2.Message.prototype.checkFieldType_ = function(field, value) { + if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.ENUM) { + goog.asserts.assertNumber(value); + } else { + goog.asserts.assert(Object(value).constructor == field.getNativeType()); + } +}; + + +/** + * Clears the field specified by tag. + * + * GENERATED CODE USE ONLY. Basis of the clear{Field} methods. + * + * @param {number} tag The tag of the field to clear. + * @protected + */ +goog.proto2.Message.prototype.clear$Field = function(tag) { + delete this.values_[tag]; + if (this.deserializedFields_) { + delete this.deserializedFields_[tag]; + } +}; + + +/** + * Creates the metadata descriptor representing the definition of this message. + * + * @param {function(new:goog.proto2.Message)} messageType Constructor for the + * message type to which this metadata applies. + * @param {!Object} metadataObj The object containing the metadata. + * @return {!goog.proto2.Descriptor} The new descriptor. + */ +goog.proto2.Message.createDescriptor = function(messageType, metadataObj) { + var fields = []; + var descriptorInfo = metadataObj[0]; + + for (var key in metadataObj) { + if (key != 0) { + // Create the field descriptor. + fields.push( + new goog.proto2.FieldDescriptor(messageType, key, metadataObj[key])); + } + } + + return new goog.proto2.Descriptor(messageType, descriptorInfo, fields); +};
diff --git a/third_party/ink/closure/proto2/objectserializer.js b/third_party/ink/closure/proto2/objectserializer.js new file mode 100644 index 0000000..95fa5d3f --- /dev/null +++ b/third_party/ink/closure/proto2/objectserializer.js
@@ -0,0 +1,202 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Protocol Buffer 2 Serializer which serializes messages + * into anonymous, simplified JSON objects. + * + * @author jschorr@google.com (Joseph Schorr) + */ + +goog.provide('goog.proto2.ObjectSerializer'); + +goog.require('goog.asserts'); +goog.require('goog.proto2.FieldDescriptor'); +goog.require('goog.proto2.Serializer'); +goog.require('goog.string'); + + + +/** + * ObjectSerializer, a serializer which turns Messages into simplified + * ECMAScript objects. + * + * @param {goog.proto2.ObjectSerializer.KeyOption=} opt_keyOption If specified, + * which key option to use when serializing/deserializing. + * @param {boolean=} opt_serializeBooleanAsNumber If specified and true, the + * serializer will convert boolean values to 0/1 representation. + * @constructor + * @extends {goog.proto2.Serializer} + */ +goog.proto2.ObjectSerializer = function( + opt_keyOption, opt_serializeBooleanAsNumber) { + this.keyOption_ = opt_keyOption; + this.serializeBooleanAsNumber_ = opt_serializeBooleanAsNumber; +}; +goog.inherits(goog.proto2.ObjectSerializer, goog.proto2.Serializer); + + +/** + * An enumeration of the options for how to emit the keys in + * the generated simplified object. + * + * @enum {number} + */ +goog.proto2.ObjectSerializer.KeyOption = { + /** + * Use the tag of the field as the key (default) + */ + TAG: 0, + + /** + * Use the name of the field as the key. Unknown fields + * will still use their tags as keys. + */ + NAME: 1 +}; + + +/** + * Serializes a message to an object. + * + * @param {goog.proto2.Message} message The message to be serialized. + * @return {!Object} The serialized form of the message. + * @override + */ +goog.proto2.ObjectSerializer.prototype.serialize = function(message) { + var descriptor = message.getDescriptor(); + var fields = descriptor.getFields(); + + var objectValue = {}; + + // Add the defined fields, recursively. + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + + var key = this.keyOption_ == goog.proto2.ObjectSerializer.KeyOption.NAME ? + field.getName() : + field.getTag(); + + + if (message.has(field)) { + if (field.isRepeated()) { + var array = []; + objectValue[key] = array; + + for (var j = 0; j < message.countOf(field); j++) { + array.push(this.getSerializedValue(field, message.get(field, j))); + } + + } else { + objectValue[key] = this.getSerializedValue(field, message.get(field)); + } + } + } + + // Add the unknown fields, if any. + message.forEachUnknown(function(tag, value) { objectValue[tag] = value; }); + + return objectValue; +}; + + +/** @override */ +goog.proto2.ObjectSerializer.prototype.getSerializedValue = function( + field, value) { + + // Handle the case where a boolean should be serialized as 0/1. + // Some deserialization libraries, such as GWT, can use this notation. + if (this.serializeBooleanAsNumber_ && + field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.BOOL && + goog.isBoolean(value)) { + return value ? 1 : 0; + } + + return goog.proto2.ObjectSerializer.base( + this, 'getSerializedValue', field, value); +}; + + +/** @override */ +goog.proto2.ObjectSerializer.prototype.getDeserializedValue = function( + field, value) { + + // Gracefully handle the case where a boolean is represented by 0/1. + // Some serialization libraries, such as GWT, can use this notation. + if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.BOOL && + goog.isNumber(value)) { + return Boolean(value); + } + + return goog.proto2.ObjectSerializer.base( + this, 'getDeserializedValue', field, value); +}; + + +/** + * Deserializes a message from an object and places the + * data in the message. + * + * @param {goog.proto2.Message} message The message in which to + * place the information. + * @param {*} data The data of the message. + * @override + */ +goog.proto2.ObjectSerializer.prototype.deserializeTo = function(message, data) { + var descriptor = message.getDescriptor(); + + for (var key in data) { + var field; + var value = data[key]; + + var isNumeric = goog.string.isNumeric(key); + + if (isNumeric) { + field = descriptor.findFieldByTag(key); + } else { + // We must be in Key == NAME mode to lookup by name. + goog.asserts.assert( + this.keyOption_ == goog.proto2.ObjectSerializer.KeyOption.NAME, + 'Key mode ' + this.keyOption_ + 'for key ' + key + ' is not ' + + goog.proto2.ObjectSerializer.KeyOption.NAME); + + field = descriptor.findFieldByName(key); + } + + if (field) { + if (field.isRepeated()) { + goog.asserts.assert( + goog.isArray(value), + 'Value for repeated field ' + field + ' must be an array.'); + + for (var j = 0; j < value.length; j++) { + message.add(field, this.getDeserializedValue(field, value[j])); + } + } else { + goog.asserts.assert( + !goog.isArray(value), + 'Value for non-repeated field ' + field + ' must not be an array.'); + message.set(field, this.getDeserializedValue(field, value)); + } + } else { + if (isNumeric) { + // We have an unknown field. + message.setUnknown(Number(key), value); + } else { + // Named fields must be present. + goog.asserts.fail('Failed to find field: ' + key); + } + } + } +};
diff --git a/third_party/ink/closure/proto2/serializer.js b/third_party/ink/closure/proto2/serializer.js new file mode 100644 index 0000000..eb976d74 --- /dev/null +++ b/third_party/ink/closure/proto2/serializer.js
@@ -0,0 +1,198 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Base class for all Protocol Buffer 2 serializers. + * @author jschorr@google.com (Joseph Schorr) + */ + +goog.provide('goog.proto2.Serializer'); + +goog.require('goog.asserts'); +goog.require('goog.proto2.FieldDescriptor'); +goog.require('goog.proto2.Message'); + + + +/** + * Abstract base class for PB2 serializers. A serializer is a class which + * implements the serialization and deserialization of a Protocol Buffer Message + * to/from a specific format. + * + * @constructor + */ +goog.proto2.Serializer = function() {}; + + +/** + * @define {boolean} Whether to decode and convert symbolic enum values to + * actual enum values or leave them as strings. + */ +goog.define('goog.proto2.Serializer.DECODE_SYMBOLIC_ENUMS', false); + + +/** + * Serializes a message to the expected format. + * + * @param {goog.proto2.Message} message The message to be serialized. + * + * @return {*} The serialized form of the message. + */ +goog.proto2.Serializer.prototype.serialize = goog.abstractMethod; + + +/** + * Returns the serialized form of the given value for the given field if the + * field is a Message or Group and returns the value unchanged otherwise, except + * for Infinity, -Infinity and NaN numerical values which are converted to + * string representation. + * + * @param {goog.proto2.FieldDescriptor} field The field from which this + * value came. + * + * @param {*} value The value of the field. + * + * @return {*} The value. + * @protected + */ +goog.proto2.Serializer.prototype.getSerializedValue = function(field, value) { + if (field.isCompositeType()) { + return this.serialize(/** @type {goog.proto2.Message} */ (value)); + } else if (goog.isNumber(value) && !isFinite(value)) { + return value.toString(); + } else { + return value; + } +}; + + +/** + * Deserializes a message from the expected format. + * + * @param {goog.proto2.Descriptor} descriptor The descriptor of the message + * to be created. + * @param {*} data The data of the message. + * + * @return {!goog.proto2.Message} The message created. + */ +goog.proto2.Serializer.prototype.deserialize = function(descriptor, data) { + var message = descriptor.createMessageInstance(); + this.deserializeTo(message, data); + goog.asserts.assert(message instanceof goog.proto2.Message); + return message; +}; + + +/** + * Deserializes a message from the expected format and places the + * data in the message. + * + * @param {goog.proto2.Message} message The message in which to + * place the information. + * @param {*} data The data of the message. + */ +goog.proto2.Serializer.prototype.deserializeTo = goog.abstractMethod; + + +/** + * Returns the deserialized form of the given value for the given field if the + * field is a Message or Group and returns the value, converted or unchanged, + * for primitive field types otherwise. + * + * @param {goog.proto2.FieldDescriptor} field The field from which this + * value came. + * + * @param {*} value The value of the field. + * + * @return {*} The value. + * @protected + */ +goog.proto2.Serializer.prototype.getDeserializedValue = function(field, value) { + // Composite types are deserialized recursively. + if (field.isCompositeType()) { + if (value instanceof goog.proto2.Message) { + return value; + } + + return this.deserialize(field.getFieldMessageType(), value); + } + + // Decode enum values. + if (field.getFieldType() == goog.proto2.FieldDescriptor.FieldType.ENUM) { + // If it's a string, get enum value by name. + // NB: In order this feature to work, property renaming should be turned off + // for the respective enums. + if (goog.proto2.Serializer.DECODE_SYMBOLIC_ENUMS && goog.isString(value)) { + // enumType is a regular Javascript enum as defined in field's metadata. + var enumType = field.getNativeType(); + if (enumType.hasOwnProperty(value)) { + return enumType[value]; + } + } + + // If it's a string containing a positive integer, this looks like a viable + // enum int value. Return as numeric. + if (goog.isString(value) && + goog.proto2.Serializer.INTEGER_REGEX.test(value)) { + var numeric = Number(value); + if (numeric > 0) { + return numeric; + } + } + + // Return unknown values as is for backward compatibility. + return value; + } + + // Return the raw value if the field does not allow the JSON input to be + // converted. + if (!field.deserializationConversionPermitted()) { + return value; + } + + // Convert to native type of field. Return the converted value or fall + // through to return the raw value. The JSON encoding of int64 value 123 + // might be either the number 123 or the string "123". The field native type + // could be either Number or String (depending on field options in the .proto + // file). All four combinations should work correctly. + var nativeType = field.getNativeType(); + if (nativeType === String) { + // JSON numbers can be converted to strings. + if (goog.isNumber(value)) { + return String(value); + } + } else if (nativeType === Number) { + // JSON strings are sometimes used for large integer numeric values, as well + // as Infinity, -Infinity and NaN. + if (goog.isString(value)) { + // Handle +/- Infinity and NaN values. + if (value === 'Infinity' || value === '-Infinity' || value === 'NaN') { + return Number(value); + } + + // Validate the string. If the string is not an integral number, we would + // rather have an assertion or error in the caller than a mysterious NaN + // value. + if (goog.proto2.Serializer.INTEGER_REGEX.test(value)) { + return Number(value); + } + } + } + + return value; +}; + + +/** @const {!RegExp} */ +goog.proto2.Serializer.INTEGER_REGEX = /^-?[0-9]+$/;
diff --git a/third_party/ink/closure/reflect/reflect.js b/third_party/ink/closure/reflect/reflect.js new file mode 100644 index 0000000..b846fba6 --- /dev/null +++ b/third_party/ink/closure/reflect/reflect.js
@@ -0,0 +1,139 @@ +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Useful compiler idioms. + * + * @author mgoodman@google.com (Mark Goodman) + * @author johnlenz@google.com (John Lenz) + */ + +goog.provide('goog.reflect'); + + +/** + * Syntax for object literal casts. + * @see http://go/jscompiler-renaming + * @see https://goo.gl/CRs09P + * + * Use this if you have an object literal whose keys need to have the same names + * as the properties of some class even after they are renamed by the compiler. + * + * @param {!Function} type Type to cast to. + * @param {Object} object Object literal to cast. + * @return {Object} The object literal. + */ +goog.reflect.object = function(type, object) { + return object; +}; + +/** + * Syntax for renaming property strings. + * @see http://go/jscompiler-renaming + * @see https://goo.gl/CRs09P + * + * Use this if you have an need to access a property as a string, but want + * to also have the property renamed by the compiler. In contrast to + * goog.reflect.object, this method takes an instance of an object. + * + * Properties must be simple names (not qualified names). + * + * @param {string} prop Name of the property + * @param {!Object} object Instance of the object whose type will be used + * for renaming + * @return {string} The renamed property. + */ +goog.reflect.objectProperty = function(prop, object) { + return prop; +}; + +/** + * To assert to the compiler that an operation is needed when it would + * otherwise be stripped. For example: + * <code> + * // Force a layout + * goog.reflect.sinkValue(dialog.offsetHeight); + * </code> + * @param {T} x + * @return {T} + * @template T + */ +goog.reflect.sinkValue = function(x) { + goog.reflect.sinkValue[' '](x); + return x; +}; + + +/** + * The compiler should optimize this function away iff no one ever uses + * goog.reflect.sinkValue. + */ +goog.reflect.sinkValue[' '] = goog.nullFunction; + + +/** + * Check if a property can be accessed without throwing an exception. + * @param {Object} obj The owner of the property. + * @param {string} prop The property name. + * @return {boolean} Whether the property is accessible. Will also return true + * if obj is null. + */ +goog.reflect.canAccessProperty = function(obj, prop) { + + try { + goog.reflect.sinkValue(obj[prop]); + return true; + } catch (e) { + } + return false; +}; + + +/** + * Retrieves a value from a cache given a key. The compiler provides special + * consideration for this call such that it is generally considered side-effect + * free. However, if the {@code opt_keyFn} or {@code valueFn} have side-effects + * then the entire call is considered to have side-effects. + * + * Conventionally storing the value on the cache would be considered a + * side-effect and preclude unused calls from being pruned, ie. even if + * the value was never used, it would still always be stored in the cache. + * + * Providing a side-effect free {@code valueFn} and {@code opt_keyFn} + * allows unused calls to {@code goog.reflect.cache} to be pruned. + * + * @param {!Object<K, V>} cacheObj The object that contains the cached values. + * @param {?} key The key to lookup in the cache. If it is not string or number + * then a {@code opt_keyFn} should be provided. The key is also used as the + * parameter to the {@code valueFn}. + * @param {function(?):V} valueFn The value provider to use to calculate the + * value to store in the cache. This function should be side-effect free + * to take advantage of the optimization. + * @param {function(?):K=} opt_keyFn The key provider to determine the cache + * map key. This should be used if the given key is not a string or number. + * If not provided then the given key is used. This function should be + * side-effect free to take advantage of the optimization. + * @return {V} The cached or calculated value. + * @template K + * @template V + */ +goog.reflect.cache = function(cacheObj, key, valueFn, opt_keyFn) { + var storedKey = opt_keyFn ? opt_keyFn(key) : key; + + if (Object.prototype.hasOwnProperty.call(cacheObj, storedKey)) { + return cacheObj[storedKey]; + } + + return (cacheObj[storedKey] = valueFn(key)); +};
diff --git a/third_party/ink/closure/soy/data.js b/third_party/ink/closure/soy/data.js new file mode 100644 index 0000000..24e3307 --- /dev/null +++ b/third_party/ink/closure/soy/data.js
@@ -0,0 +1,525 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Soy data primitives. + * + * The goal is to encompass data types used by Soy, especially to mark content + * as known to be "safe". + * + * @author gboyer@google.com (Garrett Boyer) + */ + +goog.provide('goog.soy.data.SanitizedContent'); +goog.provide('goog.soy.data.SanitizedContentKind'); +goog.provide('goog.soy.data.SanitizedCss'); +goog.provide('goog.soy.data.SanitizedHtml'); +goog.provide('goog.soy.data.SanitizedHtmlAttribute'); +goog.provide('goog.soy.data.SanitizedJs'); +goog.provide('goog.soy.data.SanitizedStyle'); +goog.provide('goog.soy.data.SanitizedTrustedResourceUri'); +goog.provide('goog.soy.data.SanitizedUri'); +goog.provide('goog.soy.data.UnsanitizedText'); + +goog.require('goog.Uri'); +goog.require('goog.asserts'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.html.uncheckedconversions'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.string.Const'); + + +/** + * A type of textual content. + * + * This is an enum of type Object so that these values are unforgeable. + * + * @enum {!Object} + */ +goog.soy.data.SanitizedContentKind = { + + /** + * A snippet of HTML that does not start or end inside a tag, comment, entity, + * or DOCTYPE; and that does not contain any executable code + * (JS, {@code <object>}s, etc.) from a different trust domain. + */ + HTML: goog.DEBUG ? {sanitizedContentKindHtml: true} : {}, + + /** + * Executable Javascript code or expression, safe for insertion in a + * script-tag or event handler context, known to be free of any + * attacker-controlled scripts. This can either be side-effect-free + * Javascript (such as JSON) or Javascript that's entirely under Google's + * control. + */ + JS: goog.DEBUG ? {sanitizedContentJsChars: true} : {}, + + /** A properly encoded portion of a URI. */ + URI: goog.DEBUG ? {sanitizedContentUri: true} : {}, + + /** A resource URI not under attacker control. */ + TRUSTED_RESOURCE_URI: + goog.DEBUG ? {sanitizedContentTrustedResourceUri: true} : {}, + + /** + * Repeated attribute names and values. For example, + * {@code dir="ltr" foo="bar" onclick="trustedFunction()" checked}. + */ + ATTRIBUTES: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {}, + + // TODO: Consider separating rules, declarations, and values into + // separate types, but for simplicity, we'll treat explicitly blessed + // SanitizedContent as allowed in all of these contexts. + /** + * A CSS3 declaration, property, value or group of semicolon separated + * declarations. + */ + STYLE: goog.DEBUG ? {sanitizedContentStyle: true} : {}, + + /** A CSS3 style sheet (list of rules). */ + CSS: goog.DEBUG ? {sanitizedContentCss: true} : {}, + + /** + * Unsanitized plain-text content. + * + * This is effectively the "null" entry of this enum, and is sometimes used + * to explicitly mark content that should never be used unescaped. Since any + * string is safe to use as text, being of ContentKind.TEXT makes no + * guarantees about its safety in any other context such as HTML. + */ + TEXT: goog.DEBUG ? {sanitizedContentKindText: true} : {} +}; + + + +/** + * A string-like object that carries a content-type and a content direction. + * + * IMPORTANT! Do not create these directly, nor instantiate the subclasses. + * Instead, use a trusted, centrally reviewed library as endorsed by your team + * to generate these objects. Otherwise, you risk accidentally creating + * SanitizedContent that is attacker-controlled and gets evaluated unescaped in + * templates. + * + * @constructor + */ +goog.soy.data.SanitizedContent = function() { + throw new Error('Do not instantiate directly'); +}; + + +/** + * The context in which this content is safe from XSS attacks. + * @type {goog.soy.data.SanitizedContentKind} + */ +goog.soy.data.SanitizedContent.prototype.contentKind; + + +/** + * The content's direction; null if unknown and thus to be estimated when + * necessary. + * @type {?goog.i18n.bidi.Dir} + */ +goog.soy.data.SanitizedContent.prototype.contentDir = null; + + +/** + * The already-safe content. + * @protected {string} + */ +goog.soy.data.SanitizedContent.prototype.content; + + +/** + * Gets the already-safe content. + * @return {string} + */ +goog.soy.data.SanitizedContent.prototype.getContent = function() { + return this.content; +}; + + +/** @override */ +goog.soy.data.SanitizedContent.prototype.toString = function() { + return this.content; +}; + + +/** + * Converts sanitized content of kind TEXT or HTML into SafeHtml. HTML content + * is converted without modification, while text content is HTML-escaped. + * @return {!goog.html.SafeHtml} + * @throws {Error} when the content kind is not TEXT or HTML. + */ +goog.soy.data.SanitizedContent.prototype.toSafeHtml = function() { + if (this.contentKind === goog.soy.data.SanitizedContentKind.TEXT) { + return goog.html.SafeHtml.htmlEscape(this.toString()); + } + if (this.contentKind !== goog.soy.data.SanitizedContentKind.HTML) { + throw new Error('Sanitized content was not of kind TEXT or HTML.'); + } + return goog.html.uncheckedconversions + .safeHtmlFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Soy SanitizedContent of kind HTML produces ' + + 'SafeHtml-contract-compliant value.'), + this.toString(), this.contentDir); +}; + + +/** + * Converts sanitized content of kind URI into SafeUrl without modification. + * @return {!goog.html.SafeUrl} + * @throws {Error} when the content kind is not URI. + */ +goog.soy.data.SanitizedContent.prototype.toSafeUrl = function() { + if (this.contentKind !== goog.soy.data.SanitizedContentKind.URI) { + throw new Error('Sanitized content was not of kind URI.'); + } + return goog.html.uncheckedconversions + .safeUrlFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Soy SanitizedContent of kind URI produces ' + + 'SafeHtml-contract-compliant value.'), + this.toString()); +}; + + +/** + * Unsanitized plain text string. + * + * While all strings are effectively safe to use as a plain text, there are no + * guarantees about safety in any other context such as HTML. This is + * sometimes used to mark that should never be used unescaped. + * + * @param {*} content Plain text with no guarantees. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.UnsanitizedText = function(content, opt_contentDir) { + // Not calling the superclass constructor which just throws an exception. + + /** @override */ + this.content = String(content); + this.contentDir = opt_contentDir != null ? opt_contentDir : null; +}; +goog.inherits(goog.soy.data.UnsanitizedText, goog.soy.data.SanitizedContent); + + +/** @override */ +goog.soy.data.UnsanitizedText.prototype.contentKind = + goog.soy.data.SanitizedContentKind.TEXT; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.HTML}. + * + * The content is a string of HTML that can safely be embedded in a PCDATA + * context in your app. If you would be surprised to find that an HTML + * sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs) and + * you wouldn't write a template that produces {@code s} on security or privacy + * grounds, then don't pass {@code s} here. The default content direction is + * unknown, i.e. to be estimated when necessary. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedHtml = function() { + goog.soy.data.SanitizedHtml.base(this, 'constructor'); +}; +goog.inherits(goog.soy.data.SanitizedHtml, goog.soy.data.SanitizedContent); + +/** @override */ +goog.soy.data.SanitizedHtml.prototype.contentKind = + goog.soy.data.SanitizedContentKind.HTML; + +/** + * Checks if the value could be used as the Soy type {html}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedHtml.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedHtml || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.SafeHtml; +}; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.JS}. + * + * The content is JavaScript source that when evaluated does not execute any + * attacker-controlled scripts. The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedJs = function() { + goog.soy.data.SanitizedJs.base(this, 'constructor'); +}; +goog.inherits(goog.soy.data.SanitizedJs, goog.soy.data.SanitizedContent); + +/** @override */ +goog.soy.data.SanitizedJs.prototype.contentKind = + goog.soy.data.SanitizedContentKind.JS; + +/** @override */ +goog.soy.data.SanitizedJs.prototype.contentDir = goog.i18n.bidi.Dir.LTR; + +/** + * Checks if the value could be used as the Soy type {js}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedJs.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedJs || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.SafeScript; +}; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.URI}. + * + * The content is a URI chunk that the caller knows is safe to emit in a + * template. The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedUri = function() { + goog.soy.data.SanitizedUri.base(this, 'constructor'); +}; +goog.inherits(goog.soy.data.SanitizedUri, goog.soy.data.SanitizedContent); + +/** @override */ +goog.soy.data.SanitizedUri.prototype.contentKind = + goog.soy.data.SanitizedContentKind.URI; + +/** @override */ +goog.soy.data.SanitizedUri.prototype.contentDir = goog.i18n.bidi.Dir.LTR; + +/** + * Checks if the value could be used as the Soy type {uri}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedUri.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedUri || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.SafeUrl || + value instanceof goog.html.TrustedResourceUrl || + value instanceof goog.Uri; +}; + + + +/** + * Content of type + * {@link goog.soy.data.SanitizedContentKind.TRUSTED_RESOURCE_URI}. + * + * The content is a TrustedResourceUri chunk that is not under attacker control. + * The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedTrustedResourceUri = function() { + goog.soy.data.SanitizedTrustedResourceUri.base(this, 'constructor'); +}; +goog.inherits( + goog.soy.data.SanitizedTrustedResourceUri, goog.soy.data.SanitizedContent); + +/** @override */ +goog.soy.data.SanitizedTrustedResourceUri.prototype.contentKind = + goog.soy.data.SanitizedContentKind.TRUSTED_RESOURCE_URI; + +/** @override */ +goog.soy.data.SanitizedTrustedResourceUri.prototype.contentDir = + goog.i18n.bidi.Dir.LTR; + +/** + * Converts sanitized content into TrustedResourceUrl without modification. + * @return {!goog.html.TrustedResourceUrl} + */ +goog.soy.data.SanitizedTrustedResourceUri.prototype.toTrustedResourceUrl = + function() { + return goog.html.uncheckedconversions + .trustedResourceUrlFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Soy SanitizedContent of kind TRUSTED_RESOURCE_URI produces ' + + 'TrustedResourceUrl-contract-compliant value.'), + this.toString()); +}; + +/** + * Checks if the value could be used as the Soy type {trusted_resource_uri}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedTrustedResourceUri.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedTrustedResourceUri || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.TrustedResourceUrl; +}; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.ATTRIBUTES}. + * + * The content should be safely embeddable within an open tag, such as a + * key="value" pair. The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedHtmlAttribute = function() { + goog.soy.data.SanitizedHtmlAttribute.base(this, 'constructor'); +}; +goog.inherits( + goog.soy.data.SanitizedHtmlAttribute, goog.soy.data.SanitizedContent); + +/** @override */ +goog.soy.data.SanitizedHtmlAttribute.prototype.contentKind = + goog.soy.data.SanitizedContentKind.ATTRIBUTES; + +/** @override */ +goog.soy.data.SanitizedHtmlAttribute.prototype.contentDir = + goog.i18n.bidi.Dir.LTR; + +/** + * Checks if the value could be used as the Soy type {attribute}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedHtmlAttribute.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedHtmlAttribute || + value instanceof goog.soy.data.UnsanitizedText; +}; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.STYLE}. + * + * The content is non-attacker-exploitable CSS, such as {@code color:#c3d9ff}. + * The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedStyle = function() { + goog.soy.data.SanitizedStyle.base(this, 'constructor'); +}; +goog.inherits(goog.soy.data.SanitizedStyle, goog.soy.data.SanitizedContent); + + +/** @override */ +goog.soy.data.SanitizedStyle.prototype.contentKind = + goog.soy.data.SanitizedContentKind.STYLE; + + +/** @override */ +goog.soy.data.SanitizedStyle.prototype.contentDir = goog.i18n.bidi.Dir.LTR; + + +/** + * Checks if the value could be used as the Soy type {css}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedStyle.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedStyle || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.SafeStyle; +}; + + + +/** + * Content of type {@link goog.soy.data.SanitizedContentKind.CSS}. + * + * The content is non-attacker-exploitable CSS, such as {@code @import url(x)}. + * The content direction is LTR. + * + * @extends {goog.soy.data.SanitizedContent} + * @constructor + */ +goog.soy.data.SanitizedCss = function() { + goog.soy.data.SanitizedCss.base(this, 'constructor'); +}; +goog.inherits(goog.soy.data.SanitizedCss, goog.soy.data.SanitizedContent); + + +/** @override */ +goog.soy.data.SanitizedCss.prototype.contentKind = + goog.soy.data.SanitizedContentKind.CSS; + + +/** @override */ +goog.soy.data.SanitizedCss.prototype.contentDir = goog.i18n.bidi.Dir.LTR; + + +/** + * Checks if the value could be used as the Soy type {css}. + * @param {*} value + * @return {boolean} + */ +goog.soy.data.SanitizedCss.isCompatibleWith = function(value) { + return goog.isString(value) || + value instanceof goog.soy.data.SanitizedCss || + value instanceof goog.soy.data.UnsanitizedText || + value instanceof goog.html.SafeStyle || // TODO(jakubvrana): Delete. + value instanceof goog.html.SafeStyleSheet; +}; + + +/** + * Converts SanitizedCss into SafeStyleSheet. + * Note: SanitizedCss in Soy represents both SafeStyle and SafeStyleSheet in + * Closure. It's about to be split so that SanitizedCss represents only + * SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} + */ +goog.soy.data.SanitizedCss.prototype.toSafeStyleSheet = function() { + var value = this.toString(); + // TODO(jakubvrana): Remove this check when there's a separate type for style + // declaration. + goog.asserts.assert( + /[@{]|^\s*$/.test(value), + 'value doesn\'t look like style sheet: ' + value); + return goog.html.uncheckedconversions + .safeStyleSheetFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Soy SanitizedCss produces SafeStyleSheet-contract-compliant ' + + 'value.'), + value); +};
diff --git a/third_party/ink/closure/soy/soy.js b/third_party/ink/closure/soy/soy.js new file mode 100644 index 0000000..e10dd5d --- /dev/null +++ b/third_party/ink/closure/soy/soy.js
@@ -0,0 +1,298 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides utility methods to render soy template. + * @author kai@google.com (Kai Huang) + * @author ptucker@google.com (Philip Tucker) + * @author chrishenry@google.com (Chris Henry) + */ + +goog.provide('goog.soy'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.html.legacyconversions'); +goog.require('goog.soy.data.SanitizedContent'); +goog.require('goog.soy.data.SanitizedContentKind'); +goog.require('goog.string'); + + +/** + * @define {boolean} Whether to require all Soy templates to be "strict html". + * Soy templates that use strict autoescaping forbid noAutoescape along with + * many dangerous directives, and return a runtime type SanitizedContent that + * marks them as safe. + * + * If this flag is enabled, Soy templates will fail to render if a template + * returns plain text -- indicating it is a non-strict template. + */ +goog.define('goog.soy.REQUIRE_STRICT_AUTOESCAPE', false); + + +/** + * Type definition for strict Soy templates. Very useful when passing a template + * as an argument. + * @typedef {function(?, null=, ?Object<string, *>=): + * !goog.soy.data.SanitizedContent} + */ +goog.soy.StrictTemplate; + + +/** + * Type definition for strict Soy HTML templates. Very useful when passing + * a template as an argument. + * @typedef {function(?, null=, ?Object<string, *>=): + * !goog.soy.data.SanitizedHtml} + */ +goog.soy.StrictHtmlTemplate; + + +/** + * Sets the processed template as the innerHTML of an element. It is recommended + * to use this helper function instead of directly setting innerHTML in your + * hand-written code, so that it will be easier to audit the code for cross-site + * scripting vulnerabilities. + * + * @param {?Element} element The element whose content we are rendering into. + * @param {!goog.soy.data.SanitizedContent} templateResult The processed + * template of kind HTML or TEXT (which will be escaped). + * @template ARG_TYPES + */ +goog.soy.renderHtml = function(element, templateResult) { + element.innerHTML = goog.soy.ensureTemplateOutputHtml_(templateResult); +}; + + +/** + * Renders a Soy template and then set the output string as + * the innerHTML of an element. It is recommended to use this helper function + * instead of directly setting innerHTML in your hand-written code, so that it + * will be easier to audit the code for cross-site scripting vulnerabilities. + * + * @param {Element} element The element whose content we are rendering into. + * @param {?function(ARG_TYPES, Object<string, *>=):*| + * ?function(ARG_TYPES, null=, Object<string, *>=):*} template + * The Soy template defining the element's content. + * @param {ARG_TYPES=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + * @template ARG_TYPES + */ +goog.soy.renderElement = function( + element, template, opt_templateData, opt_injectedData) { + // Soy template parameter is only nullable for historical reasons. + goog.asserts.assert(template, 'Soy template may not be null.'); + element.innerHTML = goog.soy.ensureTemplateOutputHtml_( + template( + opt_templateData || goog.soy.defaultTemplateData_, undefined, + opt_injectedData)); +}; + + +/** + * Renders a Soy template into a single node or a document + * fragment. If the rendered HTML string represents a single node, then that + * node is returned (note that this is *not* a fragment, despite them name of + * the method). Otherwise a document fragment is returned containing the + * rendered nodes. + * + * @param {?function(ARG_TYPES, Object<string, *>=):*| + * ?function(ARG_TYPES, null=, Object<string, *>=):*} template + * The Soy template defining the element's content. + * @param {ARG_TYPES=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to + * create DOM nodes; defaults to {@code goog.dom.getDomHelper}. + * @return {!Node} The resulting node or document fragment. + * @template ARG_TYPES + */ +goog.soy.renderAsFragment = function( + template, opt_templateData, opt_injectedData, opt_domHelper) { + // Soy template parameter is only nullable for historical reasons. + goog.asserts.assert(template, 'Soy template may not be null.'); + var dom = opt_domHelper || goog.dom.getDomHelper(); + var output = template( + opt_templateData || goog.soy.defaultTemplateData_, undefined, + opt_injectedData); + var html = goog.soy.ensureTemplateOutputHtml_(output); + goog.soy.assertFirstTagValid_(html); + var safeHtml = output instanceof goog.soy.data.SanitizedContent ? + output.toSafeHtml() : + goog.html.legacyconversions.safeHtmlFromString(html); + return dom.safeHtmlToNode(safeHtml); +}; + + +/** + * Renders a Soy template into a single node. If the rendered + * HTML string represents a single node, then that node is returned. Otherwise, + * a DIV element is returned containing the rendered nodes. + * + * @param {?function(ARG_TYPES, Object<string, *>=):*| + * ?function(ARG_TYPES, null=, Object<string, *>=):*} template + * The Soy template defining the element's content. + * @param {ARG_TYPES=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + * @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to + * create DOM nodes; defaults to {@code goog.dom.getDomHelper}. + * @return {!Element} Rendered template contents, wrapped in a parent DIV + * element if necessary. + * @template ARG_TYPES + */ +goog.soy.renderAsElement = function( + template, opt_templateData, opt_injectedData, opt_domHelper) { + // Soy template parameter is only nullable for historical reasons. + goog.asserts.assert(template, 'Soy template may not be null.'); + return goog.soy.convertToElement_( + template( + opt_templateData || goog.soy.defaultTemplateData_, undefined, + opt_injectedData), + opt_domHelper); +}; + + +/** + * Converts a processed Soy template into a single node. If the rendered + * HTML string represents a single node, then that node is returned. Otherwise, + * a DIV element is returned containing the rendered nodes. + * + * @param {!goog.soy.data.SanitizedContent} templateResult The processed + * template of kind HTML or TEXT (which will be escaped). + * @param {?goog.dom.DomHelper=} opt_domHelper The DOM helper used to + * create DOM nodes; defaults to {@code goog.dom.getDomHelper}. + * @return {!Element} Rendered template contents, wrapped in a parent DIV + * element if necessary. + */ +goog.soy.convertToElement = function(templateResult, opt_domHelper) { + return goog.soy.convertToElement_(templateResult, opt_domHelper); +}; + + +/** + * Non-strict version of {@code goog.soy.convertToElement}. + * + * @param {*} templateResult The processed template. + * @param {?goog.dom.DomHelper=} opt_domHelper The DOM helper used to + * create DOM nodes; defaults to {@code goog.dom.getDomHelper}. + * @return {!Element} Rendered template contents, wrapped in a parent DIV + * element if necessary. + * @private + */ +goog.soy.convertToElement_ = function(templateResult, opt_domHelper) { + var dom = opt_domHelper || goog.dom.getDomHelper(); + var wrapper = dom.createElement(goog.dom.TagName.DIV); + var html = goog.soy.ensureTemplateOutputHtml_(templateResult); + goog.soy.assertFirstTagValid_(html); + wrapper.innerHTML = html; + + // If the template renders as a single element, return it. + if (wrapper.childNodes.length == 1) { + var firstChild = wrapper.firstChild; + if (firstChild.nodeType == goog.dom.NodeType.ELEMENT) { + return /** @type {!Element} */ (firstChild); + } + } + + // Otherwise, return the wrapper DIV. + return wrapper; +}; + + +/** + * Ensures the result is "safe" to insert as HTML. + * + * Note if the template has non-strict autoescape, the guarantees here are very + * weak. It is recommended applications switch to requiring strict + * autoescaping over time by tweaking goog.soy.REQUIRE_STRICT_AUTOESCAPE. + * + * In the case the argument is a SanitizedContent object, it either must + * already be of kind HTML, or if it is kind="text", the output will be HTML + * escaped. + * + * @param {*} templateResult The template result. + * @return {string} The assumed-safe HTML output string. + * @private + */ +goog.soy.ensureTemplateOutputHtml_ = function(templateResult) { + // Allow strings as long as strict autoescaping is not mandated. Note we + // allow everything that isn't an object, because some non-escaping templates + // end up returning non-strings if their only print statement is a + // non-escaped argument, plus some unit tests spoof templates. + // TODO(gboyer): Track down and fix these cases. + if (!goog.soy.REQUIRE_STRICT_AUTOESCAPE && !goog.isObject(templateResult)) { + return String(templateResult); + } + + // Allow SanitizedContent of kind HTML. + if (templateResult instanceof goog.soy.data.SanitizedContent) { + templateResult = + /** @type {!goog.soy.data.SanitizedContent} */ (templateResult); + var ContentKind = goog.soy.data.SanitizedContentKind; + if (templateResult.contentKind === ContentKind.HTML) { + return goog.asserts.assertString(templateResult.getContent()); + } + if (templateResult.contentKind === ContentKind.TEXT) { + // Allow text to be rendered, as long as we escape it. Other content + // kinds will fail, since we don't know what to do with them. + // TODO(gboyer): Perhaps also include URI in this case. + return goog.string.htmlEscape(templateResult.getContent()); + } + } + + goog.asserts.fail( + 'Soy template output is unsafe for use as HTML: ' + templateResult); + + // In production, return a safe string, rather than failing hard. + return 'zSoyz'; +}; + + +/** + * Checks that the rendered HTML does not start with an invalid tag that would + * likely cause unexpected output from renderAsElement or renderAsFragment. + * See {@link http://www.w3.org/TR/html5/semantics.html#semantics} for reference + * as to which HTML elements can be parents of each other. + * @param {string} html The output of a template. + * @private + */ +goog.soy.assertFirstTagValid_ = function(html) { + if (goog.asserts.ENABLE_ASSERTS) { + var matches = html.match(goog.soy.INVALID_TAG_TO_RENDER_); + goog.asserts.assert( + !matches, 'This template starts with a %s, which ' + + 'cannot be a child of a <div>, as required by soy internals. ' + + 'Consider using goog.soy.renderElement instead.\nTemplate output: %s', + matches && matches[0], html); + } +}; + + +/** + * A pattern to find templates that cannot be rendered by renderAsElement or + * renderAsFragment, as these elements cannot exist as the child of a <div>. + * @type {!RegExp} + * @private + */ +goog.soy.INVALID_TAG_TO_RENDER_ = + /^<(body|caption|col|colgroup|head|html|tr|td|th|tbody|thead|tfoot)>/i; + + +/** + * Immutable object that is passed into templates that are rendered + * without any data. + * @private @const + */ +goog.soy.defaultTemplateData_ = {};
diff --git a/third_party/ink/closure/string/const.js b/third_party/ink/closure/string/const.js new file mode 100644 index 0000000..30bfc4e --- /dev/null +++ b/third_party/ink/closure/string/const.js
@@ -0,0 +1,186 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.string.Const'); + +goog.require('goog.asserts'); +goog.require('goog.string.TypedString'); + + + +/** + * Wrapper for compile-time-constant strings. + * + * Const is a wrapper for strings that can only be created from program + * constants (i.e., string literals). This property relies on a custom Closure + * compiler check that {@code goog.string.Const.from} is only invoked on + * compile-time-constant expressions. + * + * Const is useful in APIs whose correct and secure use requires that certain + * arguments are not attacker controlled: Compile-time constants are inherently + * under the control of the application and not under control of external + * attackers, and hence are safe to use in such contexts. + * + * Instances of this type must be created via its factory method + * {@code goog.string.Const.from} and not by invoking its constructor. The + * constructor intentionally takes no parameters and the type is immutable; + * hence only a default instance corresponding to the empty string can be + * obtained via constructor invocation. + * + * @see goog.string.Const#from + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} + */ +goog.string.Const = function() { + /** + * The wrapped value of this Const object. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.string.Const#unwrap + * @const {!Object} + * @private + */ + this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ = + goog.string.Const.TYPE_MARKER_; +}; + + +/** + * @override + * @const + */ +goog.string.Const.prototype.implementsGoogStringTypedString = true; + + +/** + * Returns this Const's value a string. + * + * IMPORTANT: In code where it is security-relevant that an object's type is + * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap} + * instead of this method. + * + * @see goog.string.Const#unwrap + * @override + */ +goog.string.Const.prototype.getTypedStringValue = function() { + return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_; +}; + + +/** + * Returns a debug-string representation of this value. + * + * To obtain the actual string value wrapped inside an object of this type, + * use {@code goog.string.Const.unwrap}. + * + * @see goog.string.Const#unwrap + * @override + */ +goog.string.Const.prototype.toString = function() { + return 'Const{' + + this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ + + '}'; +}; + + +/** + * Performs a runtime check that the provided object is indeed an instance + * of {@code goog.string.Const}, and returns its value. + * @param {!goog.string.Const} stringConst The object to extract from. + * @return {string} The Const object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.string.Const.unwrap = function(stringConst) { + // Perform additional run-time type-checking to ensure that stringConst is + // indeed an instance of the expected type. This provides some additional + // protection against security bugs due to application code that disables type + // checks. + if (stringConst instanceof goog.string.Const && + stringConst.constructor === goog.string.Const && + stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ === + goog.string.Const.TYPE_MARKER_) { + return stringConst + .stringConstValueWithSecurityContract__googStringSecurityPrivate_; + } else { + goog.asserts.fail( + 'expected object of type Const, got \'' + stringConst + '\''); + return 'type_error:Const'; + } +}; + + +/** + * Creates a Const object from a compile-time constant string. + * + * It is illegal to invoke this function on an expression whose + * compile-time-contant value cannot be determined by the Closure compiler. + * + * Correct invocations include, + * <pre> + * var s = goog.string.Const.from('hello'); + * var t = goog.string.Const.from('hello' + 'world'); + * </pre> + * + * In contrast, the following are illegal: + * <pre> + * var s = goog.string.Const.from(getHello()); + * var t = goog.string.Const.from('hello' + world); + * </pre> + * + * @param {string} s A constant string from which to create a Const. + * @return {!goog.string.Const} A Const object initialized to stringConst. + */ +goog.string.Const.from = function(s) { + return goog.string.Const.create__googStringSecurityPrivate_(s); +}; + + +/** + * Type marker for the Const type, used to implement additional run-time + * type checking. + * @const {!Object} + * @private + */ +goog.string.Const.TYPE_MARKER_ = {}; + + +/** + * Utility method to create Const instances. + * @param {string} s The string to initialize the Const object with. + * @return {!goog.string.Const} The initialized Const object. + * @private + */ +goog.string.Const.create__googStringSecurityPrivate_ = function(s) { + var stringConst = new goog.string.Const(); + stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = + s; + return stringConst; +}; + + +/** + * A Const instance wrapping the empty string. + * @const {!goog.string.Const} + */ +goog.string.Const.EMPTY = goog.string.Const.from('');
diff --git a/third_party/ink/closure/string/string.js b/third_party/ink/closure/string/string.js new file mode 100644 index 0000000..8e8c1c4f --- /dev/null +++ b/third_party/ink/closure/string/string.js
@@ -0,0 +1,1642 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for string manipulation. + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + */ + + +/** + * Namespace for string utilities + */ +goog.provide('goog.string'); +goog.provide('goog.string.Unicode'); + + +/** + * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps + * with detection of double-escaping as this letter is frequently used. + */ +goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false); + + +/** + * @define {boolean} Whether to force non-dom html unescaping. + */ +goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false); + + +/** + * Common Unicode string characters. + * @enum {string} + */ +goog.string.Unicode = { + NBSP: '\xa0' +}; + + +/** + * Fast prefix-checker. + * @param {string} str The string to check. + * @param {string} prefix A string to look for at the start of {@code str}. + * @return {boolean} True if {@code str} begins with {@code prefix}. + */ +goog.string.startsWith = function(str, prefix) { + return str.lastIndexOf(prefix, 0) == 0; +}; + + +/** + * Fast suffix-checker. + * @param {string} str The string to check. + * @param {string} suffix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} ends with {@code suffix}. + */ +goog.string.endsWith = function(str, suffix) { + var l = str.length - suffix.length; + return l >= 0 && str.indexOf(suffix, l) == l; +}; + + +/** + * Case-insensitive prefix-checker. + * @param {string} str The string to check. + * @param {string} prefix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring + * case). + */ +goog.string.caseInsensitiveStartsWith = function(str, prefix) { + return goog.string.caseInsensitiveCompare( + prefix, str.substr(0, prefix.length)) == 0; +}; + + +/** + * Case-insensitive suffix-checker. + * @param {string} str The string to check. + * @param {string} suffix A string to look for at the end of {@code str}. + * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring + * case). + */ +goog.string.caseInsensitiveEndsWith = function(str, suffix) { + return ( + goog.string.caseInsensitiveCompare( + suffix, str.substr(str.length - suffix.length, suffix.length)) == 0); +}; + + +/** + * Case-insensitive equality checker. + * @param {string} str1 First string to check. + * @param {string} str2 Second string to check. + * @return {boolean} True if {@code str1} and {@code str2} are the same string, + * ignoring case. + */ +goog.string.caseInsensitiveEquals = function(str1, str2) { + return str1.toLowerCase() == str2.toLowerCase(); +}; + + +/** + * Does simple python-style string substitution. + * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog". + * @param {string} str The string containing the pattern. + * @param {...*} var_args The items to substitute into the pattern. + * @return {string} A copy of {@code str} in which each occurrence of + * {@code %s} has been replaced an argument from {@code var_args}. + */ +goog.string.subs = function(str, var_args) { + var splitParts = str.split('%s'); + var returnString = ''; + + var subsArguments = Array.prototype.slice.call(arguments, 1); + while (subsArguments.length && + // Replace up to the last split part. We are inserting in the + // positions between split parts. + splitParts.length > 1) { + returnString += splitParts.shift() + subsArguments.shift(); + } + + return returnString + splitParts.join('%s'); // Join unused '%s' +}; + + +/** + * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines + * and tabs) to a single space, and strips leading and trailing whitespace. + * @param {string} str Input string. + * @return {string} A copy of {@code str} with collapsed whitespace. + */ +goog.string.collapseWhitespace = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, ''); +}; + + +/** + * Checks if a string is empty or contains only whitespaces. + * @param {string} str The string to check. + * @return {boolean} Whether {@code str} is empty or whitespace only. + */ +goog.string.isEmptyOrWhitespace = function(str) { + // testing length == 0 first is actually slower in all browsers (about the + // same in Opera). + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return /^[\s\xa0]*$/.test(str); +}; + + +/** + * Checks if a string is empty. + * @param {string} str The string to check. + * @return {boolean} Whether {@code str} is empty. + */ +goog.string.isEmptyString = function(str) { + return str.length == 0; +}; + + +/** + * Checks if a string is empty or contains only whitespaces. + * + * @param {string} str The string to check. + * @return {boolean} Whether {@code str} is empty or whitespace only. + * @deprecated Use goog.string.isEmptyOrWhitespace instead. + */ +goog.string.isEmpty = goog.string.isEmptyOrWhitespace; + + +/** + * Checks if a string is null, undefined, empty or contains only whitespaces. + * @param {*} str The string to check. + * @return {boolean} Whether {@code str} is null, undefined, empty, or + * whitespace only. + * @deprecated Use goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str)) + * instead. + */ +goog.string.isEmptyOrWhitespaceSafe = function(str) { + return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str)); +}; + + +/** + * Checks if a string is null, undefined, empty or contains only whitespaces. + * + * @param {*} str The string to check. + * @return {boolean} Whether {@code str} is null, undefined, empty, or + * whitespace only. + * @deprecated Use goog.string.isEmptyOrWhitespace instead. + */ +goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe; + + +/** + * Checks if a string is all breaking whitespace. + * @param {string} str The string to check. + * @return {boolean} Whether the string is all breaking whitespace. + */ +goog.string.isBreakingWhitespace = function(str) { + return !/[^\t\n\r ]/.test(str); +}; + + +/** + * Checks if a string contains all letters. + * @param {string} str string to check. + * @return {boolean} True if {@code str} consists entirely of letters. + */ +goog.string.isAlpha = function(str) { + return !/[^a-zA-Z]/.test(str); +}; + + +/** + * Checks if a string contains only numbers. + * @param {*} str string to check. If not a string, it will be + * casted to one. + * @return {boolean} True if {@code str} is numeric. + */ +goog.string.isNumeric = function(str) { + return !/[^0-9]/.test(str); +}; + + +/** + * Checks if a string contains only numbers or letters. + * @param {string} str string to check. + * @return {boolean} True if {@code str} is alphanumeric. + */ +goog.string.isAlphaNumeric = function(str) { + return !/[^a-zA-Z0-9]/.test(str); +}; + + +/** + * Checks if a character is a space character. + * @param {string} ch Character to check. + * @return {boolean} True if {@code ch} is a space. + */ +goog.string.isSpace = function(ch) { + return ch == ' '; +}; + + +/** + * Checks if a character is a valid unicode character. + * @param {string} ch Character to check. + * @return {boolean} True if {@code ch} is a valid unicode character. + */ +goog.string.isUnicodeChar = function(ch) { + return ch.length == 1 && ch >= ' ' && ch <= '~' || + ch >= '\u0080' && ch <= '\uFFFD'; +}; + + +/** + * Takes a string and replaces newlines with a space. Multiple lines are + * replaced with a single space. + * @param {string} str The string from which to strip newlines. + * @return {string} A copy of {@code str} stripped of newlines. + */ +goog.string.stripNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)+/g, ' '); +}; + + +/** + * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n. + * @param {string} str The string to in which to canonicalize newlines. + * @return {string} {@code str} A copy of {@code} with canonicalized newlines. + */ +goog.string.canonicalizeNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)/g, '\n'); +}; + + +/** + * Normalizes whitespace in a string, replacing all whitespace chars with + * a space. + * @param {string} str The string in which to normalize whitespace. + * @return {string} A copy of {@code str} with all whitespace normalized. + */ +goog.string.normalizeWhitespace = function(str) { + return str.replace(/\xa0|\s/g, ' '); +}; + + +/** + * Normalizes spaces in a string, replacing all consecutive spaces and tabs + * with a single space. Replaces non-breaking space with a space. + * @param {string} str The string in which to normalize spaces. + * @return {string} A copy of {@code str} with all consecutive spaces and tabs + * replaced with a single space. + */ +goog.string.normalizeSpaces = function(str) { + return str.replace(/\xa0|[ \t]+/g, ' '); +}; + + +/** + * Removes the breaking spaces from the left and right of the string and + * collapses the sequences of breaking spaces in the middle into single spaces. + * The original and the result strings render the same way in HTML. + * @param {string} str A string in which to collapse spaces. + * @return {string} Copy of the string with normalized breaking spaces. + */ +goog.string.collapseBreakingSpaces = function(str) { + return str.replace(/[\t\r\n ]+/g, ' ') + .replace(/^[\t\r\n ]+|[\t\r\n ]+$/g, ''); +}; + + +/** + * Trims white spaces to the left and right of a string. + * @param {string} str The string to trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trim = + (goog.TRUSTED_SITE && String.prototype.trim) ? function(str) { + return str.trim(); + } : function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s + // character class (as required by section 7.2 of the ECMAScript spec), + // we explicitly include it in the regexp to enforce consistent + // cross-browser behavior. + return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); + }; + + +/** + * Trims whitespaces at the left end of a string. + * @param {string} str The string to left trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trimLeft = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/^[\s\xa0]+/, ''); +}; + + +/** + * Trims whitespaces at the right end of a string. + * @param {string} str The string to right trim. + * @return {string} A trimmed copy of {@code str}. + */ +goog.string.trimRight = function(str) { + // Since IE doesn't include non-breaking-space (0xa0) in their \s character + // class (as required by section 7.2 of the ECMAScript spec), we explicitly + // include it in the regexp to enforce consistent cross-browser behavior. + return str.replace(/[\s\xa0]+$/, ''); +}; + + +/** + * A string comparator that ignores case. + * -1 = str1 less than str2 + * 0 = str1 equals str2 + * 1 = str1 greater than str2 + * + * @param {string} str1 The string to compare. + * @param {string} str2 The string to compare {@code str1} to. + * @return {number} The comparator result, as described above. + */ +goog.string.caseInsensitiveCompare = function(str1, str2) { + var test1 = String(str1).toLowerCase(); + var test2 = String(str2).toLowerCase(); + + if (test1 < test2) { + return -1; + } else if (test1 == test2) { + return 0; + } else { + return 1; + } +}; + + +/** + * Compares two strings interpreting their numeric substrings as numbers. + * + * @param {string} str1 First string. + * @param {string} str2 Second string. + * @param {!RegExp} tokenizerRegExp Splits a string into substrings of + * non-negative integers, non-numeric characters and optionally fractional + * numbers starting with a decimal point. + * @return {number} Negative if str1 < str2, 0 is str1 == str2, positive if + * str1 > str2. + * @private + */ +goog.string.numberAwareCompare_ = function(str1, str2, tokenizerRegExp) { + if (str1 == str2) { + return 0; + } + if (!str1) { + return -1; + } + if (!str2) { + return 1; + } + + // Using match to split the entire string ahead of time turns out to be faster + // for most inputs than using RegExp.exec or iterating over each character. + var tokens1 = str1.toLowerCase().match(tokenizerRegExp); + var tokens2 = str2.toLowerCase().match(tokenizerRegExp); + + var count = Math.min(tokens1.length, tokens2.length); + + for (var i = 0; i < count; i++) { + var a = tokens1[i]; + var b = tokens2[i]; + + // Compare pairs of tokens, returning if one token sorts before the other. + if (a != b) { + // Only if both tokens are integers is a special comparison required. + // Decimal numbers are sorted as strings (e.g., '.09' < '.1'). + var num1 = parseInt(a, 10); + if (!isNaN(num1)) { + var num2 = parseInt(b, 10); + if (!isNaN(num2) && num1 - num2) { + return num1 - num2; + } + } + return a < b ? -1 : 1; + } + } + + // If one string is a substring of the other, the shorter string sorts first. + if (tokens1.length != tokens2.length) { + return tokens1.length - tokens2.length; + } + + // The two strings must be equivalent except for case (perfect equality is + // tested at the head of the function.) Revert to default ASCII string + // comparison to stabilize the sort. + return str1 < str2 ? -1 : 1; +}; + + +/** + * String comparison function that handles non-negative integer numbers in a + * way humans might expect. Using this function, the string 'File 2.jpg' sorts + * before 'File 10.jpg', and 'Version 1.9' before 'Version 1.10'. The comparison + * is mostly case-insensitive, though strings that are identical except for case + * are sorted with the upper-case strings before lower-case. + * + * This comparison function is up to 50x slower than either the default or the + * case-insensitive compare. It should not be used in time-critical code, but + * should be fast enough to sort several hundred short strings (like filenames) + * with a reasonable delay. + * + * @param {string} str1 The string to compare in a numerically sensitive way. + * @param {string} str2 The string to compare {@code str1} to. + * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than + * 0 if str1 > str2. + */ +goog.string.intAwareCompare = function(str1, str2) { + return goog.string.numberAwareCompare_(str1, str2, /\d+|\D+/g); +}; + + +/** + * String comparison function that handles non-negative integer and fractional + * numbers in a way humans might expect. Using this function, the string + * 'File 2.jpg' sorts before 'File 10.jpg', and '3.14' before '3.2'. Equivalent + * to {@link goog.string.intAwareCompare} apart from the way how it interprets + * dots. + * + * @param {string} str1 The string to compare in a numerically sensitive way. + * @param {string} str2 The string to compare {@code str1} to. + * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than + * 0 if str1 > str2. + */ +goog.string.floatAwareCompare = function(str1, str2) { + return goog.string.numberAwareCompare_(str1, str2, /\d+|\.\d+|\D+/g); +}; + + +/** + * Alias for {@link goog.string.floatAwareCompare}. + * + * @param {string} str1 + * @param {string} str2 + * @return {number} + */ +goog.string.numerateCompare = goog.string.floatAwareCompare; + + +/** + * URL-encodes a string + * @param {*} str The string to url-encode. + * @return {string} An encoded copy of {@code str} that is safe for urls. + * Note that '#', ':', and other characters used to delimit portions + * of URLs *will* be encoded. + */ +goog.string.urlEncode = function(str) { + return encodeURIComponent(String(str)); +}; + + +/** + * URL-decodes the string. We need to specially handle '+'s because + * the javascript library doesn't convert them to spaces. + * @param {string} str The string to url decode. + * @return {string} The decoded {@code str}. + */ +goog.string.urlDecode = function(str) { + return decodeURIComponent(str.replace(/\+/g, ' ')); +}; + + +/** + * Converts \n to <br>s or <br />s. + * @param {string} str The string in which to convert newlines. + * @param {boolean=} opt_xml Whether to use XML compatible tags. + * @return {string} A copy of {@code str} with converted newlines. + */ +goog.string.newLineToBr = function(str, opt_xml) { + return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>'); +}; + + +/** + * Escapes double quote '"' and single quote '\'' characters in addition to + * '&', '<', and '>' so that a string can be included in an HTML tag attribute + * value within double or single quotes. + * + * It should be noted that > doesn't need to be escaped for the HTML or XML to + * be valid, but it has been decided to escape it for consistency with other + * implementations. + * + * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the + * lowercase letter "e". + * + * NOTE(pupius): + * HtmlEscape is often called during the generation of large blocks of HTML. + * Using statics for the regular expressions and strings is an optimization + * that can more than half the amount of time IE spends in this function for + * large apps, since strings and regexes both contribute to GC allocations. + * + * Testing for the presence of a character before escaping increases the number + * of function calls, but actually provides a speed increase for the average + * case -- since the average case often doesn't require the escaping of all 4 + * characters and indexOf() is much cheaper than replace(). + * The worst case does suffer slightly from the additional calls, therefore the + * opt_isLikelyToContainHtmlChars option has been included for situations + * where all 4 HTML entities are very likely to be present and need escaping. + * + * Some benchmarks (times tended to fluctuate +-0.05ms): + * FireFox IE6 + * (no chars / average (mix of cases) / all 4 chars) + * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80 + * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84 + * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85 + * + * An additional advantage of checking if replace actually needs to be called + * is a reduction in the number of object allocations, so as the size of the + * application grows the difference between the various methods would increase. + * + * @param {string} str string to be escaped. + * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see + * if the character needs replacing - use this option if you expect each of + * the characters to appear often. Leave false if you expect few html + * characters to occur in your strings, such as if you are escaping HTML. + * @return {string} An escaped copy of {@code str}. + */ +goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) { + + if (opt_isLikelyToContainHtmlChars) { + str = str.replace(goog.string.AMP_RE_, '&') + .replace(goog.string.LT_RE_, '<') + .replace(goog.string.GT_RE_, '>') + .replace(goog.string.QUOT_RE_, '"') + .replace(goog.string.SINGLE_QUOTE_RE_, ''') + .replace(goog.string.NULL_RE_, '�'); + if (goog.string.DETECT_DOUBLE_ESCAPING) { + str = str.replace(goog.string.E_RE_, 'e'); + } + return str; + + } else { + // quick test helps in the case when there are no chars to replace, in + // worst case this makes barely a difference to the time taken + if (!goog.string.ALL_RE_.test(str)) return str; + + // str.indexOf is faster than regex.test in this case + if (str.indexOf('&') != -1) { + str = str.replace(goog.string.AMP_RE_, '&'); + } + if (str.indexOf('<') != -1) { + str = str.replace(goog.string.LT_RE_, '<'); + } + if (str.indexOf('>') != -1) { + str = str.replace(goog.string.GT_RE_, '>'); + } + if (str.indexOf('"') != -1) { + str = str.replace(goog.string.QUOT_RE_, '"'); + } + if (str.indexOf('\'') != -1) { + str = str.replace(goog.string.SINGLE_QUOTE_RE_, '''); + } + if (str.indexOf('\x00') != -1) { + str = str.replace(goog.string.NULL_RE_, '�'); + } + if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) { + str = str.replace(goog.string.E_RE_, 'e'); + } + return str; + } +}; + + +/** + * Regular expression that matches an ampersand, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.AMP_RE_ = /&/g; + + +/** + * Regular expression that matches a less than sign, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.LT_RE_ = /</g; + + +/** + * Regular expression that matches a greater than sign, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.GT_RE_ = />/g; + + +/** + * Regular expression that matches a double quote, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.QUOT_RE_ = /"/g; + + +/** + * Regular expression that matches a single quote, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.SINGLE_QUOTE_RE_ = /'/g; + + +/** + * Regular expression that matches null character, for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.NULL_RE_ = /\x00/g; + + +/** + * Regular expression that matches a lowercase letter "e", for use in escaping. + * @const {!RegExp} + * @private + */ +goog.string.E_RE_ = /e/g; + + +/** + * Regular expression that matches any character that needs to be escaped. + * @const {!RegExp} + * @private + */ +goog.string.ALL_RE_ = + (goog.string.DETECT_DOUBLE_ESCAPING ? /[\x00&<>"'e]/ : /[\x00&<>"']/); + + +/** + * Unescapes an HTML string. + * + * @param {string} str The string to unescape. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapeEntities = function(str) { + if (goog.string.contains(str, '&')) { + // We are careful not to use a DOM if we do not have one or we explicitly + // requested non-DOM html unescaping. + if (!goog.string.FORCE_NON_DOM_HTML_UNESCAPING && + 'document' in goog.global) { + return goog.string.unescapeEntitiesUsingDom_(str); + } else { + // Fall back on pure XML entities + return goog.string.unescapePureXmlEntities_(str); + } + } + return str; +}; + + +/** + * Unescapes a HTML string using the provided document. + * + * @param {string} str The string to unescape. + * @param {!Document} document A document to use in escaping the string. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapeEntitiesWithDocument = function(str, document) { + if (goog.string.contains(str, '&')) { + return goog.string.unescapeEntitiesUsingDom_(str, document); + } + return str; +}; + + +/** + * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric + * entities. This function is XSS-safe and whitespace-preserving. + * @private + * @param {string} str The string to unescape. + * @param {Document=} opt_document An optional document to use for creating + * elements. If this is not specified then the default window.document + * will be used. + * @return {string} The unescaped {@code str} string. + */ +goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) { + /** @type {!Object<string, string>} */ + var seen = {'&': '&', '<': '<', '>': '>', '"': '"'}; + var div; + if (opt_document) { + div = opt_document.createElement('div'); + } else { + div = goog.global.document.createElement('div'); + } + // Match as many valid entity characters as possible. If the actual entity + // happens to be shorter, it will still work as innerHTML will return the + // trailing characters unchanged. Since the entity characters do not include + // open angle bracket, there is no chance of XSS from the innerHTML use. + // Since no whitespace is passed to innerHTML, whitespace is preserved. + return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) { + // Check for cached entity. + var value = seen[s]; + if (value) { + return value; + } + // Check for numeric entity. + if (entity.charAt(0) == '#') { + // Prefix with 0 so that hex entities (e.g. ) parse as hex numbers. + var n = Number('0' + entity.substr(1)); + if (!isNaN(n)) { + value = String.fromCharCode(n); + } + } + // Fall back to innerHTML otherwise. + if (!value) { + // Append a non-entity character to avoid a bug in Webkit that parses + // an invalid entity at the end of innerHTML text as the empty string. + div.innerHTML = s + ' '; + // Then remove the trailing character from the result. + value = div.firstChild.nodeValue.slice(0, -1); + } + // Cache and return. + return seen[s] = value; + }); +}; + + +/** + * Unescapes XML entities. + * @private + * @param {string} str The string to unescape. + * @return {string} An unescaped copy of {@code str}. + */ +goog.string.unescapePureXmlEntities_ = function(str) { + return str.replace(/&([^;]+);/g, function(s, entity) { + switch (entity) { + case 'amp': + return '&'; + case 'lt': + return '<'; + case 'gt': + return '>'; + case 'quot': + return '"'; + default: + if (entity.charAt(0) == '#') { + // Prefix with 0 so that hex entities (e.g. ) parse as hex. + var n = Number('0' + entity.substr(1)); + if (!isNaN(n)) { + return String.fromCharCode(n); + } + } + // For invalid entities we just return the entity + return s; + } + }); +}; + + +/** + * Regular expression that matches an HTML entity. + * See also HTML5: Tokenization / Tokenizing character references. + * @private + * @type {!RegExp} + */ +goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g; + + +/** + * Do escaping of whitespace to preserve spatial formatting. We use character + * entity #160 to make it safer for xml. + * @param {string} str The string in which to escape whitespace. + * @param {boolean=} opt_xml Whether to use XML compatible tags. + * @return {string} An escaped copy of {@code str}. + */ +goog.string.whitespaceEscape = function(str, opt_xml) { + // This doesn't use goog.string.preserveSpaces for backwards compatibility. + return goog.string.newLineToBr(str.replace(/ /g, '  '), opt_xml); +}; + + +/** + * Preserve spaces that would be otherwise collapsed in HTML by replacing them + * with non-breaking space Unicode characters. + * @param {string} str The string in which to preserve whitespace. + * @return {string} A copy of {@code str} with preserved whitespace. + */ +goog.string.preserveSpaces = function(str) { + return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP); +}; + + +/** + * Strip quote characters around a string. The second argument is a string of + * characters to treat as quotes. This can be a single character or a string of + * multiple character and in that case each of those are treated as possible + * quote characters. For example: + * + * <pre> + * goog.string.stripQuotes('"abc"', '"`') --> 'abc' + * goog.string.stripQuotes('`abc`', '"`') --> 'abc' + * </pre> + * + * @param {string} str The string to strip. + * @param {string} quoteChars The quote characters to strip. + * @return {string} A copy of {@code str} without the quotes. + */ +goog.string.stripQuotes = function(str, quoteChars) { + var length = quoteChars.length; + for (var i = 0; i < length; i++) { + var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i); + if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) { + return str.substring(1, str.length - 1); + } + } + return str; +}; + + +/** + * Truncates a string to a certain length and adds '...' if necessary. The + * length also accounts for the ellipsis, so a maximum length of 10 and a string + * 'Hello World!' produces 'Hello W...'. + * @param {string} str The string to truncate. + * @param {number} chars Max number of characters. + * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped + * characters from being cut off in the middle. + * @return {string} The truncated {@code str} string. + */ +goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) { + if (opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str); + } + + if (str.length > chars) { + str = str.substring(0, chars - 3) + '...'; + } + + if (opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str); + } + + return str; +}; + + +/** + * Truncate a string in the middle, adding "..." if necessary, + * and favoring the beginning of the string. + * @param {string} str The string to truncate the middle of. + * @param {number} chars Max number of characters. + * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped + * characters from being cutoff in the middle. + * @param {number=} opt_trailingChars Optional number of trailing characters to + * leave at the end of the string, instead of truncating as close to the + * middle as possible. + * @return {string} A truncated copy of {@code str}. + */ +goog.string.truncateMiddle = function( + str, chars, opt_protectEscapedCharacters, opt_trailingChars) { + if (opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str); + } + + if (opt_trailingChars && str.length > chars) { + if (opt_trailingChars > chars) { + opt_trailingChars = chars; + } + var endPoint = str.length - opt_trailingChars; + var startPoint = chars - opt_trailingChars; + str = str.substring(0, startPoint) + '...' + str.substring(endPoint); + } else if (str.length > chars) { + // Favor the beginning of the string: + var half = Math.floor(chars / 2); + var endPos = str.length - half; + half += chars % 2; + str = str.substring(0, half) + '...' + str.substring(endPos); + } + + if (opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str); + } + + return str; +}; + + +/** + * Special chars that need to be escaped for goog.string.quote. + * @private {!Object<string, string>} + */ +goog.string.specialEscapeChars_ = { + '\0': '\\0', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + '\x0B': '\\x0B', // '\v' is not supported in JScript + '"': '\\"', + '\\': '\\\\', + // To support the use case of embedding quoted strings inside of script + // tags, we have to make sure HTML comments and opening/closing script tags do + // not appear in the resulting string. The specific strings that must be + // escaped are documented at: + // http://www.w3.org/TR/html51/semantics.html#restrictions-for-contents-of-script-elements + '<': '\x3c' +}; + + +/** + * Character mappings used internally for goog.string.escapeChar. + * @private {!Object<string, string>} + */ +goog.string.jsEscapeCache_ = { + '\'': '\\\'' +}; + + +/** + * Encloses a string in double quotes and escapes characters so that the + * string is a valid JS string. The resulting string is safe to embed in + * `<script>` tags as "<" is escaped. + * @param {string} s The string to quote. + * @return {string} A copy of {@code s} surrounded by double quotes. + */ +goog.string.quote = function(s) { + s = String(s); + var sb = ['"']; + for (var i = 0; i < s.length; i++) { + var ch = s.charAt(i); + var cc = ch.charCodeAt(0); + sb[i + 1] = goog.string.specialEscapeChars_[ch] || + ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch)); + } + sb.push('"'); + return sb.join(''); +}; + + +/** + * Takes a string and returns the escaped string for that input string. + * @param {string} str The string to escape. + * @return {string} An escaped string representing {@code str}. + */ +goog.string.escapeString = function(str) { + var sb = []; + for (var i = 0; i < str.length; i++) { + sb[i] = goog.string.escapeChar(str.charAt(i)); + } + return sb.join(''); +}; + + +/** + * Takes a character and returns the escaped string for that character. For + * example escapeChar(String.fromCharCode(15)) -> "\\x0E". + * @param {string} c The character to escape. + * @return {string} An escaped string representing {@code c}. + */ +goog.string.escapeChar = function(c) { + if (c in goog.string.jsEscapeCache_) { + return goog.string.jsEscapeCache_[c]; + } + + if (c in goog.string.specialEscapeChars_) { + return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c]; + } + + var rv = c; + var cc = c.charCodeAt(0); + if (cc > 31 && cc < 127) { + rv = c; + } else { + // tab is 9 but handled above + if (cc < 256) { + rv = '\\x'; + if (cc < 16 || cc > 256) { + rv += '0'; + } + } else { + rv = '\\u'; + if (cc < 4096) { // \u1000 + rv += '0'; + } + } + rv += cc.toString(16).toUpperCase(); + } + + return goog.string.jsEscapeCache_[c] = rv; +}; + + +/** + * Determines whether a string contains a substring. + * @param {string} str The string to search. + * @param {string} subString The substring to search for. + * @return {boolean} Whether {@code str} contains {@code subString}. + */ +goog.string.contains = function(str, subString) { + return str.indexOf(subString) != -1; +}; + + +/** + * Determines whether a string contains a substring, ignoring case. + * @param {string} str The string to search. + * @param {string} subString The substring to search for. + * @return {boolean} Whether {@code str} contains {@code subString}. + */ +goog.string.caseInsensitiveContains = function(str, subString) { + return goog.string.contains(str.toLowerCase(), subString.toLowerCase()); +}; + + +/** + * Returns the non-overlapping occurrences of ss in s. + * If either s or ss evalutes to false, then returns zero. + * @param {string} s The string to look in. + * @param {string} ss The string to look for. + * @return {number} Number of occurrences of ss in s. + */ +goog.string.countOf = function(s, ss) { + return s && ss ? s.split(ss).length - 1 : 0; +}; + + +/** + * Removes a substring of a specified length at a specific + * index in a string. + * @param {string} s The base string from which to remove. + * @param {number} index The index at which to remove the substring. + * @param {number} stringLength The length of the substring to remove. + * @return {string} A copy of {@code s} with the substring removed or the full + * string if nothing is removed or the input is invalid. + */ +goog.string.removeAt = function(s, index, stringLength) { + var resultStr = s; + // If the index is greater or equal to 0 then remove substring + if (index >= 0 && index < s.length && stringLength > 0) { + resultStr = s.substr(0, index) + + s.substr(index + stringLength, s.length - index - stringLength); + } + return resultStr; +}; + + +/** + * Removes the first occurrence of a substring from a string. + * @param {string} str The base string from which to remove. + * @param {string} substr The string to remove. + * @return {string} A copy of {@code str} with {@code substr} removed or the + * full string if nothing is removed. + */ +goog.string.remove = function(str, substr) { + return str.replace(substr, ''); +}; + + +/** + * Removes all occurrences of a substring from a string. + * @param {string} s The base string from which to remove. + * @param {string} ss The string to remove. + * @return {string} A copy of {@code s} with {@code ss} removed or the full + * string if nothing is removed. + */ +goog.string.removeAll = function(s, ss) { + var re = new RegExp(goog.string.regExpEscape(ss), 'g'); + return s.replace(re, ''); +}; + + +/** + * Replaces all occurrences of a substring of a string with a new substring. + * @param {string} s The base string from which to remove. + * @param {string} ss The string to replace. + * @param {string} replacement The replacement string. + * @return {string} A copy of {@code s} with {@code ss} replaced by + * {@code replacement} or the original string if nothing is replaced. + */ +goog.string.replaceAll = function(s, ss, replacement) { + var re = new RegExp(goog.string.regExpEscape(ss), 'g'); + return s.replace(re, replacement.replace(/\$/g, '$$$$')); +}; + + +/** + * Escapes characters in the string that are not safe to use in a RegExp. + * @param {*} s The string to escape. If not a string, it will be casted + * to one. + * @return {string} A RegExp safe, escaped copy of {@code s}. + */ +goog.string.regExpEscape = function(s) { + return String(s) + .replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1') + .replace(/\x08/g, '\\x08'); +}; + + +/** + * Repeats a string n times. + * @param {string} string The string to repeat. + * @param {number} length The number of times to repeat. + * @return {string} A string containing {@code length} repetitions of + * {@code string}. + */ +goog.string.repeat = (String.prototype.repeat) ? function(string, length) { + // The native method is over 100 times faster than the alternative. + return string.repeat(length); +} : function(string, length) { + return new Array(length + 1).join(string); +}; + + +/** + * Pads number to given length and optionally rounds it to a given precision. + * For example: + * <pre>padNumber(1.25, 2, 3) -> '01.250' + * padNumber(1.25, 2) -> '01.25' + * padNumber(1.25, 2, 1) -> '01.3' + * padNumber(1.25, 0) -> '1.25'</pre> + * + * @param {number} num The number to pad. + * @param {number} length The desired length. + * @param {number=} opt_precision The desired precision. + * @return {string} {@code num} as a string with the given options. + */ +goog.string.padNumber = function(num, length, opt_precision) { + var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num); + var index = s.indexOf('.'); + if (index == -1) { + index = s.length; + } + return goog.string.repeat('0', Math.max(0, length - index)) + s; +}; + + +/** + * Returns a string representation of the given object, with + * null and undefined being returned as the empty string. + * + * @param {*} obj The object to convert. + * @return {string} A string representation of the {@code obj}. + */ +goog.string.makeSafe = function(obj) { + return obj == null ? '' : String(obj); +}; + + +/** + * Concatenates string expressions. This is useful + * since some browsers are very inefficient when it comes to using plus to + * concat strings. Be careful when using null and undefined here since + * these will not be included in the result. If you need to represent these + * be sure to cast the argument to a String first. + * For example: + * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd' + * buildString(null, undefined) -> '' + * </pre> + * @param {...*} var_args A list of strings to concatenate. If not a string, + * it will be casted to one. + * @return {string} The concatenation of {@code var_args}. + */ +goog.string.buildString = function(var_args) { + return Array.prototype.join.call(arguments, ''); +}; + + +/** + * Returns a string with at least 64-bits of randomness. + * + * Doesn't trust Javascript's random function entirely. Uses a combination of + * random and current timestamp, and then encodes the string in base-36 to + * make it shorter. + * + * @return {string} A random string, e.g. sn1s7vb4gcic. + */ +goog.string.getRandomString = function() { + var x = 2147483648; + return Math.floor(Math.random() * x).toString(36) + + Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36); +}; + + +/** + * Compares two version numbers. + * + * @param {string|number} version1 Version of first item. + * @param {string|number} version2 Version of second item. + * + * @return {number} 1 if {@code version1} is higher. + * 0 if arguments are equal. + * -1 if {@code version2} is higher. + */ +goog.string.compareVersions = function(version1, version2) { + var order = 0; + // Trim leading and trailing whitespace and split the versions into + // subversions. + var v1Subs = goog.string.trim(String(version1)).split('.'); + var v2Subs = goog.string.trim(String(version2)).split('.'); + var subCount = Math.max(v1Subs.length, v2Subs.length); + + // Iterate over the subversions, as long as they appear to be equivalent. + for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) { + var v1Sub = v1Subs[subIdx] || ''; + var v2Sub = v2Subs[subIdx] || ''; + + do { + // Split the subversions into pairs of numbers and qualifiers (like 'b'). + // Two different RegExp objects are use to make it clear the code + // is side-effect free + var v1Comp = /(\d*)(\D*)(.*)/.exec(v1Sub) || ['', '', '', '']; + var v2Comp = /(\d*)(\D*)(.*)/.exec(v2Sub) || ['', '', '', '']; + // Break if there are no more matches. + if (v1Comp[0].length == 0 && v2Comp[0].length == 0) { + break; + } + + // Parse the numeric part of the subversion. A missing number is + // equivalent to 0. + var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10); + var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10); + + // Compare the subversion components. The number has the highest + // precedence. Next, if the numbers are equal, a subversion without any + // qualifier is always higher than a subversion with any qualifier. Next, + // the qualifiers are compared as strings. + order = goog.string.compareElements_(v1CompNum, v2CompNum) || + goog.string.compareElements_( + v1Comp[2].length == 0, v2Comp[2].length == 0) || + goog.string.compareElements_(v1Comp[2], v2Comp[2]); + // Stop as soon as an inequality is discovered. + + v1Sub = v1Comp[3]; + v2Sub = v2Comp[3]; + } while (order == 0); + } + + return order; +}; + + +/** + * Compares elements of a version number. + * + * @param {string|number|boolean} left An element from a version number. + * @param {string|number|boolean} right An element from a version number. + * + * @return {number} 1 if {@code left} is higher. + * 0 if arguments are equal. + * -1 if {@code right} is higher. + * @private + */ +goog.string.compareElements_ = function(left, right) { + if (left < right) { + return -1; + } else if (left > right) { + return 1; + } + return 0; +}; + + +/** + * String hash function similar to java.lang.String.hashCode(). + * The hash code for a string is computed as + * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], + * where s[i] is the ith character of the string and n is the length of + * the string. We mod the result to make it between 0 (inclusive) and 2^32 + * (exclusive). + * @param {string} str A string. + * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32 + * (exclusive). The empty string returns 0. + */ +goog.string.hashCode = function(str) { + var result = 0; + for (var i = 0; i < str.length; ++i) { + // Normalize to 4 byte range, 0 ... 2^32. + result = (31 * result + str.charCodeAt(i)) >>> 0; + } + return result; +}; + + +/** + * The most recent unique ID. |0 is equivalent to Math.floor in this case. + * @type {number} + * @private + */ +goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0; + + +/** + * Generates and returns a string which is unique in the current document. + * This is useful, for example, to create unique IDs for DOM elements. + * @return {string} A unique id. + */ +goog.string.createUniqueString = function() { + return 'goog_' + goog.string.uniqueStringCounter_++; +}; + + +/** + * Converts the supplied string to a number, which may be Infinity or NaN. + * This function strips whitespace: (toNumber(' 123') === 123) + * This function accepts scientific notation: (toNumber('1e1') === 10) + * + * This is better than Javascript's built-in conversions because, sadly: + * (Number(' ') === 0) and (parseFloat('123a') === 123) + * + * @param {string} str The string to convert. + * @return {number} The number the supplied string represents, or NaN. + */ +goog.string.toNumber = function(str) { + var num = Number(str); + if (num == 0 && goog.string.isEmptyOrWhitespace(str)) { + return NaN; + } + return num; +}; + + +/** + * Returns whether the given string is lower camel case (e.g. "isFooBar"). + * + * Note that this assumes the string is entirely letters. + * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms + * + * @param {string} str String to test. + * @return {boolean} Whether the string is lower camel case. + */ +goog.string.isLowerCamelCase = function(str) { + return /^[a-z]+([A-Z][a-z]*)*$/.test(str); +}; + + +/** + * Returns whether the given string is upper camel case (e.g. "FooBarBaz"). + * + * Note that this assumes the string is entirely letters. + * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms + * + * @param {string} str String to test. + * @return {boolean} Whether the string is upper camel case. + */ +goog.string.isUpperCamelCase = function(str) { + return /^([A-Z][a-z]*)+$/.test(str); +}; + + +/** + * Converts a string from selector-case to camelCase (e.g. from + * "multi-part-string" to "multiPartString"), useful for converting + * CSS selectors and HTML dataset keys to their equivalent JS properties. + * @param {string} str The string in selector-case form. + * @return {string} The string in camelCase form. + */ +goog.string.toCamelCase = function(str) { + return String(str).replace( + /\-([a-z])/g, function(all, match) { return match.toUpperCase(); }); +}; + + +/** + * Converts a string from camelCase to selector-case (e.g. from + * "multiPartString" to "multi-part-string"), useful for converting JS + * style and dataset properties to equivalent CSS selectors and HTML keys. + * @param {string} str The string in camelCase form. + * @return {string} The string in selector-case form. + */ +goog.string.toSelectorCase = function(str) { + return String(str).replace(/([A-Z])/g, '-$1').toLowerCase(); +}; + + +/** + * Converts a string into TitleCase. First character of the string is always + * capitalized in addition to the first letter of every subsequent word. + * Words are delimited by one or more whitespaces by default. Custom delimiters + * can optionally be specified to replace the default, which doesn't preserve + * whitespace delimiters and instead must be explicitly included if needed. + * + * Default delimiter => " ": + * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree' + * goog.string.toTitleCase('one two three') => 'One Two Three' + * goog.string.toTitleCase(' one two ') => ' One Two ' + * goog.string.toTitleCase('one_two_three') => 'One_two_three' + * goog.string.toTitleCase('one-two-three') => 'One-two-three' + * + * Custom delimiter => "_-.": + * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree' + * goog.string.toTitleCase('one two three', '_-.') => 'One two three' + * goog.string.toTitleCase(' one two ', '_-.') => ' one two ' + * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three' + * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three' + * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three' + * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three' + * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three' + * + * @param {string} str String value in camelCase form. + * @param {string=} opt_delimiters Custom delimiter character set used to + * distinguish words in the string value. Each character represents a + * single delimiter. When provided, default whitespace delimiter is + * overridden and must be explicitly included if needed. + * @return {string} String value in TitleCase form. + */ +goog.string.toTitleCase = function(str, opt_delimiters) { + var delimiters = goog.isString(opt_delimiters) ? + goog.string.regExpEscape(opt_delimiters) : + '\\s'; + + // For IE8, we need to prevent using an empty character set. Otherwise, + // incorrect matching will occur. + delimiters = delimiters ? '|[' + delimiters + ']+' : ''; + + var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g'); + return str.replace( + regexp, function(all, p1, p2) { return p1 + p2.toUpperCase(); }); +}; + + +/** + * Capitalizes a string, i.e. converts the first letter to uppercase + * and all other letters to lowercase, e.g.: + * + * goog.string.capitalize('one') => 'One' + * goog.string.capitalize('ONE') => 'One' + * goog.string.capitalize('one two') => 'One two' + * + * Note that this function does not trim initial whitespace. + * + * @param {string} str String value to capitalize. + * @return {string} String value with first letter in uppercase. + */ +goog.string.capitalize = function(str) { + return String(str.charAt(0)).toUpperCase() + + String(str.substr(1)).toLowerCase(); +}; + + +/** + * Parse a string in decimal or hexidecimal ('0xFFFF') form. + * + * To parse a particular radix, please use parseInt(string, radix) directly. See + * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt + * + * This is a wrapper for the built-in parseInt function that will only parse + * numbers as base 10 or base 16. Some JS implementations assume strings + * starting with "0" are intended to be octal. ES3 allowed but discouraged + * this behavior. ES5 forbids it. This function emulates the ES5 behavior. + * + * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj + * + * @param {string|number|null|undefined} value The value to be parsed. + * @return {number} The number, parsed. If the string failed to parse, this + * will be NaN. + */ +goog.string.parseInt = function(value) { + // Force finite numbers to strings. + if (isFinite(value)) { + value = String(value); + } + + if (goog.isString(value)) { + // If the string starts with '0x' or '-0x', parse as hex. + return /^\s*-?0x/i.test(value) ? parseInt(value, 16) : parseInt(value, 10); + } + + return NaN; +}; + + +/** + * Splits a string on a separator a limited number of times. + * + * This implementation is more similar to Python or Java, where the limit + * parameter specifies the maximum number of splits rather than truncating + * the number of results. + * + * See http://docs.python.org/2/library/stdtypes.html#str.split + * See JavaDoc: http://goo.gl/F2AsY + * See Mozilla reference: http://goo.gl/dZdZs + * + * @param {string} str String to split. + * @param {string} separator The separator. + * @param {number} limit The limit to the number of splits. The resulting array + * will have a maximum length of limit+1. Negative numbers are the same + * as zero. + * @return {!Array<string>} The string, split. + */ +goog.string.splitLimit = function(str, separator, limit) { + var parts = str.split(separator); + var returnVal = []; + + // Only continue doing this while we haven't hit the limit and we have + // parts left. + while (limit > 0 && parts.length) { + returnVal.push(parts.shift()); + limit--; + } + + // If there are remaining parts, append them to the end. + if (parts.length) { + returnVal.push(parts.join(separator)); + } + + return returnVal; +}; + + +/** + * Finds the characters to the right of the last instance of any separator + * + * This function is similar to goog.string.path.baseName, except it can take a + * list of characters to split the string on. It will return the rightmost + * grouping of characters to the right of any separator as a left-to-right + * oriented string. + * + * @see goog.string.path.baseName + * @param {string} str The string + * @param {string|!Array<string>} separators A list of separator characters + * @return {string} The last part of the string with respect to the separators + */ +goog.string.lastComponent = function(str, separators) { + if (!separators) { + return str; + } else if (typeof separators == 'string') { + separators = [separators]; + } + + var lastSeparatorIndex = -1; + for (var i = 0; i < separators.length; i++) { + if (separators[i] == '') { + continue; + } + var currentSeparatorIndex = str.lastIndexOf(separators[i]); + if (currentSeparatorIndex > lastSeparatorIndex) { + lastSeparatorIndex = currentSeparatorIndex; + } + } + if (lastSeparatorIndex == -1) { + return str; + } + return str.slice(lastSeparatorIndex + 1); +}; + + +/** + * Computes the Levenshtein edit distance between two strings. + * @param {string} a + * @param {string} b + * @return {number} The edit distance between the two strings. + */ +goog.string.editDistance = function(a, b) { + var v0 = []; + var v1 = []; + + if (a == b) { + return 0; + } + + if (!a.length || !b.length) { + return Math.max(a.length, b.length); + } + + for (var i = 0; i < b.length + 1; i++) { + v0[i] = i; + } + + for (var i = 0; i < a.length; i++) { + v1[0] = i + 1; + + for (var j = 0; j < b.length; j++) { + var cost = Number(a[i] != b[j]); + // Cost for the substring is the minimum of adding one character, removing + // one character, or a swap. + v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost); + } + + for (var j = 0; j < v0.length; j++) { + v0[j] = v1[j]; + } + } + + return v1[b.length]; +};
diff --git a/third_party/ink/closure/string/typedstring.js b/third_party/ink/closure/string/typedstring.js new file mode 100644 index 0000000..d0d7bd9 --- /dev/null +++ b/third_party/ink/closure/string/typedstring.js
@@ -0,0 +1,48 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.string.TypedString'); + + + +/** + * Wrapper for strings that conform to a data type or language. + * + * Implementations of this interface are wrappers for strings, and typically + * associate a type contract with the wrapped string. Concrete implementations + * of this interface may choose to implement additional run-time type checking, + * see for example {@code goog.html.SafeHtml}. If available, client code that + * needs to ensure type membership of an object should use the type's function + * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}. + * @interface + */ +goog.string.TypedString = function() {}; + + +/** + * Interface marker of the TypedString interface. + * + * This property can be used to determine at runtime whether or not an object + * implements this interface. All implementations of this interface set this + * property to {@code true}. + * @type {boolean} + */ +goog.string.TypedString.prototype.implementsGoogStringTypedString; + + +/** + * Retrieves this wrapped string's value. + * @return {string} The wrapped string's value. + */ +goog.string.TypedString.prototype.getTypedStringValue;
diff --git a/third_party/ink/closure/structs/collection.js b/third_party/ink/closure/structs/collection.js new file mode 100644 index 0000000..267862c --- /dev/null +++ b/third_party/ink/closure/structs/collection.js
@@ -0,0 +1,55 @@ +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Defines the collection interface. + * + * @author nnaze@google.com (Nathan Naze) + */ + +goog.provide('goog.structs.Collection'); + + + +/** + * An interface for a collection of values. + * @interface + * @template T + */ +goog.structs.Collection = function() {}; + + +/** + * @param {T} value Value to add to the collection. + */ +goog.structs.Collection.prototype.add; + + +/** + * @param {T} value Value to remove from the collection. + */ +goog.structs.Collection.prototype.remove; + + +/** + * @param {T} value Value to find in the collection. + * @return {boolean} Whether the collection contains the specified value. + */ +goog.structs.Collection.prototype.contains; + + +/** + * @return {number} The number of values stored in the collection. + */ +goog.structs.Collection.prototype.getCount;
diff --git a/third_party/ink/closure/structs/inversionmap.js b/third_party/ink/closure/structs/inversionmap.js new file mode 100644 index 0000000..009c02aa --- /dev/null +++ b/third_party/ink/closure/structs/inversionmap.js
@@ -0,0 +1,158 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides inversion and inversion map functionality for storing + * integer ranges and corresponding values. + * + * @author cibu@google.com (Cibu Johny) + * @author markdavis@google.com (Mark Davis) + */ + +goog.provide('goog.structs.InversionMap'); + +goog.require('goog.array'); +goog.require('goog.asserts'); + + + +/** + * Maps ranges to values. + * @param {Array<number>} rangeArray An array of monotonically + * increasing integer values, with at least one instance. + * @param {Array<T>} valueArray An array of corresponding values. + * Length must be the same as rangeArray. + * @param {boolean=} opt_delta If true, saves only delta from previous value. + * @constructor + * @template T + */ +goog.structs.InversionMap = function(rangeArray, valueArray, opt_delta) { + /** + * @protected {Array<number>} + */ + this.rangeArray = null; + + goog.asserts.assert( + rangeArray.length == valueArray.length, + 'rangeArray and valueArray must have the same length.'); + this.storeInversion_(rangeArray, opt_delta); + + /** @protected {Array<T>} */ + this.values = valueArray; +}; + + +/** + * Stores the integers as ranges (half-open). + * If delta is true, the integers are delta from the previous value and + * will be restored to the absolute value. + * When used as a set, even indices are IN, and odd are OUT. + * @param {Array<number>} rangeArray An array of monotonically + * increasing integer values, with at least one instance. + * @param {boolean=} opt_delta If true, saves only delta from previous value. + * @private + */ +goog.structs.InversionMap.prototype.storeInversion_ = function( + rangeArray, opt_delta) { + this.rangeArray = rangeArray; + + for (var i = 1; i < rangeArray.length; i++) { + if (rangeArray[i] == null) { + rangeArray[i] = rangeArray[i - 1] + 1; + } else if (opt_delta) { + rangeArray[i] += rangeArray[i - 1]; + } + } +}; + + +/** + * Splices a range -> value map into this inversion map. + * @param {Array<number>} rangeArray An array of monotonically + * increasing integer values, with at least one instance. + * @param {Array<T>} valueArray An array of corresponding values. + * Length must be the same as rangeArray. + * @param {boolean=} opt_delta If true, saves only delta from previous value. + */ +goog.structs.InversionMap.prototype.spliceInversion = function( + rangeArray, valueArray, opt_delta) { + // By building another inversion map, we build the arrays that we need + // to splice in. + var otherMap = + new goog.structs.InversionMap(rangeArray, valueArray, opt_delta); + + // Figure out where to splice those arrays. + var startRange = otherMap.rangeArray[0]; + var endRange = + /** @type {number} */ (goog.array.peek(otherMap.rangeArray)); + var startSplice = this.getLeast(startRange); + var endSplice = this.getLeast(endRange); + + // The inversion map works by storing the start points of ranges... + if (startRange != this.rangeArray[startSplice]) { + // ...if we're splicing in a start point that isn't already here, + // then we need to insert it after the insertion point. + startSplice++; + } // otherwise we overwrite the insertion point. + + this.rangeArray = this.rangeArray.slice(0, startSplice) + .concat(otherMap.rangeArray) + .concat(this.rangeArray.slice(endSplice + 1)); + this.values = this.values.slice(0, startSplice) + .concat(otherMap.values) + .concat(this.values.slice(endSplice + 1)); +}; + + +/** + * Gets the value corresponding to a number from the inversion map. + * @param {number} intKey The number for which value needs to be retrieved + * from inversion map. + * @return {T|null} Value retrieved from inversion map; null if not found. + */ +goog.structs.InversionMap.prototype.at = function(intKey) { + var index = this.getLeast(intKey); + if (index < 0) { + return null; + } + return this.values[index]; +}; + + +/** + * Gets the largest index such that rangeArray[index] <= intKey from the + * inversion map. + * @param {number} intKey The probe for which rangeArray is searched. + * @return {number} Largest index such that rangeArray[index] <= intKey. + * @protected + */ +goog.structs.InversionMap.prototype.getLeast = function(intKey) { + var arr = this.rangeArray; + var low = 0; + var high = arr.length; + while (high - low > 8) { + var mid = (high + low) >> 1; + if (arr[mid] <= intKey) { + low = mid; + } else { + high = mid; + } + } + for (; low < high; ++low) { + if (intKey < arr[low]) { + break; + } + } + return low - 1; +};
diff --git a/third_party/ink/closure/structs/map.js b/third_party/ink/closure/structs/map.js new file mode 100644 index 0000000..ccc8184 --- /dev/null +++ b/third_party/ink/closure/structs/map.js
@@ -0,0 +1,485 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: Hash Map. + * + * @author arv@google.com (Erik Arvidsson) + * @author jonp@google.com (Jon Perlow) Optimized for IE6 + * + * This file contains an implementation of a Map structure. It implements a lot + * of the methods used in goog.structs so those functions work on hashes. This + * is best suited for complex key types. For simple keys such as numbers and + * strings consider using the lighter-weight utilities in goog.object. + * MOE:begin_intracomment_strip + * + * NOTE(flan): Internally, key types are NOT actually cast to + * strings. Some people actually rely on this behavior even though it + * is incorrect. For more information, see http://b/5622311. + * + * NOTE(flan): Erik Corry (erikcorry) from the V8 team went over this + * class with me to help look for simplifications and + * optimizations. In the end, he didn't come up with very much. Erik + * explained that "for (k in o)" is not optimized in Crankshaft + * because it needs to look up properties in the whole prototype + * chain. It also needs to return the keys in order. Thus keeping an + * array of keys is actually much more efficient. + * + * Likewise, one option to iterate safely with "for (k in o)" is to + * prefix the keys with some character, like ':'. This can create a + * lot of strings that didn't exist before. In Closure Labs, + * goog.labs.structs.Map uses extra arrays to store non-safe keys and + * values. + * + * Thus, there are not a lot of reasonable simplifications that can be + * done here without impacting performance. + * + * TODO(chrishenry): Create some performance benchmarks for common + * operations. + * MOE:end_intracomment_strip + */ + + +goog.provide('goog.structs.Map'); + +goog.require('goog.iter.Iterator'); +goog.require('goog.iter.StopIteration'); +goog.require('goog.object'); + + + +/** + * Class for Hash Map datastructure. + * @param {*=} opt_map Map or Object to initialize the map with. + * @param {...*} var_args If 2 or more arguments are present then they + * will be used as key-value pairs. + * @constructor + * @template K, V + * @deprecated This type is misleading: use ES6 Map instead. + */ +goog.structs.Map = function(opt_map, var_args) { + + /** + * Underlying JS object used to implement the map. + * @private {!Object} + */ + this.map_ = {}; + + /** + * An array of keys. This is necessary for two reasons: + * 1. Iterating the keys using for (var key in this.map_) allocates an + * object for every key in IE which is really bad for IE6 GC perf. + * 2. Without a side data structure, we would need to escape all the keys + * as that would be the only way we could tell during iteration if the + * key was an internal key or a property of the object. + * + * This array can contain deleted keys so it's necessary to check the map + * as well to see if the key is still in the map (this doesn't require a + * memory allocation in IE). + * @private {!Array<string>} + */ + this.keys_ = []; + + /** + * The number of key value pairs in the map. + * @private {number} + */ + this.count_ = 0; + + /** + * Version used to detect changes while iterating. + * @private {number} + */ + this.version_ = 0; + + var argLength = arguments.length; + + if (argLength > 1) { + if (argLength % 2) { + throw new Error('Uneven number of arguments'); + } + for (var i = 0; i < argLength; i += 2) { + this.set(arguments[i], arguments[i + 1]); + } + } else if (opt_map) { + this.addAll(/** @type {Object} */ (opt_map)); + } +}; + + +/** + * @return {number} The number of key-value pairs in the map. + */ +goog.structs.Map.prototype.getCount = function() { + return this.count_; +}; + + +/** + * Returns the values of the map. + * @return {!Array<V>} The values in the map. + */ +goog.structs.Map.prototype.getValues = function() { + this.cleanupKeysArray_(); + + var rv = []; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + rv.push(this.map_[key]); + } + return rv; +}; + + +/** + * Returns the keys of the map. + * @return {!Array<string>} Array of string values. + */ +goog.structs.Map.prototype.getKeys = function() { + this.cleanupKeysArray_(); + return /** @type {!Array<string>} */ (this.keys_.concat()); +}; + + +/** + * Whether the map contains the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the map contains the key. + */ +goog.structs.Map.prototype.containsKey = function(key) { + return goog.structs.Map.hasKey_(this.map_, key); +}; + + +/** + * Whether the map contains the given value. This is O(n). + * @param {V} val The value to check for. + * @return {boolean} Whether the map contains the value. + */ +goog.structs.Map.prototype.containsValue = function(val) { + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) { + return true; + } + } + return false; +}; + + +/** + * Whether this map is equal to the argument map. + * @param {goog.structs.Map} otherMap The map against which to test equality. + * @param {function(V, V): boolean=} opt_equalityFn Optional equality function + * to test equality of values. If not specified, this will test whether + * the values contained in each map are identical objects. + * @return {boolean} Whether the maps are equal. + */ +goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) { + if (this === otherMap) { + return true; + } + + if (this.count_ != otherMap.getCount()) { + return false; + } + + var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals; + + this.cleanupKeysArray_(); + for (var key, i = 0; key = this.keys_[i]; i++) { + if (!equalityFn(this.get(key), otherMap.get(key))) { + return false; + } + } + + return true; +}; + + +/** + * Default equality test for values. + * @param {*} a The first value. + * @param {*} b The second value. + * @return {boolean} Whether a and b reference the same object. + */ +goog.structs.Map.defaultEquals = function(a, b) { + return a === b; +}; + + +/** + * @return {boolean} Whether the map is empty. + */ +goog.structs.Map.prototype.isEmpty = function() { + return this.count_ == 0; +}; + + +/** + * Removes all key-value pairs from the map. + */ +goog.structs.Map.prototype.clear = function() { + this.map_ = {}; + this.keys_.length = 0; + this.count_ = 0; + this.version_ = 0; +}; + + +/** + * Removes a key-value pair based on the key. This is O(logN) amortized due to + * updating the keys array whenever the count becomes half the size of the keys + * in the keys array. + * @param {*} key The key to remove. + * @return {boolean} Whether object was removed. + */ +goog.structs.Map.prototype.remove = function(key) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + delete this.map_[key]; + this.count_--; + this.version_++; + + // clean up the keys array if the threshold is hit + if (this.keys_.length > 2 * this.count_) { + this.cleanupKeysArray_(); + } + + return true; + } + return false; +}; + + +/** + * Cleans up the temp keys array by removing entries that are no longer in the + * map. + * @private + */ +goog.structs.Map.prototype.cleanupKeysArray_ = function() { + if (this.count_ != this.keys_.length) { + // First remove keys that are no longer in the map. + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (goog.structs.Map.hasKey_(this.map_, key)) { + this.keys_[destIndex++] = key; + } + srcIndex++; + } + this.keys_.length = destIndex; + } + + if (this.count_ != this.keys_.length) { + // If the count still isn't correct, that means we have duplicates. This can + // happen when the same key is added and removed multiple times. Now we have + // to allocate one extra Object to remove the duplicates. This could have + // been done in the first pass, but in the common case, we can avoid + // allocating an extra object by only doing this when necessary. + var seen = {}; + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (!(goog.structs.Map.hasKey_(seen, key))) { + this.keys_[destIndex++] = key; + seen[key] = 1; + } + srcIndex++; + } + this.keys_.length = destIndex; + } +}; + + +/** + * Returns the value for the given key. If the key is not found and the default + * value is not given this will return {@code undefined}. + * @param {*} key The key to get the value for. + * @param {DEFAULT=} opt_val The value to return if no item is found for the + * given key, defaults to undefined. + * @return {V|DEFAULT} The value for the given key. + * @template DEFAULT + */ +goog.structs.Map.prototype.get = function(key, opt_val) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + return this.map_[key]; + } + return opt_val; +}; + + +/** + * Adds a key-value pair to the map. + * @param {*} key The key. + * @param {V} value The value to add. + * @return {*} Some subclasses return a value. + */ +goog.structs.Map.prototype.set = function(key, value) { + if (!(goog.structs.Map.hasKey_(this.map_, key))) { + this.count_++; + // TODO(johnlenz): This class lies, it claims to return an array of string + // keys, but instead returns the original object used. + this.keys_.push(/** @type {?} */ (key)); + // Only change the version if we add a new key. + this.version_++; + } + this.map_[key] = value; +}; + + +/** + * Adds multiple key-value pairs from another goog.structs.Map or Object. + * @param {Object} map Object containing the data to add. + */ +goog.structs.Map.prototype.addAll = function(map) { + var keys, values; + if (map instanceof goog.structs.Map) { + keys = map.getKeys(); + values = map.getValues(); + } else { + keys = goog.object.getKeys(map); + values = goog.object.getValues(map); + } + // we could use goog.array.forEach here but I don't want to introduce that + // dependency just for this. + for (var i = 0; i < keys.length; i++) { + this.set(keys[i], values[i]); + } +}; + + +/** + * Calls the given function on each entry in the map. + * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f + * @param {T=} opt_obj The value of "this" inside f. + * @template T + */ +goog.structs.Map.prototype.forEach = function(f, opt_obj) { + var keys = this.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = this.get(key); + f.call(opt_obj, value, key, this); + } +}; + + +/** + * Clones a map and returns a new map. + * @return {!goog.structs.Map} A new map with the same key-value pairs. + */ +goog.structs.Map.prototype.clone = function() { + return new goog.structs.Map(this); +}; + + +/** + * Returns a new map in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. + * + * It acts very similarly to {goog.object.transpose(Object)}. + * + * @return {!goog.structs.Map} The transposed map. + */ +goog.structs.Map.prototype.transpose = function() { + var transposed = new goog.structs.Map(); + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + var value = this.map_[key]; + transposed.set(value, key); + } + + return transposed; +}; + + +/** + * @return {!Object} Object representation of the map. + */ +goog.structs.Map.prototype.toObject = function() { + this.cleanupKeysArray_(); + var obj = {}; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + obj[key] = this.map_[key]; + } + return obj; +}; + + +/** + * Returns an iterator that iterates over the keys in the map. Removal of keys + * while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the keys in the map. + */ +goog.structs.Map.prototype.getKeyIterator = function() { + return this.__iterator__(true); +}; + + +/** + * Returns an iterator that iterates over the values in the map. Removal of + * keys while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the values in the map. + */ +goog.structs.Map.prototype.getValueIterator = function() { + return this.__iterator__(false); +}; + + +/** + * Returns an iterator that iterates over the values or the keys in the map. + * This throws an exception if the map was mutated since the iterator was + * created. + * @param {boolean=} opt_keys True to iterate over the keys. False to iterate + * over the values. The default value is false. + * @return {!goog.iter.Iterator} An iterator over the values or keys in the map. + */ +goog.structs.Map.prototype.__iterator__ = function(opt_keys) { + // Clean up keys to minimize the risk of iterating over dead keys. + this.cleanupKeysArray_(); + + var i = 0; + var version = this.version_; + var selfObj = this; + + var newIter = new goog.iter.Iterator; + newIter.next = function() { + if (version != selfObj.version_) { + throw new Error('The map has changed since the iterator was created'); + } + if (i >= selfObj.keys_.length) { + throw goog.iter.StopIteration; + } + var key = selfObj.keys_[i++]; + return opt_keys ? key : selfObj.map_[key]; + }; + return newIter; +}; + + +/** + * Safe way to test for hasOwnProperty. It even allows testing for + * 'hasOwnProperty'. + * @param {Object} obj The object to test for presence of the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the object has the key. + * @private + */ +goog.structs.Map.hasKey_ = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +};
diff --git a/third_party/ink/closure/structs/set.js b/third_party/ink/closure/structs/set.js new file mode 100644 index 0000000..e8470ab --- /dev/null +++ b/third_party/ink/closure/structs/set.js
@@ -0,0 +1,280 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: Set. + * + * @author arv@google.com (Erik Arvidsson) + * @author pallosp@google.com (Peter Pallos) + * + * This class implements a set data structure. Adding and removing is O(1). It + * supports both object and primitive values. Be careful because you can add + * both 1 and new Number(1), because these are not the same. You can even add + * multiple new Number(1) because these are not equal. + */ + + +goog.provide('goog.structs.Set'); + +goog.require('goog.structs'); +goog.require('goog.structs.Collection'); +goog.require('goog.structs.Map'); + + + +/** + * A set that can contain both primitives and objects. Adding and removing + * elements is O(1). Primitives are treated as identical if they have the same + * type and convert to the same string. Objects are treated as identical only + * if they are references to the same object. WARNING: A goog.structs.Set can + * contain both 1 and (new Number(1)), because they are not the same. WARNING: + * Adding (new Number(1)) twice will yield two distinct elements, because they + * are two different objects. WARNING: Any object that is added to a + * goog.structs.Set will be modified! Because goog.getUid() is used to + * identify objects, every object in the set will be mutated. + * @param {Array<T>|Object<?,T>=} opt_values Initial values to start with. + * @constructor + * @implements {goog.structs.Collection<T>} + * @final + * @template T + * @deprecated This type is misleading: use ES6 Set instead. + */ +goog.structs.Set = function(opt_values) { + this.map_ = new goog.structs.Map; + if (opt_values) { + this.addAll(opt_values); + } +}; + + +/** + * Obtains a unique key for an element of the set. Primitives will yield the + * same key if they have the same type and convert to the same string. Object + * references will yield the same key only if they refer to the same object. + * @param {*} val Object or primitive value to get a key for. + * @return {string} A unique key for this value/object. + * @private + */ +goog.structs.Set.getKey_ = function(val) { + var type = typeof val; + if (type == 'object' && val || type == 'function') { + return 'o' + goog.getUid(/** @type {Object} */ (val)); + } else { + return type.substr(0, 1) + val; + } +}; + + +/** + * @return {number} The number of elements in the set. + * @override + */ +goog.structs.Set.prototype.getCount = function() { + return this.map_.getCount(); +}; + + +/** + * Add a primitive or an object to the set. + * @param {T} element The primitive or object to add. + * @override + */ +goog.structs.Set.prototype.add = function(element) { + this.map_.set(goog.structs.Set.getKey_(element), element); +}; + + +/** + * Adds all the values in the given collection to this set. + * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection + * containing the elements to add. + */ +goog.structs.Set.prototype.addAll = function(col) { + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + this.add(values[i]); + } +}; + + +/** + * Removes all values in the given collection from this set. + * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection + * containing the elements to remove. + */ +goog.structs.Set.prototype.removeAll = function(col) { + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + this.remove(values[i]); + } +}; + + +/** + * Removes the given element from this set. + * @param {T} element The primitive or object to remove. + * @return {boolean} Whether the element was found and removed. + * @override + */ +goog.structs.Set.prototype.remove = function(element) { + return this.map_.remove(goog.structs.Set.getKey_(element)); +}; + + +/** + * Removes all elements from this set. + */ +goog.structs.Set.prototype.clear = function() { + this.map_.clear(); +}; + + +/** + * Tests whether this set is empty. + * @return {boolean} True if there are no elements in this set. + */ +goog.structs.Set.prototype.isEmpty = function() { + return this.map_.isEmpty(); +}; + + +/** + * Tests whether this set contains the given element. + * @param {T} element The primitive or object to test for. + * @return {boolean} True if this set contains the given element. + * @override + */ +goog.structs.Set.prototype.contains = function(element) { + return this.map_.containsKey(goog.structs.Set.getKey_(element)); +}; + + +/** + * Tests whether this set contains all the values in a given collection. + * Repeated elements in the collection are ignored, e.g. (new + * goog.structs.Set([1, 2])).containsAll([1, 1]) is True. + * @param {goog.structs.Collection<T>|Object} col A collection-like object. + * @return {boolean} True if the set contains all elements. + */ +goog.structs.Set.prototype.containsAll = function(col) { + return goog.structs.every(col, this.contains, this); +}; + + +/** + * Finds all values that are present in both this set and the given collection. + * @param {Array<S>|Object<?,S>} col A collection. + * @return {!goog.structs.Set<T|S>} A new set containing all the values + * (primitives or objects) present in both this set and the given + * collection. + * @template S + */ +goog.structs.Set.prototype.intersection = function(col) { + var result = new goog.structs.Set(); + + var values = goog.structs.getValues(col); + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (this.contains(value)) { + result.add(value); + } + } + + return result; +}; + + +/** + * Finds all values that are present in this set and not in the given + * collection. + * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection. + * @return {!goog.structs.Set} A new set containing all the values + * (primitives or objects) present in this set but not in the given + * collection. + */ +goog.structs.Set.prototype.difference = function(col) { + var result = this.clone(); + result.removeAll(col); + return result; +}; + + +/** + * Returns an array containing all the elements in this set. + * @return {!Array<T>} An array containing all the elements in this set. + */ +goog.structs.Set.prototype.getValues = function() { + return this.map_.getValues(); +}; + + +/** + * Creates a shallow clone of this set. + * @return {!goog.structs.Set<T>} A new set containing all the same elements as + * this set. + */ +goog.structs.Set.prototype.clone = function() { + return new goog.structs.Set(this); +}; + + +/** + * Tests whether the given collection consists of the same elements as this set, + * regardless of order, without repetition. Primitives are treated as equal if + * they have the same type and convert to the same string; objects are treated + * as equal if they are references to the same object. This operation is O(n). + * @param {goog.structs.Collection<T>|Object} col A collection. + * @return {boolean} True if the given collection consists of the same elements + * as this set, regardless of order, without repetition. + */ +goog.structs.Set.prototype.equals = function(col) { + return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col); +}; + + +/** + * Tests whether the given collection contains all the elements in this set. + * Primitives are treated as equal if they have the same type and convert to the + * same string; objects are treated as equal if they are references to the same + * object. This operation is O(n). + * @param {goog.structs.Collection<T>|Object} col A collection. + * @return {boolean} True if this set is a subset of the given collection. + */ +goog.structs.Set.prototype.isSubsetOf = function(col) { + var colCount = goog.structs.getCount(col); + if (this.getCount() > colCount) { + return false; + } + // TODO(pallosp) Find the minimal collection size where the conversion makes + // the contains() method faster. + if (!(col instanceof goog.structs.Set) && colCount > 5) { + // Convert to a goog.structs.Set so that goog.structs.contains runs in + // O(1) time instead of O(n) time. + col = new goog.structs.Set(col); + } + return goog.structs.every( + this, function(value) { return goog.structs.contains(col, value); }); +}; + + +/** + * Returns an iterator that iterates over the elements in this set. + * @param {boolean=} opt_keys This argument is ignored. + * @return {!goog.iter.Iterator} An iterator over the elements in this set. + */ +goog.structs.Set.prototype.__iterator__ = function(opt_keys) { + return this.map_.__iterator__(false); +};
diff --git a/third_party/ink/closure/structs/structs.js b/third_party/ink/closure/structs/structs.js new file mode 100644 index 0000000..684ddfe4 --- /dev/null +++ b/third_party/ink/closure/structs/structs.js
@@ -0,0 +1,354 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generics method for collection-like classes and objects. + * + * @author arv@google.com (Erik Arvidsson) + * + * This file contains functions to work with collections. It supports using + * Map, Set, Array and Object and other classes that implement collection-like + * methods. + */ + + +goog.provide('goog.structs'); + +goog.require('goog.array'); +goog.require('goog.object'); + + +// We treat an object as a dictionary if it has getKeys or it is an object that +// isn't arrayLike. + + +/** + * Returns the number of values in the collection-like object. + * @param {Object} col The collection-like object. + * @return {number} The number of values in the collection-like object. + */ +goog.structs.getCount = function(col) { + if (col.getCount && typeof col.getCount == 'function') { + return col.getCount(); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return col.length; + } + return goog.object.getCount(col); +}; + + +/** + * Returns the values of the collection-like object. + * @param {Object} col The collection-like object. + * @return {!Array<?>} The values in the collection-like object. + */ +goog.structs.getValues = function(col) { + if (col.getValues && typeof col.getValues == 'function') { + return col.getValues(); + } + if (goog.isString(col)) { + return col.split(''); + } + if (goog.isArrayLike(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(col[i]); + } + return rv; + } + return goog.object.getValues(col); +}; + + +/** + * Returns the keys of the collection. Some collections have no notion of + * keys/indexes and this function will return undefined in those cases. + * @param {Object} col The collection-like object. + * @return {!Array|undefined} The keys in the collection. + */ +goog.structs.getKeys = function(col) { + if (col.getKeys && typeof col.getKeys == 'function') { + return col.getKeys(); + } + // if we have getValues but no getKeys we know this is a key-less collection + if (col.getValues && typeof col.getValues == 'function') { + return undefined; + } + if (goog.isArrayLike(col) || goog.isString(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(i); + } + return rv; + } + + return goog.object.getKeys(col); +}; + + +/** + * Whether the collection contains the given value. This is O(n) and uses + * equals (==) to test the existence. + * @param {Object} col The collection-like object. + * @param {*} val The value to check for. + * @return {boolean} True if the map contains the value. + */ +goog.structs.contains = function(col, val) { + if (col.contains && typeof col.contains == 'function') { + return col.contains(val); + } + if (col.containsValue && typeof col.containsValue == 'function') { + return col.containsValue(val); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.contains(/** @type {!Array<?>} */ (col), val); + } + return goog.object.containsValue(col, val); +}; + + +/** + * Whether the collection is empty. + * @param {Object} col The collection-like object. + * @return {boolean} True if empty. + */ +goog.structs.isEmpty = function(col) { + if (col.isEmpty && typeof col.isEmpty == 'function') { + return col.isEmpty(); + } + + // We do not use goog.string.isEmptyOrWhitespace because here we treat the + // string as + // collection and as such even whitespace matters + + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.isEmpty(/** @type {!Array<?>} */ (col)); + } + return goog.object.isEmpty(col); +}; + + +/** + * Removes all the elements from the collection. + * @param {Object} col The collection-like object. + */ +goog.structs.clear = function(col) { + // NOTE(arv): This should not contain strings because strings are immutable + if (col.clear && typeof col.clear == 'function') { + col.clear(); + } else if (goog.isArrayLike(col)) { + goog.array.clear(/** @type {IArrayLike<?>} */ (col)); + } else { + goog.object.clear(col); + } +}; + + +/** + * Calls a function for each value in a collection. The function takes + * three arguments; the value, the key and the collection. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):?} f The function to call for every value. + * This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and the return value is irrelevant. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @template T,S + * @deprecated Use a more specific method, e.g. goog.array.forEach, + * goog.object.forEach, or for-of. + */ +goog.structs.forEach = function(col, f, opt_obj) { + if (col.forEach && typeof col.forEach == 'function') { + col.forEach(f, opt_obj); + } else if (goog.isArrayLike(col) || goog.isString(col)) { + goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj); + } else { + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col); + } + } +}; + + +/** + * Calls a function for every value in the collection. When a call returns true, + * adds the value to a new collection (Array is returned by default). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and should return a Boolean. If the + * return value is true the value is added to the result collection. If it + * is false the value is not included. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object|!Array<?>} A new collection where the passed values are + * present. If col is a key-less collection an array is returned. If col + * has keys and values a plain old JS object is returned. + * @template T,S + */ +goog.structs.filter = function(col, f, opt_obj) { + if (typeof col.filter == 'function') { + return col.filter(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + if (f.call(/** @type {?} */ (opt_obj), values[i], keys[i], col)) { + rv[keys[i]] = values[i]; + } + } + } else { + // We should not use goog.array.filter here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], undefined, col)) { + rv.push(values[i]); + } + } + } + return rv; +}; + + +/** + * Calls a function for every value in the collection and adds the result into a + * new collection (defaults to creating a new Array). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):V} f The function to call for every value. + * This function takes 3 arguments (the value, the key or undefined if the + * collection has no notion of keys, and the collection) and should return + * something. The result will be used as the value in the new collection. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object<V>|!Array<V>} A new collection with the new values. If + * col is a key-less collection an array is returned. If col has keys and + * values a plain old JS object is returned. + * @template T,S,V + */ +goog.structs.map = function(col, f, opt_obj) { + if (typeof col.map == 'function') { + return col.map(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + rv[keys[i]] = f.call(/** @type {?} */ (opt_obj), values[i], keys[i], col); + } + } else { + // We should not use goog.array.map here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + rv[i] = f.call(/** @type {?} */ (opt_obj), values[i], undefined, col); + } + } + return rv; +}; + + +/** + * Calls f for each value in a collection. If any call returns true this returns + * true (without checking the rest). If all returns false this returns false. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or undefined + * if the collection has no notion of keys, and the collection) and should + * return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if any value passes the test. + * @template T,S + */ +goog.structs.some = function(col, f, opt_obj) { + if (typeof col.some == 'function') { + return col.some(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col)) { + return true; + } + } + return false; +}; + + +/** + * Calls f for each value in a collection. If all calls return true this return + * true this returns true. If any returns false this returns false at this point + * and does not continue to check the remaining values. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or + * undefined if the collection has no notion of keys, and the collection) + * and should return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if all key-value pairs pass the test. + * @template T,S + */ +goog.structs.every = function(col, f, opt_obj) { + if (typeof col.every == 'function') { + return col.every(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (!f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col)) { + return false; + } + } + return true; +};
diff --git a/third_party/ink/closure/style/style.js b/third_party/ink/closure/style/style.js new file mode 100644 index 0000000..ff44b099 --- /dev/null +++ b/third_party/ink/closure/style/style.js
@@ -0,0 +1,2046 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utilities for element styles. + * + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + * @author eae@google.com (Emil A Eklund) + * @author pallosp@google.com (Peter Pallos) + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html + * @see ../demos/style_viewport.html + */ + +goog.provide('goog.style'); + + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.vendor'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Rect'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.reflect'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + +goog.forwardDeclare('goog.events.Event'); + + +/** + * Sets a style value on an element. + * + * This function is not indended to patch issues in the browser's style + * handling, but to allow easy programmatic access to setting dash-separated + * style properties. An example is setting a batch of properties from a data + * object without overwriting old styles. When possible, use native APIs: + * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) + * elem.style.cssText = 'property1: value1; property2: value2'. + * + * @param {Element} element The element to change. + * @param {string|Object} style If a string, a style name. If an object, a hash + * of style names to style values. + * @param {string|number|boolean=} opt_value If style was a string, then this + * should be the value. + */ +goog.style.setStyle = function(element, style, opt_value) { + if (goog.isString(style)) { + goog.style.setStyle_(element, opt_value, style); + } else { + for (var key in style) { + goog.style.setStyle_(element, style[key], key); + } + } +}; + + +/** + * Sets a style value on an element, with parameters swapped to work with + * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when + * necessary. + * @param {Element} element The element to change. + * @param {string|number|boolean|undefined} value Style value. + * @param {string} style Style name. + * @private + */ +goog.style.setStyle_ = function(element, value, style) { + var propertyName = goog.style.getVendorJsStyleName_(element, style); + + if (propertyName) { + // TODO(johnlenz): coerce to string? + element.style[propertyName] = /** @type {?} */ (value); + } +}; + + +/** + * Style name cache that stores previous property name lookups. + * + * This is used by setStyle to speed up property lookups, entries look like: + * { StyleName: ActualPropertyName } + * + * @private {!Object<string, string>} + */ +goog.style.styleNameCache_ = {}; + + +/** + * Returns the style property name in camel-case. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private + */ +goog.style.getVendorJsStyleName_ = function(element, style) { + var propertyName = goog.style.styleNameCache_[style]; + if (!propertyName) { + var camelStyle = goog.string.toCamelCase(style); + propertyName = camelStyle; + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + propertyName = prefixedStyle; + } + } + goog.style.styleNameCache_[style] = propertyName; + } + + return propertyName; +}; + + +/** + * Returns the style property name in CSS notation. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private + */ +goog.style.getVendorStyleName_ = function(element, style) { + var camelStyle = goog.string.toCamelCase(style); + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + return goog.dom.vendor.getVendorPrefix() + '-' + style; + } + } + + return style; +}; + + +/** + * Retrieves an explicitly-set style value of a node. This returns '' if there + * isn't a style attribute on the element or if this style property has not been + * explicitly set in script. + * + * @param {Element} element Element to get style of. + * @param {string} property Property to get, css-style (if you have a camel-case + * property, use element.style[style]). + * @return {string} Style value. + */ +goog.style.getStyle = function(element, property) { + // element.style is '' for well-known properties which are unset. + // For for browser specific styles as 'filter' is undefined + // so we need to return '' explicitly to make it consistent across + // browsers. + var styleValue = element.style[goog.string.toCamelCase(property)]; + + // Using typeof here because of a bug in Safari 5.1, where this value + // was undefined, but === undefined returned false. + if (typeof(styleValue) !== 'undefined') { + return styleValue; + } + + return element.style[goog.style.getVendorJsStyleName_(element, property)] || + ''; +}; + + +/** + * Retrieves a computed style value of a node. It returns empty string if the + * value cannot be computed (which will be the case in Internet Explorer) or + * "none" if the property requested is an SVG one and it has not been + * explicitly set (firefox and webkit). + * + * @param {Element} element Element to get style of. + * @param {string} property Property to get (camel-case). + * @return {string} Style value. + */ +goog.style.getComputedStyle = function(element, property) { + var doc = goog.dom.getOwnerDocument(element); + if (doc.defaultView && doc.defaultView.getComputedStyle) { + var styles = doc.defaultView.getComputedStyle(element, null); + if (styles) { + // element.style[..] is undefined for browser specific styles + // as 'filter'. + return styles[property] || styles.getPropertyValue(property) || ''; + } + } + + return ''; +}; + + +/** + * Gets the cascaded style value of a node, or null if the value cannot be + * computed (only Internet Explorer can do this). + * + * @param {Element} element Element to get style of. + * @param {string} style Property to get (camel-case). + * @return {string} Style value. + */ +goog.style.getCascadedStyle = function(element, style) { + // TODO(nicksantos): This should be documented to return null. #fixTypes + return /** @type {string} */ ( + element.currentStyle ? element.currentStyle[style] : null); +}; + + +/** + * Cross-browser pseudo get computed style. It returns the computed style where + * available. If not available it tries the cascaded style value (IE + * currentStyle) and in worst case the inline style value. It shouldn't be + * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for + * discussion. + * + * @param {Element} element Element to get style of. + * @param {string} style Property to get (must be camelCase, not css-style.). + * @return {string} Style value. + * @private + */ +goog.style.getStyle_ = function(element, style) { + return goog.style.getComputedStyle(element, style) || + goog.style.getCascadedStyle(element, style) || + (element.style && element.style[style]); +}; + + +/** + * Retrieves the computed value of the box-sizing CSS attribute. + * Browser support: http://caniuse.com/css3-boxsizing. + * @param {!Element} element The element whose box-sizing to get. + * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if + * box-sizing is not supported (IE7 and below). + */ +goog.style.getComputedBoxSizing = function(element) { + return goog.style.getStyle_(element, 'boxSizing') || + goog.style.getStyle_(element, 'MozBoxSizing') || + goog.style.getStyle_(element, 'WebkitBoxSizing') || null; +}; + + +/** + * Retrieves the computed value of the position CSS attribute. + * @param {Element} element The element to get the position of. + * @return {string} Position value. + */ +goog.style.getComputedPosition = function(element) { + return goog.style.getStyle_(element, 'position'); +}; + + +/** + * Retrieves the computed background color string for a given element. The + * string returned is suitable for assigning to another element's + * background-color, but is not guaranteed to be in any particular string + * format. Accessing the color in a numeric form may not be possible in all + * browsers or with all input. + * + * If the background color for the element is defined as a hexadecimal value, + * the resulting string can be parsed by goog.color.parse in all supported + * browsers. + * + * Whether named colors like "red" or "lightblue" get translated into a + * format which can be parsed is browser dependent. Calling this function on + * transparent elements will return "transparent" in most browsers or + * "rgba(0, 0, 0, 0)" in WebKit. + * @param {Element} element The element to get the background color of. + * @return {string} The computed string value of the background color. + */ +goog.style.getBackgroundColor = function(element) { + return goog.style.getStyle_(element, 'backgroundColor'); +}; + + +/** + * Retrieves the computed value of the overflow-x CSS attribute. + * @param {Element} element The element to get the overflow-x of. + * @return {string} The computed string value of the overflow-x attribute. + */ +goog.style.getComputedOverflowX = function(element) { + return goog.style.getStyle_(element, 'overflowX'); +}; + + +/** + * Retrieves the computed value of the overflow-y CSS attribute. + * @param {Element} element The element to get the overflow-y of. + * @return {string} The computed string value of the overflow-y attribute. + */ +goog.style.getComputedOverflowY = function(element) { + return goog.style.getStyle_(element, 'overflowY'); +}; + + +/** + * Retrieves the computed value of the z-index CSS attribute. + * @param {Element} element The element to get the z-index of. + * @return {string|number} The computed value of the z-index attribute. + */ +goog.style.getComputedZIndex = function(element) { + return goog.style.getStyle_(element, 'zIndex'); +}; + + +/** + * Retrieves the computed value of the text-align CSS attribute. + * @param {Element} element The element to get the text-align of. + * @return {string} The computed string value of the text-align attribute. + */ +goog.style.getComputedTextAlign = function(element) { + return goog.style.getStyle_(element, 'textAlign'); +}; + + +/** + * Retrieves the computed value of the cursor CSS attribute. + * @param {Element} element The element to get the cursor of. + * @return {string} The computed string value of the cursor attribute. + */ +goog.style.getComputedCursor = function(element) { + return goog.style.getStyle_(element, 'cursor'); +}; + + +/** + * Retrieves the computed value of the CSS transform attribute. + * @param {Element} element The element to get the transform of. + * @return {string} The computed string representation of the transform matrix. + */ +goog.style.getComputedTransform = function(element) { + var property = goog.style.getVendorStyleName_(element, 'transform'); + return goog.style.getStyle_(element, property) || + goog.style.getStyle_(element, 'transform'); +}; + + +/** + * Sets the top/left values of an element. If no unit is specified in the + * argument then it will add px. The second argument is required if the first + * argument is a string or number and is ignored if the first argument + * is a coordinate. + * @param {Element} el Element to move. + * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. + * @param {string|number=} opt_arg2 Top position. + */ +goog.style.setPosition = function(el, arg1, opt_arg2) { + var x, y; + + if (arg1 instanceof goog.math.Coordinate) { + x = arg1.x; + y = arg1.y; + } else { + x = arg1; + y = opt_arg2; + } + + el.style.left = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (x), false); + el.style.top = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (y), false); +}; + + +/** + * Gets the offsetLeft and offsetTop properties of an element and returns them + * in a Coordinate object + * @param {Element} element Element. + * @return {!goog.math.Coordinate} The position. + */ +goog.style.getPosition = function(element) { + return new goog.math.Coordinate( + /** @type {!HTMLElement} */ (element).offsetLeft, + /** @type {!HTMLElement} */ (element).offsetTop); +}; + + +/** + * Returns the viewport element for a particular document + * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element + * of. + * @return {Element} document.documentElement or document.body. + */ +goog.style.getClientViewportElement = function(opt_node) { + var doc; + if (opt_node) { + doc = goog.dom.getOwnerDocument(opt_node); + } else { + doc = goog.dom.getDocument(); + } + + // In old IE versions the document.body represented the viewport + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && + !goog.dom.getDomHelper(doc).isCss1CompatMode()) { + return doc.body; + } + return doc.documentElement; +}; + + +/** + * Calculates the viewport coordinates relative to the page/document + * containing the node. The viewport may be the browser viewport for + * non-iframe document, or the iframe container for iframe'd document. + * @param {!Document} doc The document to use as the reference point. + * @return {!goog.math.Coordinate} The page offset of the viewport. + */ +goog.style.getViewportPageOffset = function(doc) { + var body = doc.body; + var documentElement = doc.documentElement; + var scrollLeft = body.scrollLeft || documentElement.scrollLeft; + var scrollTop = body.scrollTop || documentElement.scrollTop; + return new goog.math.Coordinate(scrollLeft, scrollTop); +}; + + +/** + * Gets the client rectangle of the DOM element. + * + * getBoundingClientRect is part of a new CSS object model draft (with a + * long-time presence in IE), replacing the error-prone parent offset + * computation and the now-deprecated Gecko getBoxObjectFor. + * + * This utility patches common browser bugs in getBoundingClientRect. It + * will fail if getBoundingClientRect is unsupported. + * + * If the element is not in the DOM, the result is undefined, and an error may + * be thrown depending on user agent. + * + * @param {!Element} el The element whose bounding rectangle is being queried. + * @return {Object} A native bounding rectangle with numerical left, top, + * right, and bottom. Reported by Firefox to be of object type ClientRect. + * @private + */ +goog.style.getBoundingClientRect_ = function(el) { + var rect; + try { + rect = el.getBoundingClientRect(); + } catch (e) { + // In IE < 9, calling getBoundingClientRect on an orphan element raises an + // "Unspecified Error". All other browsers return zeros. + return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; + } + + // Patch the result in IE only, so that this function can be inlined if + // compiled for non-IE. + if (goog.userAgent.IE && el.ownerDocument.body) { + // In IE, most of the time, 2 extra pixels are added to the top and left + // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and + // IE6 standards mode, this border can be overridden by setting the + // document element's border to zero -- thus, we cannot rely on the + // offset always being 2 pixels. + + // In quirks mode, the offset can be determined by querying the body's + // clientLeft/clientTop, but in standards mode, it is found by querying + // the document element's clientLeft/clientTop. Since we already called + // getBoundingClientRect we have already forced a reflow, so it is not + // too expensive just to query them all. + + // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx + var doc = el.ownerDocument; + rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; + rect.top -= doc.documentElement.clientTop + doc.body.clientTop; + } + return rect; +}; + + +/** + * Returns the first parent that could affect the position of a given element. + * @param {Element} element The element to get the offset parent for. + * @return {Element} The first offset parent or null if one cannot be found. + */ +goog.style.getOffsetParent = function(element) { + // element.offsetParent does the right thing in IE7 and below. In other + // browsers it only includes elements with position absolute, relative or + // fixed, not elements with overflow set to auto or scroll. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { + goog.asserts.assert(element && 'offsetParent' in element); + return element.offsetParent; + } + + var doc = goog.dom.getOwnerDocument(element); + var positionStyle = goog.style.getStyle_(element, 'position'); + var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; + for (var parent = element.parentNode; parent && parent != doc; + parent = parent.parentNode) { + // Skip shadowDOM roots. + if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT && parent.host) { + parent = parent.host; + } + positionStyle = + goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); + skipStatic = skipStatic && positionStyle == 'static' && + parent != doc.documentElement && parent != doc.body; + if (!skipStatic && + (parent.scrollWidth > parent.clientWidth || + parent.scrollHeight > parent.clientHeight || + positionStyle == 'fixed' || positionStyle == 'absolute' || + positionStyle == 'relative')) { + return /** @type {!Element} */ (parent); + } + } + return null; +}; + + +/** + * Calculates and returns the visible rectangle for a given element. Returns a + * box describing the visible portion of the nearest scrollable offset ancestor. + * Coordinates are given relative to the document. + * + * @param {Element} element Element to get the visible rect for. + * @return {goog.math.Box} Bounding elementBox describing the visible rect or + * null if scrollable ancestor isn't inside the visible viewport. + */ +goog.style.getVisibleRectForElement = function(element) { + var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); + var dom = goog.dom.getDomHelper(element); + var body = dom.getDocument().body; + var documentElement = dom.getDocument().documentElement; + var scrollEl = dom.getDocumentScrollElement(); + + // Determine the size of the visible rect by climbing the dom accounting for + // all scrollable containers. + for (var el = element; el = goog.style.getOffsetParent(el);) { + // clientWidth is zero for inline block elements in IE. + // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 + if ((!goog.userAgent.IE || el.clientWidth != 0) && + (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && + // body may have overflow set on it, yet we still get the entire + // viewport. In some browsers, el.offsetParent may be + // document.documentElement, so check for that too. + (el != body && el != documentElement && + goog.style.getStyle_(el, 'overflow') != 'visible')) { + var pos = goog.style.getPageOffset(el); + var client = goog.style.getClientLeftTop(el); + pos.x += client.x; + pos.y += client.y; + + visibleRect.top = Math.max(visibleRect.top, pos.y); + visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth); + visibleRect.bottom = + Math.min(visibleRect.bottom, pos.y + el.clientHeight); + visibleRect.left = Math.max(visibleRect.left, pos.x); + } + } + + // Clip by window's viewport. + var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; + visibleRect.left = Math.max(visibleRect.left, scrollX); + visibleRect.top = Math.max(visibleRect.top, scrollY); + var winSize = dom.getViewportSize(); + visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); + visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); + return visibleRect.top >= 0 && visibleRect.left >= 0 && + visibleRect.bottom > visibleRect.top && + visibleRect.right > visibleRect.left ? + visibleRect : + null; +}; + + +/** + * Calculate the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element=} opt_container The container to scroll. If not set, then the + * document scroll element will be used. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + * @return {!goog.math.Coordinate} The new scroll position of the container, + * in form of goog.math.Coordinate(scrollLeft, scrollTop). + */ +goog.style.getContainerOffsetToScrollInto = function( + element, opt_container, opt_center) { + var container = opt_container || goog.dom.getDocumentScrollElement(); + // Absolute position of the element's border's top left corner. + var elementPos = goog.style.getPageOffset(element); + // Absolute position of the container's border's top left corner. + var containerPos = goog.style.getPageOffset(container); + var containerBorder = goog.style.getBorderBox(container); + if (container == goog.dom.getDocumentScrollElement()) { + // The element position is calculated based on the page offset, and the + // document scroll element holds the scroll position within the page. We can + // use the scroll position to calculate the relative position from the + // element. + var relX = elementPos.x - container.scrollLeft; + var relY = elementPos.y - container.scrollTop; + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { + // In older versions of IE getPageOffset(element) does not include the + // container border so it has to be added to accommodate. + relX += containerBorder.left; + relY += containerBorder.top; + } + } else { + // Relative pos. of the element's border box to the container's content box. + var relX = elementPos.x - containerPos.x - containerBorder.left; + var relY = elementPos.y - containerPos.y - containerBorder.top; + } + // How much the element can move in the container, i.e. the difference between + // the element's bottom-right-most and top-left-most position where it's + // fully visible. + var elementSize = goog.style.getSizeWithDisplay_(element); + var spaceX = container.clientWidth - elementSize.width; + var spaceY = container.clientHeight - elementSize.height; + var scrollLeft = container.scrollLeft; + var scrollTop = container.scrollTop; + if (opt_center) { + // All browsers round non-integer scroll positions down. + scrollLeft += relX - spaceX / 2; + scrollTop += relY - spaceY / 2; + } else { + // This formula was designed to give the correct scroll values in the + // following cases: + // - element is higher than container (spaceY < 0) => scroll down by relY + // - element is not higher that container (spaceY >= 0): + // - it is above container (relY < 0) => scroll up by abs(relY) + // - it is below container (relY > spaceY) => scroll down by relY - spaceY + // - it is in the container => don't scroll + scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); + scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); + } + return new goog.math.Coordinate(scrollLeft, scrollTop); +}; + + +/** + * Changes the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element=} opt_container The container to scroll. If not set, then the + * document scroll element will be used. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + */ +goog.style.scrollIntoContainerView = function( + element, opt_container, opt_center) { + var container = opt_container || goog.dom.getDocumentScrollElement(); + var offset = + goog.style.getContainerOffsetToScrollInto(element, container, opt_center); + container.scrollLeft = offset.x; + container.scrollTop = offset.y; +}; + + +/** + * Returns clientLeft (width of the left border and, if the directionality is + * right to left, the vertical scrollbar) and clientTop as a coordinate object. + * + * @param {Element} el Element to get clientLeft for. + * @return {!goog.math.Coordinate} Client left and top. + */ +goog.style.getClientLeftTop = function(el) { + return new goog.math.Coordinate(el.clientLeft, el.clientTop); +}; + + +/** + * Returns a Coordinate object relative to the top-left of the HTML document. + * Implemented as a single function to save having to do two recursive loops in + * opera and safari just to get both coordinates. If you just want one value do + * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but + * note if you call both those methods the tree will be analysed twice. + * + * @param {Element} el Element to get the page offset for. + * @return {!goog.math.Coordinate} The page offset. + */ +goog.style.getPageOffset = function(el) { + var doc = goog.dom.getOwnerDocument(el); + // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. + goog.asserts.assertObject(el, 'Parameter is required'); + + // NOTE(arv): If element is hidden (display none or disconnected or any the + // ancestors are hidden) we get (0,0) by default but we still do the + // accumulation of scroll position. + + // TODO(arv): Should we check if the node is disconnected and in that case + // return (0,0)? + + var pos = new goog.math.Coordinate(0, 0); + var viewportElement = goog.style.getClientViewportElement(doc); + if (el == viewportElement) { + // viewport is always at 0,0 as that defined the coordinate system for this + // function - this avoids special case checks in the code below + return pos; + } + + var box = goog.style.getBoundingClientRect_(el); + // Must add the scroll coordinates in to get the absolute page offset + // of element since getBoundingClientRect returns relative coordinates to + // the viewport. + var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); + pos.x = box.left + scrollCoord.x; + pos.y = box.top + scrollCoord.y; + + return pos; +}; + + +/** + * Returns the left coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The left coordinate. + */ +goog.style.getPageOffsetLeft = function(el) { + return goog.style.getPageOffset(el).x; +}; + + +/** + * Returns the top coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The top coordinate. + */ +goog.style.getPageOffsetTop = function(el) { + return goog.style.getPageOffset(el).y; +}; + + +/** + * Returns a Coordinate object relative to the top-left of an HTML document + * in an ancestor frame of this element. Used for measuring the position of + * an element inside a frame relative to a containing frame. + * + * @param {Element} el Element to get the page offset for. + * @param {Window} relativeWin The window to measure relative to. If relativeWin + * is not in the ancestor frame chain of the element, we measure relative to + * the top-most window. + * @return {!goog.math.Coordinate} The page offset. + */ +goog.style.getFramedPageOffset = function(el, relativeWin) { + var position = new goog.math.Coordinate(0, 0); + + // Iterate up the ancestor frame chain, keeping track of the current window + // and the current element in that window. + var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); + + // MS Edge throws when accessing "parent" if el's containing iframe has been + // deleted. + if (!goog.reflect.canAccessProperty(currentWin, 'parent')) { + return position; + } + + var currentEl = el; + do { + // if we're at the top window, we want to get the page offset. + // if we're at an inner frame, we only want to get the window position + // so that we can determine the actual page offset in the context of + // the outer window. + var offset = currentWin == relativeWin ? + goog.style.getPageOffset(currentEl) : + goog.style.getClientPositionForElement_(goog.asserts.assert(currentEl)); + + position.x += offset.x; + position.y += offset.y; + } while (currentWin && currentWin != relativeWin && + currentWin != currentWin.parent && + (currentEl = currentWin.frameElement) && + (currentWin = currentWin.parent)); + + return position; +}; + + +/** + * Translates the specified rect relative to origBase page, for newBase page. + * If origBase and newBase are the same, this function does nothing. + * + * @param {goog.math.Rect} rect The source rectangle relative to origBase page, + * and it will have the translated result. + * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. + * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant + * coordinate. This must be a DOM for an ancestor frame of origBase + * or the same as origBase. + */ +goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { + if (origBase.getDocument() != newBase.getDocument()) { + var body = origBase.getDocument().body; + var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); + + // Adjust Body's margin. + pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); + + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && + !origBase.isCss1CompatMode()) { + pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); + } + + rect.left += pos.x; + rect.top += pos.y; + } +}; + + +/** + * Returns the position of an element relative to another element in the + * document. A relative to B + * @param {Element|Event|goog.events.Event} a Element or mouse event whose + * position we're calculating. + * @param {Element|Event|goog.events.Event} b Element or mouse event position + * is relative to. + * @return {!goog.math.Coordinate} The relative position. + */ +goog.style.getRelativePosition = function(a, b) { + var ap = goog.style.getClientPosition(a); + var bp = goog.style.getClientPosition(b); + return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); +}; + + +/** + * Returns the position of the event or the element's border box relative to + * the client viewport. + * @param {!Element} el Element whose position to get. + * @return {!goog.math.Coordinate} The position. + * @private + */ +goog.style.getClientPositionForElement_ = function(el) { + var box = goog.style.getBoundingClientRect_(el); + return new goog.math.Coordinate(box.left, box.top); +}; + + +/** + * Returns the position of the event or the element's border box relative to + * the client viewport. If an event is passed, and if this event is a "touch" + * event, then the position of the first changedTouches will be returned. + * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. + * @return {!goog.math.Coordinate} The position. + */ +goog.style.getClientPosition = function(el) { + goog.asserts.assert(el); + if (el.nodeType == goog.dom.NodeType.ELEMENT) { + return goog.style.getClientPositionForElement_( + /** @type {!Element} */ (el)); + } else { + var targetEvent = el.changedTouches ? el.changedTouches[0] : el; + return new goog.math.Coordinate(targetEvent.clientX, targetEvent.clientY); + } +}; + + +/** + * Moves an element to the given coordinates relative to the client viewport. + * @param {Element} el Absolutely positioned element to set page offset for. + * It must be in the document. + * @param {number|goog.math.Coordinate} x Left position of the element's margin + * box or a coordinate object. + * @param {number=} opt_y Top position of the element's margin box. + */ +goog.style.setPageOffset = function(el, x, opt_y) { + // Get current pageoffset + var cur = goog.style.getPageOffset(el); + + if (x instanceof goog.math.Coordinate) { + opt_y = x.y; + x = x.x; + } + + // NOTE(arv): We cannot allow strings for x and y. We could but that would + // require us to manually transform between different units + + // Work out deltas + var dx = goog.asserts.assertNumber(x) - cur.x; + var dy = Number(opt_y) - cur.y; + + // Set position to current left/top + delta + goog.style.setPosition( + el, /** @type {!HTMLElement} */ (el).offsetLeft + dx, + /** @type {!HTMLElement} */ (el).offsetTop + dy); +}; + + +/** + * Sets the width/height values of an element. If an argument is numeric, + * or a goog.math.Size is passed, it is assumed to be pixels and will add + * 'px' after converting it to an integer in string form. (This just sets the + * CSS width and height properties so it might set content-box or border-box + * size depending on the box model the browser is using.) + * + * @param {Element} element Element to set the size of. + * @param {string|number|goog.math.Size} w Width of the element, or a + * size object. + * @param {string|number=} opt_h Height of the element. Required if w is not a + * size object. + */ +goog.style.setSize = function(element, w, opt_h) { + var h; + if (w instanceof goog.math.Size) { + h = w.height; + w = w.width; + } else { + if (opt_h == undefined) { + throw new Error('missing height argument'); + } + h = opt_h; + } + + goog.style.setWidth(element, /** @type {string|number} */ (w)); + goog.style.setHeight(element, h); +}; + + +/** + * Helper function to create a string to be set into a pixel-value style + * property of an element. Can round to the nearest integer value. + * + * @param {string|number} value The style value to be used. If a number, + * 'px' will be appended, otherwise the value will be applied directly. + * @param {boolean} round Whether to round the nearest integer (if property + * is a number). + * @return {string} The string value for the property. + * @private + */ +goog.style.getPixelStyleValue_ = function(value, round) { + if (typeof value == 'number') { + value = (round ? Math.round(value) : value) + 'px'; + } + + return value; +}; + + +/** + * Set the height of an element. Sets the element's style property. + * @param {Element} element Element to set the height of. + * @param {string|number} height The height value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setHeight = function(element, height) { + element.style.height = goog.style.getPixelStyleValue_(height, true); +}; + + +/** + * Set the width of an element. Sets the element's style property. + * @param {Element} element Element to set the width of. + * @param {string|number} width The width value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setWidth = function(element, width) { + element.style.width = goog.style.getPixelStyleValue_(width, true); +}; + + +/** + * Gets the height and width of an element, even if its display is none. + * + * Specifically, this returns the height and width of the border box, + * irrespective of the box model in effect. + * + * Note that this function does not take CSS transforms into account. Please see + * {@code goog.style.getTransformedSize}. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. + */ +goog.style.getSize = function(element) { + return goog.style.evaluateWithTemporaryDisplay_( + goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); +}; + + +/** + * Call {@code fn} on {@code element} such that {@code element}'s dimensions are + * accurate when it's passed to {@code fn}. + * @param {function(!Element): T} fn Function to call with {@code element} as + * an argument after temporarily changing {@code element}'s display such + * that its dimensions are accurate. + * @param {!Element} element Element (which may have display none) to use as + * argument to {@code fn}. + * @return {T} Value returned by calling {@code fn} with {@code element}. + * @template T + * @private + */ +goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { + if (goog.style.getStyle_(element, 'display') != 'none') { + return fn(element); + } + + var style = element.style; + var originalDisplay = style.display; + var originalVisibility = style.visibility; + var originalPosition = style.position; + + style.visibility = 'hidden'; + style.position = 'absolute'; + style.display = 'inline'; + + var retVal = fn(element); + + style.display = originalDisplay; + style.position = originalPosition; + style.visibility = originalVisibility; + + return retVal; +}; + + +/** + * Gets the height and width of an element when the display is not none. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. + * @private + */ +goog.style.getSizeWithDisplay_ = function(element) { + var offsetWidth = /** @type {!HTMLElement} */ (element).offsetWidth; + var offsetHeight = /** @type {!HTMLElement} */ (element).offsetHeight; + var webkitOffsetsZero = + goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; + if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && + element.getBoundingClientRect) { + // Fall back to calling getBoundingClientRect when offsetWidth or + // offsetHeight are not defined, or when they are zero in WebKit browsers. + // This makes sure that we return for the correct size for SVG elements, but + // will still return 0 on Webkit prior to 534.8, see + // http://trac.webkit.org/changeset/67252. + var clientRect = goog.style.getBoundingClientRect_(element); + return new goog.math.Size( + clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); + } + return new goog.math.Size(offsetWidth, offsetHeight); +}; + + +/** + * Gets the height and width of an element, post transform, even if its display + * is none. + * + * This is like {@code goog.style.getSize}, except: + * <ol> + * <li>Takes webkitTransforms such as rotate and scale into account. + * <li>Will return null if {@code element} doesn't respond to + * {@code getBoundingClientRect}. + * <li>Currently doesn't make sense on non-WebKit browsers which don't support + * webkitTransforms. + * </ol> + * @param {!Element} element Element to get size of. + * @return {goog.math.Size} Object with width/height properties. + */ +goog.style.getTransformedSize = function(element) { + if (!element.getBoundingClientRect) { + return null; + } + + var clientRect = goog.style.evaluateWithTemporaryDisplay_( + goog.style.getBoundingClientRect_, element); + return new goog.math.Size( + clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); +}; + + +/** + * Returns a bounding rectangle for a given element in page space. + * @param {Element} element Element to get bounds of. Must not be display none. + * @return {!goog.math.Rect} Bounding rectangle for the element. + */ +goog.style.getBounds = function(element) { + var o = goog.style.getPageOffset(element); + var s = goog.style.getSize(element); + return new goog.math.Rect(o.x, o.y, s.width, s.height); +}; + + +/** + * Converts a CSS selector in the form style-property to styleProperty. + * @param {*} selector CSS Selector. + * @return {string} Camel case selector. + * @deprecated Use goog.string.toCamelCase instead. + */ +goog.style.toCamelCase = function(selector) { + return goog.string.toCamelCase(String(selector)); +}; + + +/** + * Converts a CSS selector in the form styleProperty to style-property. + * @param {string} selector Camel case selector. + * @return {string} Selector cased. + * @deprecated Use goog.string.toSelectorCase instead. + */ +goog.style.toSelectorCase = function(selector) { + return goog.string.toSelectorCase(selector); +}; + + +/** + * Gets the opacity of a node (x-browser). This gets the inline style opacity + * of the node, and does not take into account the cascaded or the computed + * style for this node. + * @param {Element} el Element whose opacity has to be found. + * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} + * if the opacity is not set. + */ +goog.style.getOpacity = function(el) { + goog.asserts.assert(el); + var style = el.style; + var result = ''; + if ('opacity' in style) { + result = style.opacity; + } else if ('MozOpacity' in style) { + result = style.MozOpacity; + } else if ('filter' in style) { + var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); + if (match) { + result = String(match[1] / 100); + } + } + return result == '' ? result : Number(result); +}; + + +/** + * Sets the opacity of a node (x-browser). + * @param {Element} el Elements whose opacity has to be set. + * @param {number|string} alpha Opacity between 0 and 1 or an empty string + * {@code ''} to clear the opacity. + */ +goog.style.setOpacity = function(el, alpha) { + goog.asserts.assert(el); + var style = el.style; + if ('opacity' in style) { + style.opacity = alpha; + } else if ('MozOpacity' in style) { + style.MozOpacity = alpha; + } else if ('filter' in style) { + // TODO(arv): Overwriting the filter might have undesired side effects. + if (alpha === '') { + style.filter = ''; + } else { + style.filter = 'alpha(opacity=' + (Number(alpha) * 100) + ')'; + } + } +}; + + +/** + * Sets the background of an element to a transparent image in a browser- + * independent manner. + * + * This function does not support repeating backgrounds or alternate background + * positions to match the behavior of Internet Explorer. It also does not + * support sizingMethods other than crop since they cannot be replicated in + * browsers other than Internet Explorer. + * + * @param {Element} el The element to set background on. + * @param {string} src The image source URL. + */ +goog.style.setTransparentBackgroundImage = function(el, src) { + var style = el.style; + // It is safe to use the style.filter in IE only. In Safari 'filter' is in + // style object but access to style.filter causes it to throw an exception. + // Note: IE8 supports images with an alpha channel. + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // See TODO in setOpacity. + style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + + 'src="' + src + '", sizingMethod="crop")'; + } else { + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'url(' + src + ')'; + style.backgroundPosition = 'top left'; + style.backgroundRepeat = 'no-repeat'; + } +}; + + +/** + * Clears the background image of an element in a browser independent manner. + * @param {Element} el The element to clear background image for. + */ +goog.style.clearTransparentBackgroundImage = function(el) { + var style = el.style; + if ('filter' in style) { + // See TODO in setOpacity. + style.filter = ''; + } else { + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'none'; + } +}; + + +/** + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules.) + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * showElement(), effectively toggling the display between "none" and "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * showElement will clear that setting and defer to the inherited style in the + * stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} display True to render the element in its default style, + * false to disable rendering the element. + * @deprecated Use goog.style.setElementShown instead. + */ +goog.style.showElement = function(el, display) { + goog.style.setElementShown(el, display); +}; + + +/** + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules). + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * setElementShown(), effectively toggling the display between "none" and + * "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * setElementShown will clear that setting and defer to the inherited style in + * the stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} isShown True to render the element in its default style, + * false to disable rendering the element. + */ +goog.style.setElementShown = function(el, isShown) { + el.style.display = isShown ? '' : 'none'; +}; + + +/** + * Test whether the given element has been shown or hidden via a call to + * {@link #setElementShown}. + * + * Note this is strictly a companion method for a call + * to {@link #setElementShown} and the same caveats apply; in particular, this + * method does not guarantee that the return value will be consistent with + * whether or not the element is actually visible. + * + * @param {Element} el The element to test. + * @return {boolean} Whether the element has been shown. + * @see #setElementShown + */ +goog.style.isElementShown = function(el) { + return el.style.display != 'none'; +}; + + +/** + * Installs the style sheet into the window that contains opt_node. If + * opt_node is null, the main window is used. + * @param {!goog.html.SafeStyleSheet} safeStyleSheet The style sheet to install. + * @param {?Node=} opt_node Node whose parent document should have the + * styles installed. + * @return {!HTMLStyleElement|!StyleSheet} In IE<11, a StyleSheet object with no + * owning <style> tag (this is how IE creates style sheets). In every other + * browser, a <style> element with an attached style. This doesn't return a + * StyleSheet object so that setSafeStyleSheet can replace it (otherwise, if + * you pass a StyleSheet to setSafeStyleSheet, it will make a new StyleSheet + * and leave the original StyleSheet orphaned). + */ +goog.style.installSafeStyleSheet = function(safeStyleSheet, opt_node) { + var dh = goog.dom.getDomHelper(opt_node); + + // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be + // undefined as of IE 11. + var doc = dh.getDocument(); + if (goog.userAgent.IE && doc.createStyleSheet) { + var styleSheet = doc.createStyleSheet(); + goog.style.setSafeStyleSheet(styleSheet, safeStyleSheet); + return styleSheet; + } else { + var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0]; + + // In opera documents are not guaranteed to have a head element, thus we + // have to make sure one exists before using it. + if (!head) { + var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0]; + head = dh.createDom(goog.dom.TagName.HEAD); + body.parentNode.insertBefore(head, body); + } + var el = dh.createDom(goog.dom.TagName.STYLE); + // NOTE(vkarun): Setting styles after the style element has been appended + // to the head results in a nasty Webkit bug in certain scenarios. Please + // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional + // details. + goog.style.setSafeStyleSheet(el, safeStyleSheet); + dh.appendChild(head, el); + return el; + } +}; + + +/** + * Removes the styles added by {@link #installStyles}. + * @param {!HTMLStyleElement|!StyleSheet} styleSheet The value returned by + * {@link #installStyles}. + */ +goog.style.uninstallStyles = function(styleSheet) { + var node = styleSheet.ownerNode || styleSheet.owningElement || + /** @type {Element} */ (styleSheet); + goog.dom.removeNode(node); +}; + + +/** + * Sets the content of a style element. The style element can be any valid + * style element. This element will have its content completely replaced by + * the safeStyleSheet. + * @param {!HTMLStyleElement|!StyleSheet} element A <style> element, as returned + * by installStyles (or a Stylesheet in IE<11). + * @param {!goog.html.SafeStyleSheet} safeStyleSheet The new content of the + * stylesheet. + */ +goog.style.setSafeStyleSheet = function(element, safeStyleSheet) { + var stylesString = goog.html.SafeStyleSheet.unwrap(safeStyleSheet); + if (goog.userAgent.IE && goog.isDef(element.cssText)) { + // Adding the selectors individually caused the browser to hang if the + // selector was invalid or there were CSS comments. Setting the cssText of + // the style node works fine and ignores CSS that IE doesn't understand. + // However IE >= 11 doesn't support cssText any more, so we make sure that + // cssText is a defined property and otherwise fall back to innerHTML. + element.cssText = stylesString; + } else { + // Setting textContent doesn't work in Safari, see b/29340337. + element.innerHTML = stylesString; + } +}; + + +/** + * Sets 'white-space: pre-wrap' for a node (x-browser). + * + * There are as many ways of specifying pre-wrap as there are browsers. + * + * CSS3/IE8: white-space: pre-wrap; + * Mozilla: white-space: -moz-pre-wrap; + * Opera: white-space: -o-pre-wrap; + * IE6/7: white-space: pre; word-wrap: break-word; + * + * @param {Element} el Element to enable pre-wrap for. + */ +goog.style.setPreWrap = function(el) { + var style = el.style; + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + style.whiteSpace = 'pre'; + style.wordWrap = 'break-word'; + } else if (goog.userAgent.GECKO) { + style.whiteSpace = '-moz-pre-wrap'; + } else { + style.whiteSpace = 'pre-wrap'; + } +}; + + +/** + * Sets 'display: inline-block' for an element (cross-browser). + * @param {Element} el Element to which the inline-block display style is to be + * applied. + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html + */ +goog.style.setInlineBlock = function(el) { + var style = el.style; + // Without position:relative, weirdness ensues. Just accept it and move on. + style.position = 'relative'; + + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // IE8 supports inline-block so fall through to the else + // Zoom:1 forces hasLayout, display:inline gives inline behavior. + style.zoom = '1'; + style.display = 'inline'; + } else { + // Opera, Webkit, and Safari seem to do OK with the standard inline-block + // style. + style.display = 'inline-block'; + } +}; + + +/** + * Returns true if the element is using right to left (rtl) direction. + * @param {Element} el The element to test. + * @return {boolean} True for right to left, false for left to right. + */ +goog.style.isRightToLeft = function(el) { + return 'rtl' == goog.style.getStyle_(el, 'direction'); +}; + + +/** + * The CSS style property corresponding to an element being + * unselectable on the current browser platform (null if none). + * Opera and IE instead use a DOM attribute 'unselectable'. MS Edge uses + * the Webkit prefix. + * @type {?string} + * @private + */ +goog.style.unselectableStyle_ = goog.userAgent.GECKO ? + 'MozUserSelect' : + goog.userAgent.WEBKIT || goog.userAgent.EDGE ? 'WebkitUserSelect' : null; + + +/** + * Returns true if the element is set to be unselectable, false otherwise. + * Note that on some platforms (e.g. Mozilla), even if an element isn't set + * to be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el Element to check. + * @return {boolean} Whether the element is set to be unselectable. + */ +goog.style.isUnselectable = function(el) { + if (goog.style.unselectableStyle_) { + return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + return el.getAttribute('unselectable') == 'on'; + } + return false; +}; + + +/** + * Makes the element and its descendants selectable or unselectable. Note + * that on some platforms (e.g. Mozilla), even if an element isn't set to + * be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el The element to alter. + * @param {boolean} unselectable Whether the element and its descendants + * should be made unselectable. + * @param {boolean=} opt_noRecurse Whether to only alter the element's own + * selectable state, and leave its descendants alone; defaults to false. + */ +goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { + // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure? + var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; + var name = goog.style.unselectableStyle_; + if (name) { + // Add/remove the appropriate CSS style to/from the element and its + // descendants. + var value = unselectable ? 'none' : ''; + // MathML elements do not have a style property. Verify before setting. + if (el.style) { + el.style[name] = value; + } + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + if (descendant.style) { + descendant.style[name] = value; + } + } + } + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + // Toggle the 'unselectable' attribute on the element and its descendants. + var value = unselectable ? 'on' : ''; + el.setAttribute('unselectable', value); + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + descendant.setAttribute('unselectable', value); + } + } + } +}; + + +/** + * Gets the border box size for an element. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The border box size. + */ +goog.style.getBorderBoxSize = function(element) { + return new goog.math.Size( + /** @type {!HTMLElement} */ (element).offsetWidth, + /** @type {!HTMLElement} */ (element).offsetHeight); +}; + + +/** + * Sets the border box size of an element. This is potentially expensive in IE + * if the document is CSS1Compat mode + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. + */ +goog.style.setBorderBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); + + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width - borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right; + style.pixelHeight = size.height - borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom; + } else { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'border-box'); + } +}; + + +/** + * Gets the content box size for an element. This is potentially expensive in + * all browsers. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The content box size. + */ +goog.style.getContentBoxSize = function(element) { + var doc = goog.dom.getOwnerDocument(element); + var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; + if (ieCurrentStyle && goog.dom.getDomHelper(doc).isCss1CompatMode() && + ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && + !ieCurrentStyle.boxSizing) { + // If IE in CSS1Compat mode than just use the width and height. + // If we have a boxSizing then fall back on measuring the borders etc. + var width = goog.style.getIePixelValue_( + element, /** @type {string} */ (ieCurrentStyle.width), 'width', + 'pixelWidth'); + var height = goog.style.getIePixelValue_( + element, /** @type {string} */ (ieCurrentStyle.height), 'height', + 'pixelHeight'); + return new goog.math.Size(width, height); + } else { + var borderBoxSize = goog.style.getBorderBoxSize(element); + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + return new goog.math.Size( + borderBoxSize.width - borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right, + borderBoxSize.height - borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom); + } +}; + + +/** + * Sets the content box size of an element. This is potentially expensive in IE + * if the document is BackCompat mode. + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. + */ +goog.style.setContentBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10') && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } else { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width + borderBox.left + paddingBox.left + + paddingBox.right + borderBox.right; + style.pixelHeight = size.height + borderBox.top + paddingBox.top + + paddingBox.bottom + borderBox.bottom; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'content-box'); + } +}; + + +/** + * Helper function that sets the box sizing as well as the width and height + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size to set. + * @param {string} boxSizing The box-sizing value. + * @private + */ +goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { + var style = element.style; + if (goog.userAgent.GECKO) { + style.MozBoxSizing = boxSizing; + } else if (goog.userAgent.WEBKIT) { + style.WebkitBoxSizing = boxSizing; + } else { + // Includes IE8 and Opera 9.50+ + style.boxSizing = boxSizing; + } + + // Setting this to a negative value will throw an exception on IE + // (and doesn't do anything different than setting it to 0). + style.width = Math.max(size.width, 0) + 'px'; + style.height = Math.max(size.height, 0) + 'px'; +}; + + +/** + * IE specific function that converts a non pixel unit to pixels. + * @param {Element} element The element to convert the value for. + * @param {string} value The current value as a string. The value must not be + * ''. + * @param {string} name The CSS property name to use for the converstion. This + * should be 'left', 'top', 'width' or 'height'. + * @param {string} pixelName The CSS pixel property name to use to get the + * value in pixels. + * @return {number} The value in pixels. + * @private + */ +goog.style.getIePixelValue_ = function(element, value, name, pixelName) { + // Try if we already have a pixel value. IE does not do half pixels so we + // only check if it matches a number followed by 'px'. + if (/^\d+px?$/.test(value)) { + return parseInt(value, 10); + } else { + var oldStyleValue = element.style[name]; + var oldRuntimeValue = element.runtimeStyle[name]; + // set runtime style to prevent changes + element.runtimeStyle[name] = element.currentStyle[name]; + element.style[name] = value; + var pixelValue = element.style[pixelName]; + // restore + element.style[name] = oldStyleValue; + element.runtimeStyle[name] = oldRuntimeValue; + return +pixelValue; + } +}; + + +/** + * Helper function for getting the pixel padding or margin for IE. + * @param {Element} element The element to get the padding for. + * @param {string} propName The property name. + * @return {number} The pixel padding. + * @private + */ +goog.style.getIePixelDistance_ = function(element, propName) { + var value = goog.style.getCascadedStyle(element, propName); + return value ? + goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : + 0; +}; + + +/** + * Gets the computed paddings or margins (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, + * or 'margin' to retrieve the margin box. + * @return {!goog.math.Box} The computed paddings or margins. + * @private + */ +goog.style.getBox_ = function(element, stylePrefix) { + if (goog.userAgent.IE) { + var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); + var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); + var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); + var bottom = + goog.style.getIePixelDistance_(element, stylePrefix + 'Bottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = goog.style.getComputedStyle(element, stylePrefix + 'Left'); + var right = goog.style.getComputedStyle(element, stylePrefix + 'Right'); + var top = goog.style.getComputedStyle(element, stylePrefix + 'Top'); + var bottom = goog.style.getComputedStyle(element, stylePrefix + 'Bottom'); + + // NOTE(arv): Gecko can return floating point numbers for the computed + // style values. + return new goog.math.Box( + parseFloat(top), parseFloat(right), parseFloat(bottom), + parseFloat(left)); + } +}; + + +/** + * Gets the computed paddings (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @return {!goog.math.Box} The computed paddings. + */ +goog.style.getPaddingBox = function(element) { + return goog.style.getBox_(element, 'padding'); +}; + + +/** + * Gets the computed margins (on all sides) in pixels. + * @param {Element} element The element to get the margins for. + * @return {!goog.math.Box} The computed margins. + */ +goog.style.getMarginBox = function(element) { + return goog.style.getBox_(element, 'margin'); +}; + + +/** + * A map used to map the border width keywords to a pixel width. + * @type {!Object} + * @private + */ +goog.style.ieBorderWidthKeywords_ = { + 'thin': 2, + 'medium': 4, + 'thick': 6 +}; + + +/** + * Helper function for IE to get the pixel border. + * @param {Element} element The element to get the pixel border for. + * @param {string} prop The part of the property name. + * @return {number} The value in pixels. + * @private + */ +goog.style.getIePixelBorder_ = function(element, prop) { + if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { + return 0; + } + var width = goog.style.getCascadedStyle(element, prop + 'Width'); + if (width in goog.style.ieBorderWidthKeywords_) { + return goog.style.ieBorderWidthKeywords_[width]; + } + return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); +}; + + +/** + * Gets the computed border widths (on all sides) in pixels + * @param {Element} element The element to get the border widths for. + * @return {!goog.math.Box} The computed border widths. + */ +goog.style.getBorderBox = function(element) { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + var left = goog.style.getIePixelBorder_(element, 'borderLeft'); + var right = goog.style.getIePixelBorder_(element, 'borderRight'); + var top = goog.style.getIePixelBorder_(element, 'borderTop'); + var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = goog.style.getComputedStyle(element, 'borderLeftWidth'); + var right = goog.style.getComputedStyle(element, 'borderRightWidth'); + var top = goog.style.getComputedStyle(element, 'borderTopWidth'); + var bottom = goog.style.getComputedStyle(element, 'borderBottomWidth'); + + return new goog.math.Box( + parseFloat(top), parseFloat(right), parseFloat(bottom), + parseFloat(left)); + } +}; + + +/** + * Returns the font face applied to a given node. Opera and IE should return + * the font actually displayed. Firefox returns the author's most-preferred + * font (whether the browser is capable of displaying it or not.) + * @param {Element} el The element whose font family is returned. + * @return {string} The font family applied to el. + */ +goog.style.getFontFamily = function(el) { + var doc = goog.dom.getOwnerDocument(el); + var font = ''; + // The moveToElementText method from the TextRange only works if the element + // is attached to the owner document. + if (doc.body.createTextRange && goog.dom.contains(doc, el)) { + var range = doc.body.createTextRange(); + range.moveToElementText(el); + + try { + font = range.queryCommandValue('FontName'); + } catch (e) { + // This is a workaround for a awkward exception. + // On some IE, there is an exception coming from it. + // The error description from this exception is: + // This window has already been registered as a drop target + // This is bogus description, likely due to a bug in ie. + font = ''; + } + } + if (!font) { + // Note if for some reason IE can't derive FontName with a TextRange, we + // fallback to using currentStyle + font = goog.style.getStyle_(el, 'fontFamily'); + } + + // Firefox returns the applied font-family string (author's list of + // preferred fonts.) We want to return the most-preferred font, in lieu of + // the *actually* applied font. + var fontsArray = font.split(','); + if (fontsArray.length > 1) font = fontsArray[0]; + + // Sanitize for x-browser consistency: + // Strip quotes because browsers aren't consistent with how they're + // applied; Opera always encloses, Firefox sometimes, and IE never. + return goog.string.stripQuotes(font, '"\''); +}; + + +/** + * Regular expression used for getLengthUnits. + * @type {RegExp} + * @private + */ +goog.style.lengthUnitRegex_ = /[^\d]+$/; + + +/** + * Returns the units used for a CSS length measurement. + * @param {string} value A CSS length quantity. + * @return {?string} The units of measurement. + */ +goog.style.getLengthUnits = function(value) { + var units = value.match(goog.style.lengthUnitRegex_); + return units && units[0] || null; +}; + + +/** + * Map of absolute CSS length units + * @type {!Object} + * @private + */ +goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { + 'cm': 1, + 'in': 1, + 'mm': 1, + 'pc': 1, + 'pt': 1 +}; + + +/** + * Map of relative CSS length units that can be accurately converted to px + * font-size values using getIePixelValue_. Only units that are defined in + * relation to a font size are convertible (%, small, etc. are not). + * @type {!Object} + * @private + */ +goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { + 'em': 1, + 'ex': 1 +}; + + +/** + * Returns the font size, in pixels, of text in an element. + * @param {Element} el The element whose font size is returned. + * @return {number} The font size (in pixels). + */ +goog.style.getFontSize = function(el) { + var fontSize = goog.style.getStyle_(el, 'fontSize'); + var sizeUnits = goog.style.getLengthUnits(fontSize); + if (fontSize && 'px' == sizeUnits) { + // NOTE(nathanl): This could be parseFloat instead, but IE doesn't return + // decimal fractions in getStyle_ and Firefox reports the fractions, but + // ignores them when rendering. Interestingly enough, when we force the + // issue and size something to e.g., 50% of 25px, the browsers round in + // opposite directions with Firefox reporting 12px and IE 13px. I punt. + return parseInt(fontSize, 10); + } + + // In IE, we can convert absolute length units to a px value using + // goog.style.getIePixelValue_. Units defined in relation to a font size + // (em, ex) are applied relative to the element's parentNode and can also + // be converted. + if (goog.userAgent.IE) { + if (String(sizeUnits) in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { + return goog.style.getIePixelValue_(el, fontSize, 'left', 'pixelLeft'); + } else if ( + el.parentNode && el.parentNode.nodeType == goog.dom.NodeType.ELEMENT && + String(sizeUnits) in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { + // Check the parent size - if it is the same it means the relative size + // value is inherited and we therefore don't want to count it twice. If + // it is different, this element either has explicit style or has a CSS + // rule applying to it. + var parentElement = /** @type {!Element} */ (el.parentNode); + var parentSize = goog.style.getStyle_(parentElement, 'fontSize'); + return goog.style.getIePixelValue_( + parentElement, fontSize == parentSize ? '1em' : fontSize, 'left', + 'pixelLeft'); + } + } + + // Sometimes we can't cleanly find the font size (some units relative to a + // node's parent's font size are difficult: %, smaller et al), so we create + // an invisible, absolutely-positioned span sized to be the height of an 'M' + // rendered in its parent's (i.e., our target element's) font size. This is + // the definition of CSS's font size attribute. + var sizeElement = goog.dom.createDom(goog.dom.TagName.SPAN, { + 'style': 'visibility:hidden;position:absolute;' + + 'line-height:0;padding:0;margin:0;border:0;height:1em;' + }); + goog.dom.appendChild(el, sizeElement); + fontSize = sizeElement.offsetHeight; + goog.dom.removeNode(sizeElement); + + return fontSize; +}; + + +/** + * Parses a style attribute value. Converts CSS property names to camel case. + * @param {string} value The style attribute value. + * @return {!Object} Map of CSS properties to string values. + */ +goog.style.parseStyleAttribute = function(value) { + var result = {}; + goog.array.forEach(value.split(/\s*;\s*/), function(pair) { + var keyValue = pair.match(/\s*([\w-]+)\s*\:(.+)/); + if (keyValue) { + var styleName = keyValue[1]; + var styleValue = goog.string.trim(keyValue[2]); + result[goog.string.toCamelCase(styleName.toLowerCase())] = styleValue; + } + }); + return result; +}; + + +/** + * Reverse of parseStyleAttribute; that is, takes a style object and returns the + * corresponding attribute value. Converts camel case property names to proper + * CSS selector names. + * @param {Object} obj Map of CSS properties to values. + * @return {string} The style attribute value. + */ +goog.style.toStyleAttribute = function(obj) { + var buffer = []; + goog.object.forEach(obj, function(value, key) { + buffer.push(goog.string.toSelectorCase(key), ':', value, ';'); + }); + return buffer.join(''); +}; + + +/** + * Sets CSS float property on an element. + * @param {Element} el The element to set float property on. + * @param {string} value The value of float CSS property to set on this element. + */ +goog.style.setFloat = function(el, value) { + el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; +}; + + +/** + * Gets value of explicitly-set float CSS property on an element. + * @param {Element} el The element to get float property of. + * @return {string} The value of explicitly-set float CSS property on this + * element. + */ +goog.style.getFloat = function(el) { + return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; +}; + + +/** + * Returns the scroll bar width (represents the width of both horizontal + * and vertical scroll). + * + * @param {string=} opt_className An optional class name (or names) to apply + * to the invisible div created to measure the scrollbar. This is necessary + * if some scrollbars are styled differently than others. + * @return {number} The scroll bar width in px. + */ +goog.style.getScrollbarWidth = function(opt_className) { + // Add two hidden divs. The child div is larger than the parent and + // forces scrollbars to appear on it. + // Using overflow:scroll does not work consistently with scrollbars that + // are styled with ::-webkit-scrollbar. + var outerDiv = goog.dom.createElement(goog.dom.TagName.DIV); + if (opt_className) { + outerDiv.className = opt_className; + } + outerDiv.style.cssText = 'overflow:auto;' + + 'position:absolute;top:0;width:100px;height:100px'; + var innerDiv = goog.dom.createElement(goog.dom.TagName.DIV); + goog.style.setSize(innerDiv, '200px', '200px'); + outerDiv.appendChild(innerDiv); + goog.dom.appendChild(goog.dom.getDocument().body, outerDiv); + var width = outerDiv.offsetWidth - outerDiv.clientWidth; + goog.dom.removeNode(outerDiv); + return width; +}; + + +/** + * Regular expression to extract x and y translation components from a CSS + * transform Matrix representation. + * + * @type {!RegExp} + * @const + * @private + */ +goog.style.MATRIX_TRANSLATION_REGEX_ = new RegExp( + 'matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '[0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)'); + + +/** + * Returns the x,y translation component of any CSS transforms applied to the + * element, in pixels. + * + * @param {!Element} element The element to get the translation of. + * @return {!goog.math.Coordinate} The CSS translation of the element in px. + */ +goog.style.getCssTranslation = function(element) { + var transform = goog.style.getComputedTransform(element); + if (!transform) { + return new goog.math.Coordinate(0, 0); + } + var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_); + if (!matches) { + return new goog.math.Coordinate(0, 0); + } + return new goog.math.Coordinate( + parseFloat(matches[1]), parseFloat(matches[2])); +};
diff --git a/third_party/ink/closure/ui/component.js b/third_party/ink/closure/ui/component.js new file mode 100644 index 0000000..f4f7737a --- /dev/null +++ b/third_party/ink/closure/ui/component.js
@@ -0,0 +1,1305 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Abstract class for all UI components. This defines the standard + * design pattern that all UI components should follow. + * + * @author pupius@google.com (Daniel Pupius) + * @author ssaviano@google.com (Steven Saviano) + * @author baker@google.com (Greg Baker) + * @author attila@google.com (Attila Bodis) + * @see ../demos/samplecomponent.html + * @see http://code.google.com/p/closure-library/wiki/IntroToComponents + */ + +goog.provide('goog.ui.Component'); +goog.provide('goog.ui.Component.Error'); +goog.provide('goog.ui.Component.EventType'); +goog.provide('goog.ui.Component.State'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.ui.IdGenerator'); + + + +/** + * Default implementation of UI component. + * + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {goog.events.EventTarget} + * @suppress {underscore} + */ +goog.ui.Component = function(opt_domHelper) { + goog.events.EventTarget.call(this); + /** + * DomHelper used to interact with the document, allowing components to be + * created in a different window. + * @protected {!goog.dom.DomHelper} + * @suppress {underscore|visibility} + */ + this.dom_ = opt_domHelper || goog.dom.getDomHelper(); + + /** + * Whether the component is rendered right-to-left. Right-to-left is set + * lazily when {@link #isRightToLeft} is called the first time, unless it has + * been set by calling {@link #setRightToLeft} explicitly. + * @private {?boolean} + */ + this.rightToLeft_ = goog.ui.Component.defaultRightToLeft_; + + /** + * Unique ID of the component, lazily initialized in {@link + * goog.ui.Component#getId} if needed. This property is strictly private and + * must not be accessed directly outside of this class! + * @private {?string} + */ + this.id_ = null; + + /** + * Whether the component is in the document. + * @private {boolean} + */ + this.inDocument_ = false; + + // TODO(attila): Stop referring to this private field in subclasses. + /** + * The DOM element for the component. + * @private {Element} + */ + this.element_ = null; + + /** + * Event handler. + * TODO(pallosp): rename it to handler_ after all component subclasses in + * inside Google have been cleaned up. + * Code search: http://go/component_code_search + * @private {goog.events.EventHandler|undefined} + */ + this.googUiComponentHandler_ = void 0; + + /** + * Arbitrary data object associated with the component. Such as meta-data. + * @private {*} + */ + this.model_ = null; + + /** + * Parent component to which events will be propagated. This property is + * strictly private and must not be accessed directly outside of this class! + * @private {goog.ui.Component?} + */ + this.parent_ = null; + + /** + * Array of child components. Lazily initialized on first use. Must be kept + * in sync with {@code childIndex_}. This property is strictly private and + * must not be accessed directly outside of this class! + * @private {Array<goog.ui.Component>?} + */ + this.children_ = null; + + /** + * Map of child component IDs to child components. Used for constant-time + * random access to child components by ID. Lazily initialized on first use. + * Must be kept in sync with {@code children_}. This property is strictly + * private and must not be accessed directly outside of this class! + * + * We use a plain Object, not a {@link goog.structs.Map}, for simplicity. + * This means components can't have children with IDs such as 'constructor' or + * 'valueOf', but this shouldn't really be an issue in practice, and if it is, + * we can always fix it later without changing the API. + * + * @private {Object} + */ + this.childIndex_ = null; + + /** + * Flag used to keep track of whether a component decorated an already + * existing element or whether it created the DOM itself. + * + * If an element is decorated, dispose will leave the node in the document. + * It is up to the app to remove the node. + * + * If an element was rendered, dispose will remove the node automatically. + * + * @private {boolean} + */ + this.wasDecorated_ = false; +}; +goog.inherits(goog.ui.Component, goog.events.EventTarget); + + +/** + * @define {boolean} Whether to support calling decorate with an element that is + * not yet in the document. If true, we check if the element is in the + * document, and avoid calling enterDocument if it isn't. If false, we + * maintain legacy behavior (always call enterDocument from decorate). + */ +goog.define('goog.ui.Component.ALLOW_DETACHED_DECORATION', false); + + +/** + * Generator for unique IDs. + * @type {goog.ui.IdGenerator} + * @private + */ +goog.ui.Component.prototype.idGenerator_ = goog.ui.IdGenerator.getInstance(); + + +// TODO(gboyer): See if we can remove this and just check goog.i18n.bidi.IS_RTL. +/** + * @define {number} Defines the default BIDI directionality. + * 0: Unknown. + * 1: Left-to-right. + * -1: Right-to-left. + */ +goog.define('goog.ui.Component.DEFAULT_BIDI_DIR', 0); + + +/** + * The default right to left value. + * @type {?boolean} + * @private + */ +goog.ui.Component.defaultRightToLeft_ = + (goog.ui.Component.DEFAULT_BIDI_DIR == 1) ? + false : + (goog.ui.Component.DEFAULT_BIDI_DIR == -1) ? true : null; + + +/** + * Common events fired by components so that event propagation is useful. Not + * all components are expected to dispatch or listen for all event types. + * Events dispatched before a state transition should be cancelable to prevent + * the corresponding state change. + * @enum {string} + */ +goog.ui.Component.EventType = { + /** Dispatched before the component becomes visible. */ + BEFORE_SHOW: 'beforeshow', + + /** + * Dispatched after the component becomes visible. + * NOTE(bloom): For goog.ui.Container, this actually fires before containers + * are shown. Use goog.ui.Container.EventType.AFTER_SHOW if you want an event + * that fires after a goog.ui.Container is shown. + */ + SHOW: 'show', + + /** Dispatched before the component becomes hidden. */ + HIDE: 'hide', + + /** Dispatched before the component becomes disabled. */ + DISABLE: 'disable', + + /** Dispatched before the component becomes enabled. */ + ENABLE: 'enable', + + /** Dispatched before the component becomes highlighted. */ + HIGHLIGHT: 'highlight', + + /** Dispatched before the component becomes un-highlighted. */ + UNHIGHLIGHT: 'unhighlight', + + /** Dispatched before the component becomes activated. */ + ACTIVATE: 'activate', + + /** Dispatched before the component becomes deactivated. */ + DEACTIVATE: 'deactivate', + + /** Dispatched before the component becomes selected. */ + SELECT: 'select', + + /** Dispatched before the component becomes un-selected. */ + UNSELECT: 'unselect', + + /** Dispatched before a component becomes checked. */ + CHECK: 'check', + + /** Dispatched before a component becomes un-checked. */ + UNCHECK: 'uncheck', + + /** Dispatched before a component becomes focused. */ + FOCUS: 'focus', + + /** Dispatched before a component becomes blurred. */ + BLUR: 'blur', + + /** Dispatched before a component is opened (expanded). */ + OPEN: 'open', + + /** Dispatched before a component is closed (collapsed). */ + CLOSE: 'close', + + /** Dispatched after a component is moused over. */ + ENTER: 'enter', + + /** Dispatched after a component is moused out of. */ + LEAVE: 'leave', + + /** Dispatched after the user activates the component. */ + ACTION: 'action', + + /** Dispatched after the external-facing state of a component is changed. */ + CHANGE: 'change' +}; + + +/** + * Errors thrown by the component. + * @enum {string} + */ +goog.ui.Component.Error = { + /** + * Error when a method is not supported. + */ + NOT_SUPPORTED: 'Method not supported', + + /** + * Error when the given element can not be decorated. + */ + DECORATE_INVALID: 'Invalid element to decorate', + + /** + * Error when the component is already rendered and another render attempt is + * made. + */ + ALREADY_RENDERED: 'Component already rendered', + + /** + * Error when an attempt is made to set the parent of a component in a way + * that would result in an inconsistent object graph. + */ + PARENT_UNABLE_TO_BE_SET: 'Unable to set parent component', + + /** + * Error when an attempt is made to add a child component at an out-of-bounds + * index. We don't support sparse child arrays. + */ + CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds', + + /** + * Error when an attempt is made to remove a child component from a component + * other than its parent. + */ + NOT_OUR_CHILD: 'Child is not in parent component', + + /** + * Error when an operation requiring DOM interaction is made when the + * component is not in the document + */ + NOT_IN_DOCUMENT: 'Operation not supported while component is not in document', + + /** + * Error when an invalid component state is encountered. + */ + STATE_INVALID: 'Invalid component state' +}; + + +/** + * Common component states. Components may have distinct appearance depending + * on what state(s) apply to them. Not all components are expected to support + * all states. + * @enum {number} + */ +goog.ui.Component.State = { + /** + * Union of all supported component states. + */ + ALL: 0xFF, + + /** + * Component is disabled. + * @see goog.ui.Component.EventType.DISABLE + * @see goog.ui.Component.EventType.ENABLE + */ + DISABLED: 0x01, + + /** + * Component is highlighted. + * @see goog.ui.Component.EventType.HIGHLIGHT + * @see goog.ui.Component.EventType.UNHIGHLIGHT + */ + HOVER: 0x02, + + /** + * Component is active (or "pressed"). + * @see goog.ui.Component.EventType.ACTIVATE + * @see goog.ui.Component.EventType.DEACTIVATE + */ + ACTIVE: 0x04, + + /** + * Component is selected. + * @see goog.ui.Component.EventType.SELECT + * @see goog.ui.Component.EventType.UNSELECT + */ + SELECTED: 0x08, + + /** + * Component is checked. + * @see goog.ui.Component.EventType.CHECK + * @see goog.ui.Component.EventType.UNCHECK + */ + CHECKED: 0x10, + + /** + * Component has focus. + * @see goog.ui.Component.EventType.FOCUS + * @see goog.ui.Component.EventType.BLUR + */ + FOCUSED: 0x20, + + /** + * Component is opened (expanded). Applies to tree nodes, menu buttons, + * submenus, zippys (zippies?), etc. + * @see goog.ui.Component.EventType.OPEN + * @see goog.ui.Component.EventType.CLOSE + */ + OPENED: 0x40 +}; + + +/** + * Static helper method; returns the type of event components are expected to + * dispatch when transitioning to or from the given state. + * @param {goog.ui.Component.State} state State to/from which the component + * is transitioning. + * @param {boolean} isEntering Whether the component is entering or leaving the + * state. + * @return {goog.ui.Component.EventType} Event type to dispatch. + */ +goog.ui.Component.getStateTransitionEvent = function(state, isEntering) { + switch (state) { + case goog.ui.Component.State.DISABLED: + return isEntering ? goog.ui.Component.EventType.DISABLE : + goog.ui.Component.EventType.ENABLE; + case goog.ui.Component.State.HOVER: + return isEntering ? goog.ui.Component.EventType.HIGHLIGHT : + goog.ui.Component.EventType.UNHIGHLIGHT; + case goog.ui.Component.State.ACTIVE: + return isEntering ? goog.ui.Component.EventType.ACTIVATE : + goog.ui.Component.EventType.DEACTIVATE; + case goog.ui.Component.State.SELECTED: + return isEntering ? goog.ui.Component.EventType.SELECT : + goog.ui.Component.EventType.UNSELECT; + case goog.ui.Component.State.CHECKED: + return isEntering ? goog.ui.Component.EventType.CHECK : + goog.ui.Component.EventType.UNCHECK; + case goog.ui.Component.State.FOCUSED: + return isEntering ? goog.ui.Component.EventType.FOCUS : + goog.ui.Component.EventType.BLUR; + case goog.ui.Component.State.OPENED: + return isEntering ? goog.ui.Component.EventType.OPEN : + goog.ui.Component.EventType.CLOSE; + default: + // Fall through. + } + + // Invalid state. + throw new Error(goog.ui.Component.Error.STATE_INVALID); +}; + + +/** + * Set the default right-to-left value. This causes all component's created from + * this point forward to have the given value. This is useful for cases where + * a given page is always in one directionality, avoiding unnecessary + * right to left determinations. + * @param {?boolean} rightToLeft Whether the components should be rendered + * right-to-left. Null iff components should determine their directionality. + */ +goog.ui.Component.setDefaultRightToLeft = function(rightToLeft) { + goog.ui.Component.defaultRightToLeft_ = rightToLeft; +}; + + +/** + * Gets the unique ID for the instance of this component. If the instance + * doesn't already have an ID, generates one on the fly. + * @return {string} Unique component ID. + */ +goog.ui.Component.prototype.getId = function() { + return this.id_ || (this.id_ = this.idGenerator_.getNextUniqueId()); +}; + + +/** + * Assigns an ID to this component instance. It is the caller's responsibility + * to guarantee that the ID is unique. If the component is a child of a parent + * component, then the parent component's child index is updated to reflect the + * new ID; this may throw an error if the parent already has a child with an ID + * that conflicts with the new ID. + * @param {string} id Unique component ID. + */ +goog.ui.Component.prototype.setId = function(id) { + if (this.parent_ && this.parent_.childIndex_) { + // Update the parent's child index. + goog.object.remove(this.parent_.childIndex_, this.id_); + goog.object.add(this.parent_.childIndex_, id, this); + } + + // Update the component ID. + this.id_ = id; +}; + + +/** + * Gets the component's element. + * @return {Element} The element for the component. + */ +goog.ui.Component.prototype.getElement = function() { + return this.element_; +}; + + +/** + * Gets the component's element. This differs from getElement in that + * it assumes that the element exists (i.e. the component has been + * rendered/decorated) and will cause an assertion error otherwise (if + * assertion is enabled). + * @return {!Element} The element for the component. + */ +goog.ui.Component.prototype.getElementStrict = function() { + var el = this.element_; + goog.asserts.assert( + el, 'Can not call getElementStrict before rendering/decorating.'); + return el; +}; + + +/** + * Sets the component's root element to the given element. Considered + * protected and final. + * + * This should generally only be called during createDom. Setting the element + * does not actually change which element is rendered, only the element that is + * associated with this UI component. + * + * This should only be used by subclasses and its associated renderers. + * + * @param {Element} element Root element for the component. + */ +goog.ui.Component.prototype.setElementInternal = function(element) { + this.element_ = element; +}; + + +/** + * Returns an array of all the elements in this component's DOM with the + * provided className. + * @param {string} className The name of the class to look for. + * @return {!IArrayLike<!Element>} The items found with the class name provided. + */ +goog.ui.Component.prototype.getElementsByClass = function(className) { + return this.element_ ? + this.dom_.getElementsByClass(className, this.element_) : + []; +}; + + +/** + * Returns the first element in this component's DOM with the provided + * className. + * @param {string} className The name of the class to look for. + * @return {Element} The first item with the class name provided. + */ +goog.ui.Component.prototype.getElementByClass = function(className) { + return this.element_ ? this.dom_.getElementByClass(className, this.element_) : + null; +}; + + +/** + * Similar to {@code getElementByClass} except that it expects the + * element to be present in the dom thus returning a required value. Otherwise, + * will assert. + * @param {string} className The name of the class to look for. + * @return {!Element} The first item with the class name provided. + */ +goog.ui.Component.prototype.getRequiredElementByClass = function(className) { + var el = this.getElementByClass(className); + goog.asserts.assert( + el, 'Expected element in component with class: %s', className); + return el; +}; + + +/** + * Returns the event handler for this component, lazily created the first time + * this method is called. + * @return {!goog.events.EventHandler<T>} Event handler for this component. + * @protected + * @this {T} + * @template T + */ +goog.ui.Component.prototype.getHandler = function() { + // TODO(17988911): templated "this" values currently result in "this" being + // "unknown" in the body of the function. + var self = /** @type {goog.ui.Component} */ (this); + if (!self.googUiComponentHandler_) { + self.googUiComponentHandler_ = new goog.events.EventHandler(self); + } + return self.googUiComponentHandler_; +}; + + +/** + * Sets the parent of this component to use for event bubbling. Throws an error + * if the component already has a parent or if an attempt is made to add a + * component to itself as a child. Callers must use {@code removeChild} + * or {@code removeChildAt} to remove components from their containers before + * calling this method. + * @see goog.ui.Component#removeChild + * @see goog.ui.Component#removeChildAt + * @param {goog.ui.Component} parent The parent component. + */ +goog.ui.Component.prototype.setParent = function(parent) { + if (this == parent) { + // Attempting to add a child to itself is an error. + throw new Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET); + } + + if (parent && this.parent_ && this.id_ && this.parent_.getChild(this.id_) && + this.parent_ != parent) { + // This component is already the child of some parent, so it should be + // removed using removeChild/removeChildAt first. + throw new Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET); + } + + this.parent_ = parent; + goog.ui.Component.superClass_.setParentEventTarget.call(this, parent); +}; + + +/** + * Returns the component's parent, if any. + * @return {goog.ui.Component?} The parent component. + */ +goog.ui.Component.prototype.getParent = function() { + return this.parent_; +}; + + +/** + * Overrides {@link goog.events.EventTarget#setParentEventTarget} to throw an + * error if the parent component is set, and the argument is not the parent. + * @override + */ +goog.ui.Component.prototype.setParentEventTarget = function(parent) { + if (this.parent_ && this.parent_ != parent) { + throw new Error(goog.ui.Component.Error.NOT_SUPPORTED); + } + goog.ui.Component.superClass_.setParentEventTarget.call(this, parent); +}; + + +/** + * Returns the dom helper that is being used on this component. + * @return {!goog.dom.DomHelper} The dom helper used on this component. + */ +goog.ui.Component.prototype.getDomHelper = function() { + return this.dom_; +}; + + +/** + * Determines whether the component has been added to the document. + * @return {boolean} TRUE if rendered. Otherwise, FALSE. + */ +goog.ui.Component.prototype.isInDocument = function() { + return this.inDocument_; +}; + + +/** + * Creates the initial DOM representation for the component. The default + * implementation is to set this.element_ = div. + */ +goog.ui.Component.prototype.createDom = function() { + this.element_ = this.dom_.createElement(goog.dom.TagName.DIV); +}; + + +/** + * Renders the component. If a parent element is supplied, the component's + * element will be appended to it. If there is no optional parent element and + * the element doesn't have a parentNode then it will be appended to the + * document body. + * + * If this component has a parent component, and the parent component is + * not in the document already, then this will not call {@code enterDocument} + * on this component. + * + * Throws an Error if the component is already rendered. + * + * @param {Element=} opt_parentElement Optional parent element to render the + * component into. + */ +goog.ui.Component.prototype.render = function(opt_parentElement) { + this.render_(opt_parentElement); +}; + + +/** + * Renders the component before another element. The other element should be in + * the document already. + * + * Throws an Error if the component is already rendered. + * + * @param {Node} sibling Node to render the component before. + */ +goog.ui.Component.prototype.renderBefore = function(sibling) { + this.render_(/** @type {Element} */ (sibling.parentNode), sibling); +}; + + +/** + * Renders the component. If a parent element is supplied, the component's + * element will be appended to it. If there is no optional parent element and + * the element doesn't have a parentNode then it will be appended to the + * document body. + * + * If this component has a parent component, and the parent component is + * not in the document already, then this will not call {@code enterDocument} + * on this component. + * + * Throws an Error if the component is already rendered. + * + * @param {Element=} opt_parentElement Optional parent element to render the + * component into. + * @param {Node=} opt_beforeNode Node before which the component is to + * be rendered. If left out the node is appended to the parent element. + * @private + */ +goog.ui.Component.prototype.render_ = function( + opt_parentElement, opt_beforeNode) { + if (this.inDocument_) { + throw new Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (!this.element_) { + this.createDom(); + } + + if (opt_parentElement) { + opt_parentElement.insertBefore(this.element_, opt_beforeNode || null); + } else { + this.dom_.getDocument().body.appendChild(this.element_); + } + + // If this component has a parent component that isn't in the document yet, + // we don't call enterDocument() here. Instead, when the parent component + // enters the document, the enterDocument() call will propagate to its + // children, including this one. If the component doesn't have a parent + // or if the parent is already in the document, we call enterDocument(). + if (!this.parent_ || this.parent_.isInDocument()) { + this.enterDocument(); + } +}; + + +/** + * Decorates the element for the UI component. If the element is in the + * document, the enterDocument method will be called. + * + * If goog.ui.Component.ALLOW_DETACHED_DECORATION is false, the caller must + * pass an element that is in the document. + * + * @param {Element} element Element to decorate. + */ +goog.ui.Component.prototype.decorate = function(element) { + if (this.inDocument_) { + throw new Error(goog.ui.Component.Error.ALREADY_RENDERED); + } else if (element && this.canDecorate(element)) { + this.wasDecorated_ = true; + + // Set the DOM helper of the component to match the decorated element. + var doc = goog.dom.getOwnerDocument(element); + if (!this.dom_ || this.dom_.getDocument() != doc) { + this.dom_ = goog.dom.getDomHelper(element); + } + + // Call specific component decorate logic. + this.decorateInternal(element); + + // If supporting detached decoration, check that element is in doc. + if (!goog.ui.Component.ALLOW_DETACHED_DECORATION || + goog.dom.contains(doc, element)) { + this.enterDocument(); + } + } else { + throw new Error(goog.ui.Component.Error.DECORATE_INVALID); + } +}; + + +/** + * Determines if a given element can be decorated by this type of component. + * This method should be overridden by inheriting objects. + * @param {Element} element Element to decorate. + * @return {boolean} True if the element can be decorated, false otherwise. + */ +goog.ui.Component.prototype.canDecorate = function(element) { + return true; +}; + + +/** + * @return {boolean} Whether the component was decorated. + */ +goog.ui.Component.prototype.wasDecorated = function() { + return this.wasDecorated_; +}; + + +/** + * Actually decorates the element. Should be overridden by inheriting objects. + * This method can assume there are checks to ensure the component has not + * already been rendered have occurred and that enter document will be called + * afterwards. This method is considered protected. + * @param {Element} element Element to decorate. + * @protected + */ +goog.ui.Component.prototype.decorateInternal = function(element) { + this.element_ = element; +}; + + +/** + * Called when the component's element is known to be in the document. Anything + * using document.getElementById etc. should be done at this stage. + * + * If the component contains child components, this call is propagated to its + * children. + */ +goog.ui.Component.prototype.enterDocument = function() { + this.inDocument_ = true; + + // Propagate enterDocument to child components that have a DOM, if any. + // If a child was decorated before entering the document (permitted when + // goog.ui.Component.ALLOW_DETACHED_DECORATION is true), its enterDocument + // will be called here. + this.forEachChild(function(child) { + if (!child.isInDocument() && child.getElement()) { + child.enterDocument(); + } + }); +}; + + +/** + * Called by dispose to clean up the elements and listeners created by a + * component, or by a parent component/application who has removed the + * component from the document but wants to reuse it later. + * + * If the component contains child components, this call is propagated to its + * children. + * + * It should be possible for the component to be rendered again once this method + * has been called. + */ +goog.ui.Component.prototype.exitDocument = function() { + // Propagate exitDocument to child components that have been rendered, if any. + this.forEachChild(function(child) { + if (child.isInDocument()) { + child.exitDocument(); + } + }); + + if (this.googUiComponentHandler_) { + this.googUiComponentHandler_.removeAll(); + } + + this.inDocument_ = false; +}; + + +/** + * Disposes of the component. Calls {@code exitDocument}, which is expected to + * remove event handlers and clean up the component. Propagates the call to + * the component's children, if any. Removes the component's DOM from the + * document unless it was decorated. + * @override + * @protected + */ +goog.ui.Component.prototype.disposeInternal = function() { + if (this.inDocument_) { + this.exitDocument(); + } + + if (this.googUiComponentHandler_) { + this.googUiComponentHandler_.dispose(); + delete this.googUiComponentHandler_; + } + + // Disposes of the component's children, if any. + this.forEachChild(function(child) { child.dispose(); }); + + // Detach the component's element from the DOM, unless it was decorated. + if (!this.wasDecorated_ && this.element_) { + goog.dom.removeNode(this.element_); + } + + this.children_ = null; + this.childIndex_ = null; + this.element_ = null; + this.model_ = null; + this.parent_ = null; + + goog.ui.Component.superClass_.disposeInternal.call(this); +}; + + +/** + * Helper function for subclasses that gets a unique id for a given fragment, + * this can be used by components to generate unique string ids for DOM + * elements. + * @param {string} idFragment A partial id. + * @return {string} Unique element id. + */ +goog.ui.Component.prototype.makeId = function(idFragment) { + return this.getId() + '.' + idFragment; +}; + + +/** + * Makes a collection of ids. This is a convenience method for makeId. The + * object's values are the id fragments and the new values are the generated + * ids. The key will remain the same. + * @param {Object} object The object that will be used to create the ids. + * @return {!Object<string, string>} An object of id keys to generated ids. + */ +goog.ui.Component.prototype.makeIds = function(object) { + var ids = {}; + for (var key in object) { + ids[key] = this.makeId(object[key]); + } + return ids; +}; + + +/** + * Returns the model associated with the UI component. + * @return {*} The model. + */ +goog.ui.Component.prototype.getModel = function() { + return this.model_; +}; + + +/** + * Sets the model associated with the UI component. + * @param {*} obj The model. + */ +goog.ui.Component.prototype.setModel = function(obj) { + this.model_ = obj; +}; + + +/** + * Helper function for returning the fragment portion of an id generated using + * makeId(). + * @param {string} id Id generated with makeId(). + * @return {string} Fragment. + */ +goog.ui.Component.prototype.getFragmentFromId = function(id) { + return id.substring(this.getId().length + 1); +}; + + +/** + * Helper function for returning an element in the document with a unique id + * generated using makeId(). + * @param {string} idFragment The partial id. + * @return {Element} The element with the unique id, or null if it cannot be + * found. + */ +goog.ui.Component.prototype.getElementByFragment = function(idFragment) { + if (!this.inDocument_) { + throw new Error(goog.ui.Component.Error.NOT_IN_DOCUMENT); + } + return this.dom_.getElement(this.makeId(idFragment)); +}; + + +/** + * Adds the specified component as the last child of this component. See + * {@link goog.ui.Component#addChildAt} for detailed semantics. + * + * @see goog.ui.Component#addChildAt + * @param {goog.ui.Component} child The new child component. + * @param {boolean=} opt_render If true, the child component will be rendered + * into the parent. + */ +goog.ui.Component.prototype.addChild = function(child, opt_render) { + // TODO(gboyer): addChildAt(child, this.getChildCount(), false) will + // reposition any already-rendered child to the end. Instead, perhaps + // addChild(child, false) should never reposition the child; instead, clients + // that need the repositioning will use addChildAt explicitly. Right now, + // clients can get around this by calling addChild before calling decorate. + this.addChildAt(child, this.getChildCount(), opt_render); +}; + + +/** + * Adds the specified component as a child of this component at the given + * 0-based index. + * + * Both {@code addChild} and {@code addChildAt} assume the following contract + * between parent and child components: + * <ul> + * <li>the child component's element must be a descendant of the parent + * component's element, and + * <li>the DOM state of the child component must be consistent with the DOM + * state of the parent component (see {@code isInDocument}) in the + * steady state -- the exception is to addChildAt(child, i, false) and + * then immediately decorate/render the child. + * </ul> + * + * In particular, {@code parent.addChild(child)} will throw an error if the + * child component is already in the document, but the parent isn't. + * + * Clients of this API may call {@code addChild} and {@code addChildAt} with + * {@code opt_render} set to true. If {@code opt_render} is true, calling these + * methods will automatically render the child component's element into the + * parent component's element. If the parent does not yet have an element, then + * {@code createDom} will automatically be invoked on the parent before + * rendering the child. + * + * Invoking {@code parent.addChild(child, true)} will throw an error if the + * child component is already in the document, regardless of the parent's DOM + * state. + * + * If {@code opt_render} is true and the parent component is not already + * in the document, {@code enterDocument} will not be called on this component + * at this point. + * + * Finally, this method also throws an error if the new child already has a + * different parent, or the given index is out of bounds. + * + * @see goog.ui.Component#addChild + * @param {goog.ui.Component} child The new child component. + * @param {number} index 0-based index at which the new child component is to be + * added; must be between 0 and the current child count (inclusive). + * @param {boolean=} opt_render If true, the child component will be rendered + * into the parent. + * @return {void} Nada. + */ +goog.ui.Component.prototype.addChildAt = function(child, index, opt_render) { + goog.asserts.assert(!!child, 'Provided element must not be null.'); + + if (child.inDocument_ && (opt_render || !this.inDocument_)) { + // Adding a child that's already in the document is an error, except if the + // parent is also in the document and opt_render is false (e.g. decorate()). + throw new Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + + if (index < 0 || index > this.getChildCount()) { + // Allowing sparse child arrays would lead to strange behavior, so we don't. + throw new Error(goog.ui.Component.Error.CHILD_INDEX_OUT_OF_BOUNDS); + } + + // Create the index and the child array on first use. + if (!this.childIndex_ || !this.children_) { + this.childIndex_ = {}; + this.children_ = []; + } + + // Moving child within component, remove old reference. + if (child.getParent() == this) { + goog.object.set(this.childIndex_, child.getId(), child); + goog.array.remove(this.children_, child); + + // Add the child to this component. goog.object.add() throws an error if + // a child with the same ID already exists. + } else { + goog.object.add(this.childIndex_, child.getId(), child); + } + + // Set the parent of the child to this component. This throws an error if + // the child is already contained by another component. + child.setParent(this); + goog.array.insertAt(this.children_, child, index); + + if (child.inDocument_ && this.inDocument_ && child.getParent() == this) { + // Changing the position of an existing child, move the DOM node (if + // necessary). + var contentElement = this.getContentElement(); + var insertBeforeElement = contentElement.childNodes[index] || null; + if (insertBeforeElement != child.getElement()) { + contentElement.insertBefore(child.getElement(), insertBeforeElement); + } + } else if (opt_render) { + // If this (parent) component doesn't have a DOM yet, call createDom now + // to make sure we render the child component's element into the correct + // parent element (otherwise render_ with a null first argument would + // render the child into the document body, which is almost certainly not + // what we want). + if (!this.element_) { + this.createDom(); + } + // Render the child into the parent at the appropriate location. Note that + // getChildAt(index + 1) returns undefined if inserting at the end. + // TODO(attila): We should have a renderer with a renderChildAt API. + var sibling = this.getChildAt(index + 1); + // render_() calls enterDocument() if the parent is already in the document. + child.render_(this.getContentElement(), sibling ? sibling.element_ : null); + } else if ( + this.inDocument_ && !child.inDocument_ && child.element_ && + child.element_.parentNode && + // Under some circumstances, IE8 implicitly creates a Document Fragment + // for detached nodes, so ensure the parent is an Element as it should be. + child.element_.parentNode.nodeType == goog.dom.NodeType.ELEMENT) { + // We don't touch the DOM, but if the parent is in the document, and the + // child element is in the document but not marked as such, then we call + // enterDocument on the child. + // TODO(gboyer): It would be nice to move this condition entirely, but + // there's a large risk of breaking existing applications that manually + // append the child to the DOM and then call addChild. + child.enterDocument(); + } +}; + + +/** + * Returns the DOM element into which child components are to be rendered, + * or null if the component itself hasn't been rendered yet. This default + * implementation returns the component's root element. Subclasses with + * complex DOM structures must override this method. + * @return {Element} Element to contain child elements (null if none). + */ +goog.ui.Component.prototype.getContentElement = function() { + return this.element_; +}; + + +/** + * Returns true if the component is rendered right-to-left, false otherwise. + * The first time this function is invoked, the right-to-left rendering property + * is set if it has not been already. + * @return {boolean} Whether the control is rendered right-to-left. + */ +goog.ui.Component.prototype.isRightToLeft = function() { + if (this.rightToLeft_ == null) { + this.rightToLeft_ = goog.style.isRightToLeft( + this.inDocument_ ? this.element_ : this.dom_.getDocument().body); + } + return this.rightToLeft_; +}; + + +/** + * Set is right-to-left. This function should be used if the component needs + * to know the rendering direction during dom creation (i.e. before + * {@link #enterDocument} is called and is right-to-left is set). + * @param {boolean} rightToLeft Whether the component is rendered + * right-to-left. + */ +goog.ui.Component.prototype.setRightToLeft = function(rightToLeft) { + if (this.inDocument_) { + throw new Error(goog.ui.Component.Error.ALREADY_RENDERED); + } + this.rightToLeft_ = rightToLeft; +}; + + +/** + * Returns true if the component has children. + * @return {boolean} True if the component has children. + */ +goog.ui.Component.prototype.hasChildren = function() { + return !!this.children_ && this.children_.length != 0; +}; + + +/** + * Returns the number of children of this component. + * @return {number} The number of children. + */ +goog.ui.Component.prototype.getChildCount = function() { + return this.children_ ? this.children_.length : 0; +}; + + +/** + * Returns an array containing the IDs of the children of this component, or an + * empty array if the component has no children. + * @return {!Array<string>} Child component IDs. + */ +goog.ui.Component.prototype.getChildIds = function() { + var ids = []; + + // We don't use goog.object.getKeys(this.childIndex_) because we want to + // return the IDs in the correct order as determined by this.children_. + this.forEachChild(function(child) { + // addChild()/addChildAt() guarantee that the child array isn't sparse. + ids.push(child.getId()); + }); + + return ids; +}; + + +/** + * Returns the child with the given ID, or null if no such child exists. + * @param {string} id Child component ID. + * @return {goog.ui.Component?} The child with the given ID; null if none. + */ +goog.ui.Component.prototype.getChild = function(id) { + // Use childIndex_ for O(1) access by ID. + return (this.childIndex_ && id) ? + /** @type {goog.ui.Component} */ ( + goog.object.get(this.childIndex_, id)) || + null : + null; +}; + + +/** + * Returns the child at the given index, or null if the index is out of bounds. + * @param {number} index 0-based index. + * @return {goog.ui.Component?} The child at the given index; null if none. + */ +goog.ui.Component.prototype.getChildAt = function(index) { + // Use children_ for access by index. + return this.children_ ? this.children_[index] || null : null; +}; + + +/** + * Calls the given function on each of this component's children in order. If + * {@code opt_obj} is provided, it will be used as the 'this' object in the + * function when called. The function should take two arguments: the child + * component and its 0-based index. The return value is ignored. + * @param {function(this:T,?,number):?} f The function to call for every + * child component; should take 2 arguments (the child and its index). + * @param {T=} opt_obj Used as the 'this' object in f when called. + * @template T + */ +goog.ui.Component.prototype.forEachChild = function(f, opt_obj) { + if (this.children_) { + goog.array.forEach(this.children_, f, opt_obj); + } +}; + + +/** + * Returns the 0-based index of the given child component, or -1 if no such + * child is found. + * @param {goog.ui.Component?} child The child component. + * @return {number} 0-based index of the child component; -1 if not found. + */ +goog.ui.Component.prototype.indexOfChild = function(child) { + return (this.children_ && child) ? goog.array.indexOf(this.children_, child) : + -1; +}; + + +/** + * Removes the given child from this component, and returns it. Throws an error + * if the argument is invalid or if the specified child isn't found in the + * parent component. The argument can either be a string (interpreted as the + * ID of the child component to remove) or the child component itself. + * + * If {@code opt_unrender} is true, calls {@link goog.ui.component#exitDocument} + * on the removed child, and subsequently detaches the child's DOM from the + * document. Otherwise it is the caller's responsibility to clean up the child + * component's DOM. + * + * @see goog.ui.Component#removeChildAt + * @param {string|goog.ui.Component|null} child The ID of the child to remove, + * or the child component itself. + * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the + * removed child component, and detaches its DOM from the document. + * @return {goog.ui.Component} The removed component, if any. + */ +goog.ui.Component.prototype.removeChild = function(child, opt_unrender) { + if (child) { + // Normalize child to be the object and id to be the ID string. This also + // ensures that the child is really ours. + var id = goog.isString(child) ? child : child.getId(); + child = this.getChild(id); + + if (id && child) { + goog.object.remove(this.childIndex_, id); + goog.array.remove(this.children_, child); + + if (opt_unrender) { + // Remove the child component's DOM from the document. We have to call + // exitDocument first (see documentation). + child.exitDocument(); + if (child.element_) { + goog.dom.removeNode(child.element_); + } + } + + // Child's parent must be set to null after exitDocument is called + // so that the child can unlisten to its parent if required. + child.setParent(null); + } + } + + if (!child) { + throw new Error(goog.ui.Component.Error.NOT_OUR_CHILD); + } + + return /** @type {!goog.ui.Component} */ (child); +}; + + +/** + * Removes the child at the given index from this component, and returns it. + * Throws an error if the argument is out of bounds, or if the specified child + * isn't found in the parent. See {@link goog.ui.Component#removeChild} for + * detailed semantics. + * + * @see goog.ui.Component#removeChild + * @param {number} index 0-based index of the child to remove. + * @param {boolean=} opt_unrender If true, calls {@code exitDocument} on the + * removed child component, and detaches its DOM from the document. + * @return {goog.ui.Component} The removed component, if any. + */ +goog.ui.Component.prototype.removeChildAt = function(index, opt_unrender) { + // removeChild(null) will throw error. + return this.removeChild(this.getChildAt(index), opt_unrender); +}; + + +/** + * Removes every child component attached to this one and returns them. + * + * @see goog.ui.Component#removeChild + * @param {boolean=} opt_unrender If true, calls {@link #exitDocument} on the + * removed child components, and detaches their DOM from the document. + * @return {!Array<goog.ui.Component>} The removed components if any. + */ +goog.ui.Component.prototype.removeChildren = function(opt_unrender) { + var removedChildren = []; + while (this.hasChildren()) { + removedChildren.push(this.removeChildAt(0, opt_unrender)); + } + return removedChildren; +};
diff --git a/third_party/ink/closure/ui/idgenerator.js b/third_party/ink/closure/ui/idgenerator.js new file mode 100644 index 0000000..799dc2b --- /dev/null +++ b/third_party/ink/closure/ui/idgenerator.js
@@ -0,0 +1,48 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generator for unique element IDs. + * + * @author jonp@google.com (Jon Perlow) + */ + +goog.provide('goog.ui.IdGenerator'); + + + +/** + * Creates a new id generator. + * @constructor + * @final + */ +goog.ui.IdGenerator = function() {}; +goog.addSingletonGetter(goog.ui.IdGenerator); + + +/** + * Next unique ID to use + * @type {number} + * @private + */ +goog.ui.IdGenerator.prototype.nextId_ = 0; + + +/** + * Gets the next unique ID. + * @return {string} The next unique identifier. + */ +goog.ui.IdGenerator.prototype.getNextUniqueId = function() { + return ':' + (this.nextId_++).toString(36); +};
diff --git a/third_party/ink/closure/uri/uri.js b/third_party/ink/closure/uri/uri.js new file mode 100644 index 0000000..33bb5ac --- /dev/null +++ b/third_party/ink/closure/uri/uri.js
@@ -0,0 +1,1550 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Class for parsing and formatting URIs. + * + * Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to + * create a new instance of the goog.Uri object from Uri parts. + * + * e.g: <code>var myUri = new goog.Uri(window.location);</code> + * + * Implements RFC 3986 for parsing/formatting URIs. + * http://www.ietf.org/rfc/rfc3986.txt + * + * Some changes have been made to the interface (more like .NETs), though the + * internal representation is now of un-encoded parts, this will change the + * behavior slightly. + * + * @author msamuel@google.com (Mike Samuel) + * @author pupius@google.com (Dan Pupius) - Ported to Closure + * @author jonp@google.com (Jon Perlow) - Optimized for IE6 + * @author micapolos@google.com (Michal Pociecha-Los) - Dot segments removal + */ + +goog.provide('goog.Uri'); +goog.provide('goog.Uri.QueryData'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.string'); +goog.require('goog.structs'); +goog.require('goog.structs.Map'); +goog.require('goog.uri.utils'); +goog.require('goog.uri.utils.ComponentIndex'); +goog.require('goog.uri.utils.StandardQueryParam'); + + + +/** + * This class contains setters and getters for the parts of the URI. + * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part + * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the + * decoded path, <code>/foo bar</code>. + * + * Reserved characters (see RFC 3986 section 2.2) can be present in + * their percent-encoded form in scheme, domain, and path URI components and + * will not be auto-decoded. For example: + * <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will + * return <code>relative/path%2fto/resource</code>. + * + * The constructor accepts an optional unparsed, raw URI string. The parser + * is relaxed, so special characters that aren't escaped but don't cause + * ambiguities will not cause parse failures. + * + * All setters return <code>this</code> and so may be chained, a la + * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>. + * + * @param {*=} opt_uri Optional string URI to parse + * (use goog.Uri.create() to create a URI from parts), or if + * a goog.Uri is passed, a clone is created. + * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore + * the case of the parameter name. + * + * @throws URIError If opt_uri is provided and URI is malformed (that is, + * if decodeURIComponent fails on any of the URI components). + * @constructor + * @struct + */ +goog.Uri = function(opt_uri, opt_ignoreCase) { + /** + * Scheme such as "http". + * @private {string} + */ + this.scheme_ = ''; + + /** + * User credentials in the form "username:password". + * @private {string} + */ + this.userInfo_ = ''; + + /** + * Domain part, e.g. "www.google.com". + * @private {string} + */ + this.domain_ = ''; + + /** + * Port, e.g. 8080. + * @private {?number} + */ + this.port_ = null; + + /** + * Path, e.g. "/tests/img.png". + * @private {string} + */ + this.path_ = ''; + + /** + * The fragment without the #. + * @private {string} + */ + this.fragment_ = ''; + + /** + * Whether or not this Uri should be treated as Read Only. + * @private {boolean} + */ + this.isReadOnly_ = false; + + /** + * Whether or not to ignore case when comparing query params. + * @private {boolean} + */ + this.ignoreCase_ = false; + + /** + * Object representing query data. + * @private {!goog.Uri.QueryData} + */ + this.queryData_; + + // Parse in the uri string + var m; + if (opt_uri instanceof goog.Uri) { + this.ignoreCase_ = + goog.isDef(opt_ignoreCase) ? opt_ignoreCase : opt_uri.getIgnoreCase(); + this.setScheme(opt_uri.getScheme()); + this.setUserInfo(opt_uri.getUserInfo()); + this.setDomain(opt_uri.getDomain()); + this.setPort(opt_uri.getPort()); + this.setPath(opt_uri.getPath()); + this.setQueryData(opt_uri.getQueryData().clone()); + this.setFragment(opt_uri.getFragment()); + } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) { + this.ignoreCase_ = !!opt_ignoreCase; + + // Set the parts -- decoding as we do so. + // COMPATIBILITY NOTE - In IE, unmatched fields may be empty strings, + // whereas in other browsers they will be undefined. + this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true); + this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true); + this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true); + this.setPort(m[goog.uri.utils.ComponentIndex.PORT]); + this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true); + this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true); + this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true); + + } else { + this.ignoreCase_ = !!opt_ignoreCase; + this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_); + } +}; + + +/** + * If true, we preserve the type of query parameters set programmatically. + * + * This means that if you set a parameter to a boolean, and then call + * getParameterValue, you will get a boolean back. + * + * If false, we will coerce parameters to strings, just as they would + * appear in real URIs. + * + * TODO(nicksantos): Remove this once people have time to fix all tests. + * + * @type {boolean} + */ +goog.Uri.preserveParameterTypesCompatibilityFlag = false; + + +/** + * Parameter name added to stop caching. + * @type {string} + */ +goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM; + + +/** + * @return {string} The string form of the url. + * @override + */ +goog.Uri.prototype.toString = function() { + var out = []; + + var scheme = this.getScheme(); + if (scheme) { + out.push( + goog.Uri.encodeSpecialChars_( + scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), + ':'); + } + + var domain = this.getDomain(); + if (domain || scheme == 'file') { + out.push('//'); + + var userInfo = this.getUserInfo(); + if (userInfo) { + out.push( + goog.Uri.encodeSpecialChars_( + userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), + '@'); + } + + out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain))); + + var port = this.getPort(); + if (port != null) { + out.push(':', String(port)); + } + } + + var path = this.getPath(); + if (path) { + if (this.hasDomain() && path.charAt(0) != '/') { + out.push('/'); + } + out.push( + goog.Uri.encodeSpecialChars_( + path, path.charAt(0) == '/' ? goog.Uri.reDisallowedInAbsolutePath_ : + goog.Uri.reDisallowedInRelativePath_, + true)); + } + + var query = this.getEncodedQuery(); + if (query) { + out.push('?', query); + } + + var fragment = this.getFragment(); + if (fragment) { + out.push( + '#', goog.Uri.encodeSpecialChars_( + fragment, goog.Uri.reDisallowedInFragment_)); + } + return out.join(''); +}; + + +/** + * Resolves the given relative URI (a goog.Uri object), using the URI + * represented by this instance as the base URI. + * + * There are several kinds of relative URIs:<br> + * 1. foo - replaces the last part of the path, the whole query and fragment<br> + * 2. /foo - replaces the the path, the query and fragment<br> + * 3. //foo - replaces everything from the domain on. foo is a domain name<br> + * 4. ?foo - replace the query and fragment<br> + * 5. #foo - replace the fragment only + * + * Additionally, if relative URI has a non-empty path, all ".." and "." + * segments will be resolved, as described in RFC 3986. + * + * @param {!goog.Uri} relativeUri The relative URI to resolve. + * @return {!goog.Uri} The resolved URI. + */ +goog.Uri.prototype.resolve = function(relativeUri) { + + var absoluteUri = this.clone(); + + // we satisfy these conditions by looking for the first part of relativeUri + // that is not blank and applying defaults to the rest + + var overridden = relativeUri.hasScheme(); + + if (overridden) { + absoluteUri.setScheme(relativeUri.getScheme()); + } else { + overridden = relativeUri.hasUserInfo(); + } + + if (overridden) { + absoluteUri.setUserInfo(relativeUri.getUserInfo()); + } else { + overridden = relativeUri.hasDomain(); + } + + if (overridden) { + absoluteUri.setDomain(relativeUri.getDomain()); + } else { + overridden = relativeUri.hasPort(); + } + + var path = relativeUri.getPath(); + if (overridden) { + absoluteUri.setPort(relativeUri.getPort()); + } else { + overridden = relativeUri.hasPath(); + if (overridden) { + // resolve path properly + if (path.charAt(0) != '/') { + // path is relative + if (this.hasDomain() && !this.hasPath()) { + // RFC 3986, section 5.2.3, case 1 + path = '/' + path; + } else { + // RFC 3986, section 5.2.3, case 2 + var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/'); + if (lastSlashIndex != -1) { + path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path; + } + } + } + path = goog.Uri.removeDotSegments(path); + } + } + + if (overridden) { + absoluteUri.setPath(path); + } else { + overridden = relativeUri.hasQuery(); + } + + if (overridden) { + absoluteUri.setQueryData(relativeUri.getQueryData().clone()); + } else { + overridden = relativeUri.hasFragment(); + } + + if (overridden) { + absoluteUri.setFragment(relativeUri.getFragment()); + } + + return absoluteUri; +}; + + +/** + * Clones the URI instance. + * @return {!goog.Uri} New instance of the URI object. + */ +goog.Uri.prototype.clone = function() { + return new goog.Uri(this); +}; + + +/** + * @return {string} The encoded scheme/protocol for the URI. + */ +goog.Uri.prototype.getScheme = function() { + return this.scheme_; +}; + + +/** + * Sets the scheme/protocol. + * @throws URIError If opt_decode is true and newScheme is malformed (that is, + * if decodeURIComponent fails). + * @param {string} newScheme New scheme value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setScheme = function(newScheme, opt_decode) { + this.enforceReadOnly(); + this.scheme_ = + opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) : newScheme; + + // remove an : at the end of the scheme so somebody can pass in + // window.location.protocol + if (this.scheme_) { + this.scheme_ = this.scheme_.replace(/:$/, ''); + } + return this; +}; + + +/** + * @return {boolean} Whether the scheme has been set. + */ +goog.Uri.prototype.hasScheme = function() { + return !!this.scheme_; +}; + + +/** + * @return {string} The decoded user info. + */ +goog.Uri.prototype.getUserInfo = function() { + return this.userInfo_; +}; + + +/** + * Sets the userInfo. + * @throws URIError If opt_decode is true and newUserInfo is malformed (that is, + * if decodeURIComponent fails). + * @param {string} newUserInfo New userInfo value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) { + this.enforceReadOnly(); + this.userInfo_ = + opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : newUserInfo; + return this; +}; + + +/** + * @return {boolean} Whether the user info has been set. + */ +goog.Uri.prototype.hasUserInfo = function() { + return !!this.userInfo_; +}; + + +/** + * @return {string} The decoded domain. + */ +goog.Uri.prototype.getDomain = function() { + return this.domain_; +}; + + +/** + * Sets the domain. + * @throws URIError If opt_decode is true and newDomain is malformed (that is, + * if decodeURIComponent fails). + * @param {string} newDomain New domain value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setDomain = function(newDomain, opt_decode) { + this.enforceReadOnly(); + this.domain_ = + opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) : newDomain; + return this; +}; + + +/** + * @return {boolean} Whether the domain has been set. + */ +goog.Uri.prototype.hasDomain = function() { + return !!this.domain_; +}; + + +/** + * @return {?number} The port number. + */ +goog.Uri.prototype.getPort = function() { + return this.port_; +}; + + +/** + * Sets the port number. + * @param {*} newPort Port number. Will be explicitly casted to a number. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setPort = function(newPort) { + this.enforceReadOnly(); + + if (newPort) { + newPort = Number(newPort); + if (isNaN(newPort) || newPort < 0) { + throw new Error('Bad port number ' + newPort); + } + this.port_ = newPort; + } else { + this.port_ = null; + } + + return this; +}; + + +/** + * @return {boolean} Whether the port has been set. + */ +goog.Uri.prototype.hasPort = function() { + return this.port_ != null; +}; + + +/** + * @return {string} The decoded path. + */ +goog.Uri.prototype.getPath = function() { + return this.path_; +}; + + +/** + * Sets the path. + * @throws URIError If opt_decode is true and newPath is malformed (that is, + * if decodeURIComponent fails). + * @param {string} newPath New path value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setPath = function(newPath, opt_decode) { + this.enforceReadOnly(); + this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath; + return this; +}; + + +/** + * @return {boolean} Whether the path has been set. + */ +goog.Uri.prototype.hasPath = function() { + return !!this.path_; +}; + + +/** + * @return {boolean} Whether the query string has been set. + */ +goog.Uri.prototype.hasQuery = function() { + return this.queryData_.toString() !== ''; +}; + + +/** + * Sets the query data. + * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * Applies only if queryData is a string. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setQueryData = function(queryData, opt_decode) { + this.enforceReadOnly(); + + if (queryData instanceof goog.Uri.QueryData) { + this.queryData_ = queryData; + this.queryData_.setIgnoreCase(this.ignoreCase_); + } else { + if (!opt_decode) { + // QueryData accepts encoded query string, so encode it if + // opt_decode flag is not true. + queryData = goog.Uri.encodeSpecialChars_( + queryData, goog.Uri.reDisallowedInQuery_); + } + this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_); + } + + return this; +}; + + +/** + * Sets the URI query. + * @param {string} newQuery New query value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setQuery = function(newQuery, opt_decode) { + return this.setQueryData(newQuery, opt_decode); +}; + + +/** + * @return {string} The encoded URI query, not including the ?. + */ +goog.Uri.prototype.getEncodedQuery = function() { + return this.queryData_.toString(); +}; + + +/** + * @return {string} The decoded URI query, not including the ?. + */ +goog.Uri.prototype.getDecodedQuery = function() { + return this.queryData_.toDecodedString(); +}; + + +/** + * Returns the query data. + * @return {!goog.Uri.QueryData} QueryData object. + */ +goog.Uri.prototype.getQueryData = function() { + return this.queryData_; +}; + + +/** + * @return {string} The encoded URI query, not including the ?. + * + * Warning: This method, unlike other getter methods, returns encoded + * value, instead of decoded one. + */ +goog.Uri.prototype.getQuery = function() { + return this.getEncodedQuery(); +}; + + +/** + * Sets the value of the named query parameters, clearing previous values for + * that key. + * + * @param {string} key The parameter to set. + * @param {*} value The new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setParameterValue = function(key, value) { + this.enforceReadOnly(); + this.queryData_.set(key, value); + return this; +}; + + +/** + * Sets the values of the named query parameters, clearing previous values for + * that key. Not new values will currently be moved to the end of the query + * string. + * + * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new']) + * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p> + * + * @param {string} key The parameter to set. + * @param {*} values The new values. If values is a single + * string then it will be treated as the sole value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setParameterValues = function(key, values) { + this.enforceReadOnly(); + + if (!goog.isArray(values)) { + values = [String(values)]; + } + + this.queryData_.setValues(key, values); + + return this; +}; + + +/** + * Returns the value<b>s</b> for a given cgi parameter as a list of decoded + * query parameter values. + * @param {string} name The parameter to get values for. + * @return {!Array<?>} The values for a given cgi parameter as a list of + * decoded query parameter values. + */ +goog.Uri.prototype.getParameterValues = function(name) { + return this.queryData_.getValues(name); +}; + + +/** + * Returns the first value for a given cgi parameter or undefined if the given + * parameter name does not appear in the query string. + * @param {string} paramName Unescaped parameter name. + * @return {string|undefined} The first value for a given cgi parameter or + * undefined if the given parameter name does not appear in the query + * string. + */ +goog.Uri.prototype.getParameterValue = function(paramName) { + // NOTE(nicksantos): This type-cast is a lie when + // preserveParameterTypesCompatibilityFlag is set to true. + // But this should only be set to true in tests. + return /** @type {string|undefined} */ (this.queryData_.get(paramName)); +}; + + +/** + * @return {string} The URI fragment, not including the #. + */ +goog.Uri.prototype.getFragment = function() { + return this.fragment_; +}; + + +/** + * Sets the URI fragment. + * @throws URIError If opt_decode is true and newFragment is malformed (that is, + * if decodeURIComponent fails). + * @param {string} newFragment New fragment value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setFragment = function(newFragment, opt_decode) { + this.enforceReadOnly(); + this.fragment_ = + opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : newFragment; + return this; +}; + + +/** + * @return {boolean} Whether the URI has a fragment set. + */ +goog.Uri.prototype.hasFragment = function() { + return !!this.fragment_; +}; + + +/** + * Returns true if this has the same domain as that of uri2. + * @param {!goog.Uri} uri2 The URI object to compare to. + * @return {boolean} true if same domain; false otherwise. + */ +goog.Uri.prototype.hasSameDomainAs = function(uri2) { + return ((!this.hasDomain() && !uri2.hasDomain()) || + this.getDomain() == uri2.getDomain()) && + ((!this.hasPort() && !uri2.hasPort()) || + this.getPort() == uri2.getPort()); +}; + + +/** + * Adds a random parameter to the Uri. + * @return {!goog.Uri} Reference to this Uri object. + */ +goog.Uri.prototype.makeUnique = function() { + this.enforceReadOnly(); + this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString()); + + return this; +}; + + +/** + * Removes the named query parameter. + * + * @param {string} key The parameter to remove. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.removeParameter = function(key) { + this.enforceReadOnly(); + this.queryData_.remove(key); + return this; +}; + + +/** + * Sets whether Uri is read only. If this goog.Uri is read-only, + * enforceReadOnly_ will be called at the start of any function that may modify + * this Uri. + * @param {boolean} isReadOnly whether this goog.Uri should be read only. + * @return {!goog.Uri} Reference to this Uri object. + */ +goog.Uri.prototype.setReadOnly = function(isReadOnly) { + this.isReadOnly_ = isReadOnly; + return this; +}; + + +/** + * @return {boolean} Whether the URI is read only. + */ +goog.Uri.prototype.isReadOnly = function() { + return this.isReadOnly_; +}; + + +/** + * Checks if this Uri has been marked as read only, and if so, throws an error. + * This should be called whenever any modifying function is called. + */ +goog.Uri.prototype.enforceReadOnly = function() { + if (this.isReadOnly_) { + throw new Error('Tried to modify a read-only Uri'); + } +}; + + +/** + * Sets whether to ignore case. + * NOTE: If there are already key/value pairs in the QueryData, and + * ignoreCase_ is set to false, the keys will all be lower-cased. + * @param {boolean} ignoreCase whether this goog.Uri should ignore case. + * @return {!goog.Uri} Reference to this Uri object. + */ +goog.Uri.prototype.setIgnoreCase = function(ignoreCase) { + this.ignoreCase_ = ignoreCase; + if (this.queryData_) { + this.queryData_.setIgnoreCase(ignoreCase); + } + return this; +}; + + +/** + * @return {boolean} Whether to ignore case. + */ +goog.Uri.prototype.getIgnoreCase = function() { + return this.ignoreCase_; +}; + + +//============================================================================== +// Static members +//============================================================================== + + +/** + * Creates a uri from the string form. Basically an alias of new goog.Uri(). + * If a Uri object is passed to parse then it will return a clone of the object. + * + * @throws URIError If parsing the URI is malformed. The passed URI components + * should all be parseable by decodeURIComponent. + * @param {*} uri Raw URI string or instance of Uri + * object. + * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter + * names in #getParameterValue. + * @return {!goog.Uri} The new URI object. + */ +goog.Uri.parse = function(uri, opt_ignoreCase) { + return uri instanceof goog.Uri ? uri.clone() : + new goog.Uri(uri, opt_ignoreCase); +}; + + +/** + * Creates a new goog.Uri object from unencoded parts. + * + * @param {?string=} opt_scheme Scheme/protocol or full URI to parse. + * @param {?string=} opt_userInfo username:password. + * @param {?string=} opt_domain www.google.com. + * @param {?number=} opt_port 9830. + * @param {?string=} opt_path /some/path/to/a/file.html. + * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2. + * @param {?string=} opt_fragment The fragment without the #. + * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in + * #getParameterValue. + * + * @return {!goog.Uri} The new URI object. + */ +goog.Uri.create = function( + opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query, + opt_fragment, opt_ignoreCase) { + + var uri = new goog.Uri(null, opt_ignoreCase); + + // Only set the parts if they are defined and not empty strings. + opt_scheme && uri.setScheme(opt_scheme); + opt_userInfo && uri.setUserInfo(opt_userInfo); + opt_domain && uri.setDomain(opt_domain); + opt_port && uri.setPort(opt_port); + opt_path && uri.setPath(opt_path); + opt_query && uri.setQueryData(opt_query); + opt_fragment && uri.setFragment(opt_fragment); + + return uri; +}; + + +/** + * Resolves a relative Uri against a base Uri, accepting both strings and + * Uri objects. + * + * @param {*} base Base Uri. + * @param {*} rel Relative Uri. + * @return {!goog.Uri} Resolved uri. + */ +goog.Uri.resolve = function(base, rel) { + if (!(base instanceof goog.Uri)) { + base = goog.Uri.parse(base); + } + + if (!(rel instanceof goog.Uri)) { + rel = goog.Uri.parse(rel); + } + + return base.resolve(rel); +}; + + +/** + * Removes dot segments in given path component, as described in + * RFC 3986, section 5.2.4. + * + * @param {string} path A non-empty path component. + * @return {string} Path component with removed dot segments. + */ +goog.Uri.removeDotSegments = function(path) { + if (path == '..' || path == '.') { + return ''; + + } else if ( + !goog.string.contains(path, './') && !goog.string.contains(path, '/.')) { + // This optimization detects uris which do not contain dot-segments, + // and as a consequence do not require any processing. + return path; + + } else { + var leadingSlash = goog.string.startsWith(path, '/'); + var segments = path.split('/'); + var out = []; + + for (var pos = 0; pos < segments.length;) { + var segment = segments[pos++]; + + if (segment == '.') { + if (leadingSlash && pos == segments.length) { + out.push(''); + } + } else if (segment == '..') { + if (out.length > 1 || out.length == 1 && out[0] != '') { + out.pop(); + } + if (leadingSlash && pos == segments.length) { + out.push(''); + } + } else { + out.push(segment); + leadingSlash = true; + } + } + + return out.join('/'); + } +}; + + +/** + * Decodes a value or returns the empty string if it isn't defined or empty. + * @throws URIError If decodeURIComponent fails to decode val. + * @param {string|undefined} val Value to decode. + * @param {boolean=} opt_preserveReserved If true, restricted characters will + * not be decoded. + * @return {string} Decoded value. + * @private + */ +goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) { + // Don't use UrlDecode() here because val is not a query parameter. + if (!val) { + return ''; + } + + // decodeURI has the same output for '%2f' and '%252f'. We double encode %25 + // so that we can distinguish between the 2 inputs. This is later undone by + // removeDoubleEncoding_. + return opt_preserveReserved ? decodeURI(val.replace(/%25/g, '%2525')) : + decodeURIComponent(val); +}; + + +/** + * If unescapedPart is non null, then escapes any characters in it that aren't + * valid characters in a url and also escapes any special characters that + * appear in extra. + * + * @param {*} unescapedPart The string to encode. + * @param {RegExp} extra A character set of characters in [\01-\177]. + * @param {boolean=} opt_removeDoubleEncoding If true, remove double percent + * encoding. + * @return {?string} null iff unescapedPart == null. + * @private + */ +goog.Uri.encodeSpecialChars_ = function( + unescapedPart, extra, opt_removeDoubleEncoding) { + if (goog.isString(unescapedPart)) { + var encoded = encodeURI(unescapedPart).replace(extra, goog.Uri.encodeChar_); + if (opt_removeDoubleEncoding) { + // encodeURI double-escapes %XX sequences used to represent restricted + // characters in some URI components, remove the double escaping here. + encoded = goog.Uri.removeDoubleEncoding_(encoded); + } + return encoded; + } + return null; +}; + + +/** + * Converts a character in [\01-\177] to its unicode character equivalent. + * @param {string} ch One character string. + * @return {string} Encoded string. + * @private + */ +goog.Uri.encodeChar_ = function(ch) { + var n = ch.charCodeAt(0); + return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16); +}; + + +/** + * Removes double percent-encoding from a string. + * @param {string} doubleEncodedString String + * @return {string} String with double encoding removed. + * @private + */ +goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) { + return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1'); +}; + + +/** + * Regular expression for characters that are disallowed in the scheme or + * userInfo part of the URI. + * @type {RegExp} + * @private + */ +goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g; + + +/** + * Regular expression for characters that are disallowed in a relative path. + * Colon is included due to RFC 3986 3.3. + * @type {RegExp} + * @private + */ +goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g; + + +/** + * Regular expression for characters that are disallowed in an absolute path. + * @type {RegExp} + * @private + */ +goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g; + + +/** + * Regular expression for characters that are disallowed in the query. + * @type {RegExp} + * @private + */ +goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g; + + +/** + * Regular expression for characters that are disallowed in the fragment. + * @type {RegExp} + * @private + */ +goog.Uri.reDisallowedInFragment_ = /#/g; + + +/** + * Checks whether two URIs have the same domain. + * @param {string} uri1String First URI string. + * @param {string} uri2String Second URI string. + * @return {boolean} true if the two URIs have the same domain; false otherwise. + */ +goog.Uri.haveSameDomain = function(uri1String, uri2String) { + // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme. + // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain. + var pieces1 = goog.uri.utils.split(uri1String); + var pieces2 = goog.uri.utils.split(uri2String); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == + pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && + pieces1[goog.uri.utils.ComponentIndex.PORT] == + pieces2[goog.uri.utils.ComponentIndex.PORT]; +}; + + + +/** + * Class used to represent URI query parameters. It is essentially a hash of + * name-value pairs, though a name can be present more than once. + * + * Has the same interface as the collections in goog.structs. + * + * @param {?string=} opt_query Optional encoded query string to parse into + * the object. + * @param {goog.Uri=} opt_uri Optional uri object that should have its + * cache invalidated when this object updates. Deprecated -- this + * is no longer required. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. + * @constructor + * @struct + * @final + */ +goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) { + /** + * The map containing name/value or name/array-of-values pairs. + * May be null if it requires parsing from the query string. + * + * We need to use a Map because we cannot guarantee that the key names will + * not be problematic for IE. + * + * @private {goog.structs.Map<string, !Array<*>>} + */ + this.keyMap_ = null; + + /** + * The number of params, or null if it requires computing. + * @private {?number} + */ + this.count_ = null; + + /** + * Encoded query string, or null if it requires computing from the key map. + * @private {?string} + */ + this.encodedQuery_ = opt_query || null; + + /** + * If true, ignore the case of the parameter name in #get. + * @private {boolean} + */ + this.ignoreCase_ = !!opt_ignoreCase; +}; + + +/** + * If the underlying key map is not yet initialized, it parses the + * query string and fills the map with parsed data. + * @private + */ +goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() { + if (!this.keyMap_) { + this.keyMap_ = new goog.structs.Map(); + this.count_ = 0; + if (this.encodedQuery_) { + var self = this; + goog.uri.utils.parseQueryData(this.encodedQuery_, function(name, value) { + self.add(goog.string.urlDecode(name), value); + }); + } + } +}; + + +/** + * Creates a new query data instance from a map of names and values. + * + * @param {!goog.structs.Map<string, ?>|!Object} map Map of string parameter + * names to parameter value. If parameter value is an array, it is + * treated as if the key maps to each individual value in the + * array. + * @param {goog.Uri=} opt_uri URI object that should have its cache + * invalidated when this object updates. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. + * @return {!goog.Uri.QueryData} The populated query data instance. + */ +goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) { + var keys = goog.structs.getKeys(map); + if (typeof keys == 'undefined') { + throw new Error('Keys are undefined'); + } + + var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); + var values = goog.structs.getValues(map); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = values[i]; + if (!goog.isArray(value)) { + queryData.add(key, value); + } else { + queryData.setValues(key, value); + } + } + return queryData; +}; + + +/** + * Creates a new query data instance from parallel arrays of parameter names + * and values. Allows for duplicate parameter names. Throws an error if the + * lengths of the arrays differ. + * + * @param {!Array<string>} keys Parameter names. + * @param {!Array<?>} values Parameter values. + * @param {goog.Uri=} opt_uri URI object that should have its cache + * invalidated when this object updates. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. + * @return {!goog.Uri.QueryData} The populated query data instance. + */ +goog.Uri.QueryData.createFromKeysValues = function( + keys, values, opt_uri, opt_ignoreCase) { + if (keys.length != values.length) { + throw new Error('Mismatched lengths for keys/values'); + } + var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); + for (var i = 0; i < keys.length; i++) { + queryData.add(keys[i], values[i]); + } + return queryData; +}; + + +/** + * @return {?number} The number of parameters. + */ +goog.Uri.QueryData.prototype.getCount = function() { + this.ensureKeyMapInitialized_(); + return this.count_; +}; + + +/** + * Adds a key value pair. + * @param {string} key Name. + * @param {*} value Value. + * @return {!goog.Uri.QueryData} Instance of this object. + */ +goog.Uri.QueryData.prototype.add = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + + key = this.getKeyName_(key); + var values = this.keyMap_.get(key); + if (!values) { + this.keyMap_.set(key, (values = [])); + } + values.push(value); + this.count_ = goog.asserts.assertNumber(this.count_) + 1; + return this; +}; + + +/** + * Removes all the params with the given key. + * @param {string} key Name. + * @return {boolean} Whether any parameter was removed. + */ +goog.Uri.QueryData.prototype.remove = function(key) { + this.ensureKeyMapInitialized_(); + + key = this.getKeyName_(key); + if (this.keyMap_.containsKey(key)) { + this.invalidateCache_(); + + // Decrement parameter count. + this.count_ = + goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length; + return this.keyMap_.remove(key); + } + return false; +}; + + +/** + * Clears the parameters. + */ +goog.Uri.QueryData.prototype.clear = function() { + this.invalidateCache_(); + this.keyMap_ = null; + this.count_ = 0; +}; + + +/** + * @return {boolean} Whether we have any parameters. + */ +goog.Uri.QueryData.prototype.isEmpty = function() { + this.ensureKeyMapInitialized_(); + return this.count_ == 0; +}; + + +/** + * Whether there is a parameter with the given name + * @param {string} key The parameter name to check for. + * @return {boolean} Whether there is a parameter with the given name. + */ +goog.Uri.QueryData.prototype.containsKey = function(key) { + this.ensureKeyMapInitialized_(); + key = this.getKeyName_(key); + return this.keyMap_.containsKey(key); +}; + + +/** + * Whether there is a parameter with the given value. + * @param {*} value The value to check for. + * @return {boolean} Whether there is a parameter with the given value. + */ +goog.Uri.QueryData.prototype.containsValue = function(value) { + // NOTE(arv): This solution goes through all the params even if it was the + // first param. We can get around this by not reusing code or by switching to + // iterators. + var vals = this.getValues(); + return goog.array.contains(vals, value); +}; + + +/** + * Runs a callback on every key-value pair in the map, including duplicate keys. + * This won't maintain original order when duplicate keys are interspersed (like + * getKeys() / getValues()). + * @param {function(this:SCOPE, ?, string, !goog.Uri.QueryData)} f + * @param {SCOPE=} opt_scope The value of "this" inside f. + * @template SCOPE + */ +goog.Uri.QueryData.prototype.forEach = function(f, opt_scope) { + this.ensureKeyMapInitialized_(); + this.keyMap_.forEach(function(values, key) { + goog.array.forEach(values, function(value) { + f.call(opt_scope, value, key, this); + }, this); + }, this); +}; + + +/** + * Returns all the keys of the parameters. If a key is used multiple times + * it will be included multiple times in the returned array + * @return {!Array<string>} All the keys of the parameters. + */ +goog.Uri.QueryData.prototype.getKeys = function() { + this.ensureKeyMapInitialized_(); + // We need to get the values to know how many keys to add. + var vals = this.keyMap_.getValues(); + var keys = this.keyMap_.getKeys(); + var rv = []; + for (var i = 0; i < keys.length; i++) { + var val = vals[i]; + for (var j = 0; j < val.length; j++) { + rv.push(keys[i]); + } + } + return rv; +}; + + +/** + * Returns all the values of the parameters with the given name. If the query + * data has no such key this will return an empty array. If no key is given + * all values wil be returned. + * @param {string=} opt_key The name of the parameter to get the values for. + * @return {!Array<?>} All the values of the parameters with the given name. + */ +goog.Uri.QueryData.prototype.getValues = function(opt_key) { + this.ensureKeyMapInitialized_(); + var rv = []; + if (goog.isString(opt_key)) { + if (this.containsKey(opt_key)) { + rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key))); + } + } else { + // Return all values. + var values = this.keyMap_.getValues(); + for (var i = 0; i < values.length; i++) { + rv = goog.array.concat(rv, values[i]); + } + } + return rv; +}; + + +/** + * Sets a key value pair and removes all other keys with the same value. + * + * @param {string} key Name. + * @param {*} value Value. + * @return {!goog.Uri.QueryData} Instance of this object. + */ +goog.Uri.QueryData.prototype.set = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + + // TODO(chrishenry): This could be better written as + // this.remove(key), this.add(key, value), but that would reorder + // the key (since the key is first removed and then added at the + // end) and we would have to fix unit tests that depend on key + // ordering. + key = this.getKeyName_(key); + if (this.containsKey(key)) { + this.count_ = + goog.asserts.assertNumber(this.count_) - this.keyMap_.get(key).length; + } + this.keyMap_.set(key, [value]); + this.count_ = goog.asserts.assertNumber(this.count_) + 1; + return this; +}; + + +/** + * Returns the first value associated with the key. If the query data has no + * such key this will return undefined or the optional default. + * @param {string} key The name of the parameter to get the value for. + * @param {*=} opt_default The default value to return if the query data + * has no such key. + * @return {*} The first string value associated with the key, or opt_default + * if there's no value. + */ +goog.Uri.QueryData.prototype.get = function(key, opt_default) { + var values = key ? this.getValues(key) : []; + if (goog.Uri.preserveParameterTypesCompatibilityFlag) { + return values.length > 0 ? values[0] : opt_default; + } else { + return values.length > 0 ? String(values[0]) : opt_default; + } +}; + + +/** + * Sets the values for a key. If the key already exists, this will + * override all of the existing values that correspond to the key. + * @param {string} key The key to set values for. + * @param {!Array<?>} values The values to set. + */ +goog.Uri.QueryData.prototype.setValues = function(key, values) { + this.remove(key); + + if (values.length > 0) { + this.invalidateCache_(); + this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values)); + this.count_ = goog.asserts.assertNumber(this.count_) + values.length; + } +}; + + +/** + * @return {string} Encoded query string. + * @override + */ +goog.Uri.QueryData.prototype.toString = function() { + if (this.encodedQuery_) { + return this.encodedQuery_; + } + + if (!this.keyMap_) { + return ''; + } + + var sb = []; + + // In the past, we use this.getKeys() and this.getVals(), but that + // generates a lot of allocations as compared to simply iterating + // over the keys. + var keys = this.keyMap_.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var encodedKey = goog.string.urlEncode(key); + var val = this.getValues(key); + for (var j = 0; j < val.length; j++) { + var param = encodedKey; + // Ensure that null and undefined are encoded into the url as + // literal strings. + if (val[j] !== '') { + param += '=' + goog.string.urlEncode(val[j]); + } + sb.push(param); + } + } + + return this.encodedQuery_ = sb.join('&'); +}; + + +/** + * @throws URIError If URI is malformed (that is, if decodeURIComponent fails on + * any of the URI components). + * @return {string} Decoded query string. + */ +goog.Uri.QueryData.prototype.toDecodedString = function() { + return goog.Uri.decodeOrEmpty_(this.toString()); +}; + + +/** + * Invalidate the cache. + * @private + */ +goog.Uri.QueryData.prototype.invalidateCache_ = function() { + this.encodedQuery_ = null; +}; + + +/** + * Removes all keys that are not in the provided list. (Modifies this object.) + * @param {Array<string>} keys The desired keys. + * @return {!goog.Uri.QueryData} a reference to this object. + */ +goog.Uri.QueryData.prototype.filterKeys = function(keys) { + this.ensureKeyMapInitialized_(); + this.keyMap_.forEach(function(value, key) { + if (!goog.array.contains(keys, key)) { + this.remove(key); + } + }, this); + return this; +}; + + +/** + * Clone the query data instance. + * @return {!goog.Uri.QueryData} New instance of the QueryData object. + */ +goog.Uri.QueryData.prototype.clone = function() { + var rv = new goog.Uri.QueryData(); + rv.encodedQuery_ = this.encodedQuery_; + if (this.keyMap_) { + rv.keyMap_ = this.keyMap_.clone(); + rv.count_ = this.count_; + } + return rv; +}; + + +/** + * Helper function to get the key name from a JavaScript object. Converts + * the object to a string, and to lower case if necessary. + * @private + * @param {*} arg The object to get a key name from. + * @return {string} valid key name which can be looked up in #keyMap_. + */ +goog.Uri.QueryData.prototype.getKeyName_ = function(arg) { + var keyName = String(arg); + if (this.ignoreCase_) { + keyName = keyName.toLowerCase(); + } + return keyName; +}; + + +/** + * Ignore case in parameter names. + * NOTE: If there are already key/value pairs in the QueryData, and + * ignoreCase_ is set to false, the keys will all be lower-cased. + * @param {boolean} ignoreCase whether this goog.Uri should ignore case. + */ +goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) { + var resetKeys = ignoreCase && !this.ignoreCase_; + if (resetKeys) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + this.keyMap_.forEach(function(value, key) { + var lowerCase = key.toLowerCase(); + if (key != lowerCase) { + this.remove(key); + this.setValues(lowerCase, value); + } + }, this); + } + this.ignoreCase_ = ignoreCase; +}; + + +/** + * Extends a query data object with another query data or map like object. This + * operates 'in-place', it does not create a new QueryData object. + * + * @param {...(?goog.Uri.QueryData|?goog.structs.Map<?, ?>|?Object)} var_args + * The object from which key value pairs will be copied. + * @suppress {deprecated} Use deprecated goog.structs.forEach to allow different + * types of parameters. + */ +goog.Uri.QueryData.prototype.extend = function(var_args) { + for (var i = 0; i < arguments.length; i++) { + var data = arguments[i]; + goog.structs.forEach( + data, function(value, key) { this.add(key, value); }, this); + } +};
diff --git a/third_party/ink/closure/uri/utils.js b/third_party/ink/closure/uri/utils.js new file mode 100644 index 0000000..3b8917ae --- /dev/null +++ b/third_party/ink/closure/uri/utils.js
@@ -0,0 +1,1103 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple utilities for dealing with URI strings. + * + * This is intended to be a lightweight alternative to constructing goog.Uri + * objects. Whereas goog.Uri adds several kilobytes to the binary regardless + * of how much of its functionality you use, this is designed to be a set of + * mostly-independent utilities so that the compiler includes only what is + * necessary for the task. Estimated savings of porting is 5k pre-gzip and + * 1.5k post-gzip. To ensure the savings remain, future developers should + * avoid adding new functionality to existing functions, but instead create + * new ones and factor out shared code. + * + * Many of these utilities have limited functionality, tailored to common + * cases. The query parameter utilities assume that the parameter keys are + * already encoded, since most keys are compile-time alphanumeric strings. The + * query parameter mutation utilities also do not tolerate fragment identifiers. + * + * By design, these functions can be slower than goog.Uri equivalents. + * Repeated calls to some of functions may be quadratic in behavior for IE, + * although the effect is somewhat limited given the 2kb limit. + * + * One advantage of the limited functionality here is that this approach is + * less sensitive to differences in URI encodings than goog.Uri, since these + * functions operate on strings directly, rather than decoding them and + * then re-encoding. + * + * Uses features of RFC 3986 for parsing/formatting URIs: + * http://www.ietf.org/rfc/rfc3986.txt + * + * @author gboyer@google.com (Garrett Boyer) - The "lightened" design. + * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes. + */ + +goog.provide('goog.uri.utils'); +goog.provide('goog.uri.utils.ComponentIndex'); +goog.provide('goog.uri.utils.QueryArray'); +goog.provide('goog.uri.utils.QueryValue'); +goog.provide('goog.uri.utils.StandardQueryParam'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.string'); + + +/** + * Character codes inlined to avoid object allocations due to charCode. + * @enum {number} + * @private + */ +goog.uri.utils.CharCode_ = { + AMPERSAND: 38, + EQUAL: 61, + HASH: 35, + QUESTION: 63 +}; + + +/** + * Builds a URI string from already-encoded parts. + * + * No encoding is performed. Any component may be omitted as either null or + * undefined. + * + * @param {?string=} opt_scheme The scheme such as 'http'. + * @param {?string=} opt_userInfo The user name before the '@'. + * @param {?string=} opt_domain The domain such as 'www.google.com', already + * URI-encoded. + * @param {(string|number|null)=} opt_port The port number. + * @param {?string=} opt_path The path, already URI-encoded. If it is not + * empty, it must begin with a slash. + * @param {?string=} opt_queryData The URI-encoded query data. + * @param {?string=} opt_fragment The URI-encoded fragment identifier. + * @return {string} The fully combined URI. + */ +goog.uri.utils.buildFromEncodedParts = function( + opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_queryData, + opt_fragment) { + var out = ''; + + if (opt_scheme) { + out += opt_scheme + ':'; + } + + if (opt_domain) { + out += '//'; + + if (opt_userInfo) { + out += opt_userInfo + '@'; + } + + out += opt_domain; + + if (opt_port) { + out += ':' + opt_port; + } + } + + if (opt_path) { + out += opt_path; + } + + if (opt_queryData) { + out += '?' + opt_queryData; + } + + if (opt_fragment) { + out += '#' + opt_fragment; + } + + return out; +}; + + +/** + * A regular expression for breaking a URI into its component parts. + * + * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B + * As the "first-match-wins" algorithm is identical to the "greedy" + * disambiguation method used by POSIX regular expressions, it is natural and + * commonplace to use a regular expression for parsing the potential five + * components of a URI reference. + * + * The following line is the regular expression for breaking-down a + * well-formed URI reference into its components. + * + * <pre> + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + * 12 3 4 5 6 7 8 9 + * </pre> + * + * The numbers in the second line above are only to assist readability; they + * indicate the reference points for each subexpression (i.e., each paired + * parenthesis). We refer to the value matched for subexpression <n> as $<n>. + * For example, matching the above expression to + * <pre> + * http://www.ics.uci.edu/pub/ietf/uri/#Related + * </pre> + * results in the following subexpression matches: + * <pre> + * $1 = http: + * $2 = http + * $3 = //www.ics.uci.edu + * $4 = www.ics.uci.edu + * $5 = /pub/ietf/uri/ + * $6 = <undefined> + * $7 = <undefined> + * $8 = #Related + * $9 = Related + * </pre> + * where <undefined> indicates that the component is not present, as is the + * case for the query component in the above example. Therefore, we can + * determine the value of the five components as + * <pre> + * scheme = $2 + * authority = $4 + * path = $5 + * query = $7 + * fragment = $9 + * </pre> + * + * The regular expression has been modified slightly to expose the + * userInfo, domain, and port separately from the authority. + * The modified version yields + * <pre> + * $1 = http scheme + * $2 = <undefined> userInfo -\ + * $3 = www.ics.uci.edu domain | authority + * $4 = <undefined> port -/ + * $5 = /pub/ietf/uri/ path + * $6 = <undefined> query without ? + * $7 = Related fragment without # + * </pre> + * @type {!RegExp} + * @private + */ +goog.uri.utils.splitRe_ = new RegExp( + '^' + + '(?:' + + '([^:/?#.]+)' + // scheme - ignore special characters + // used by other URL parts such as :, + // ?, /, #, and . + ':)?' + + '(?://' + + '(?:([^/?#]*)@)?' + // userInfo + '([^/#?]*?)' + // domain + '(?::([0-9]+))?' + // port + '(?=[/#?]|$)' + // authority-terminating character + ')?' + + '([^?#]+)?' + // path + '(?:\\?([^#]*))?' + // query + '(?:#([\\s\\S]*))?' + // fragment + '$'); + + +/** + * The index of each URI component in the return value of goog.uri.utils.split. + * @enum {number} + */ +goog.uri.utils.ComponentIndex = { + SCHEME: 1, + USER_INFO: 2, + DOMAIN: 3, + PORT: 4, + PATH: 5, + QUERY_DATA: 6, + FRAGMENT: 7 +}; + + +/** + * Splits a URI into its component parts. + * + * Each component can be accessed via the component indices; for example: + * <pre> + * goog.uri.utils.split(someStr)[goog.uri.utils.ComponentIndex.QUERY_DATA]; + * </pre> + * + * @param {string} uri The URI string to examine. + * @return {!Array<string|undefined>} Each component still URI-encoded. + * Each component that is present will contain the encoded value, whereas + * components that are not present will be undefined or empty, depending + * on the browser's regular expression implementation. Never null, since + * arbitrary strings may still look like path names. + */ +goog.uri.utils.split = function(uri) { + // See @return comment -- never null. + return /** @type {!Array<string|undefined>} */ ( + uri.match(goog.uri.utils.splitRe_)); +}; + + +/** + * @param {?string} uri A possibly null string. + * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986 + * reserved characters will not be removed. + * @return {?string} The string URI-decoded, or null if uri is null. + * @private + */ +goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) { + if (!uri) { + return uri; + } + + return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri); +}; + + +/** + * Gets a URI component by index. + * + * It is preferred to use the getPathEncoded() variety of functions ahead, + * since they are more readable. + * + * @param {goog.uri.utils.ComponentIndex} componentIndex The component index. + * @param {string} uri The URI to examine. + * @return {?string} The still-encoded component, or null if the component + * is not present. + * @private + */ +goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) { + // Convert undefined, null, and empty string into null. + return goog.uri.utils.split(uri)[componentIndex] || null; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The protocol or scheme, or null if none. Does not + * include trailing colons or slashes. + */ +goog.uri.utils.getScheme = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.SCHEME, uri); +}; + + +/** + * Gets the effective scheme for the URL. If the URL is relative then the + * scheme is derived from the page's location. + * @param {string} uri The URI to examine. + * @return {string} The protocol or scheme, always lower case. + */ +goog.uri.utils.getEffectiveScheme = function(uri) { + var scheme = goog.uri.utils.getScheme(uri); + if (!scheme && goog.global.self && goog.global.self.location) { + var protocol = goog.global.self.location.protocol; + scheme = protocol.substr(0, protocol.length - 1); + } + // NOTE: When called from a web worker in Firefox 3.5, location maybe null. + // All other browsers with web workers support self.location from the worker. + return scheme ? scheme.toLowerCase() : ''; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The user name still encoded, or null if none. + */ +goog.uri.utils.getUserInfoEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.USER_INFO, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded user info, or null if none. + */ +goog.uri.utils.getUserInfo = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getUserInfoEncoded(uri)); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The domain name still encoded, or null if none. + */ +goog.uri.utils.getDomainEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.DOMAIN, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded domain, or null if none. + */ +goog.uri.utils.getDomain = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?number} The port number, or null if none. + */ +goog.uri.utils.getPort = function(uri) { + // Coerce to a number. If the result of getComponentByIndex_ is null or + // non-numeric, the number coersion yields NaN. This will then return + // null for all non-numeric cases (though also zero, which isn't a relevant + // port number). + return Number( + goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PORT, uri)) || + null; +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The path still encoded, or null if none. Includes the + * leading slash, if any. + */ +goog.uri.utils.getPathEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PATH, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded path, or null if none. Includes the leading + * slash, if any. + */ +goog.uri.utils.getPath = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The query data still encoded, or null if none. Does not + * include the question mark itself. + */ +goog.uri.utils.getQueryData = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.QUERY_DATA, uri); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The fragment identifier, or null if none. Does not + * include the hash mark itself. + */ +goog.uri.utils.getFragmentEncoded = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? null : uri.substr(hashIndex + 1); +}; + + +/** + * @param {string} uri The URI to examine. + * @param {?string} fragment The encoded fragment identifier, or null if none. + * Does not include the hash mark itself. + * @return {string} The URI with the fragment set. + */ +goog.uri.utils.setFragmentEncoded = function(uri, fragment) { + return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : ''); +}; + + +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded fragment identifier, or null if none. Does + * not include the hash mark. + */ +goog.uri.utils.getFragment = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getFragmentEncoded(uri)); +}; + + +/** + * Extracts everything up to the port of the URI. + * @param {string} uri The URI string. + * @return {string} Everything up to and including the port. + */ +goog.uri.utils.getHost = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + pieces[goog.uri.utils.ComponentIndex.SCHEME], + pieces[goog.uri.utils.ComponentIndex.USER_INFO], + pieces[goog.uri.utils.ComponentIndex.DOMAIN], + pieces[goog.uri.utils.ComponentIndex.PORT]); +}; + + +/** + * Returns the origin for a given URL. + * @param {string} uri The URI string. + * @return {string} Everything up to and including the port. + */ +goog.uri.utils.getOrigin = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + pieces[goog.uri.utils.ComponentIndex.SCHEME], null /* opt_userInfo */, + pieces[goog.uri.utils.ComponentIndex.DOMAIN], + pieces[goog.uri.utils.ComponentIndex.PORT]); +}; + + +/** + * Extracts the path of the URL and everything after. + * @param {string} uri The URI string. + * @return {string} The URI, starting at the path and including the query + * parameters and fragment identifier. + */ +goog.uri.utils.getPathAndAfter = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + null, null, null, null, pieces[goog.uri.utils.ComponentIndex.PATH], + pieces[goog.uri.utils.ComponentIndex.QUERY_DATA], + pieces[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; + + +/** + * Gets the URI with the fragment identifier removed. + * @param {string} uri The URI to examine. + * @return {string} Everything preceding the hash mark. + */ +goog.uri.utils.removeFragment = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? uri : uri.substr(0, hashIndex); +}; + + +/** + * Ensures that two URI's have the exact same domain, scheme, and port. + * + * Unlike the version in goog.Uri, this checks protocol, and therefore is + * suitable for checking against the browser's same-origin policy. + * + * @param {string} uri1 The first URI. + * @param {string} uri2 The second URI. + * @return {boolean} Whether they have the same scheme, domain and port. + */ +goog.uri.utils.haveSameDomain = function(uri1, uri2) { + var pieces1 = goog.uri.utils.split(uri1); + var pieces2 = goog.uri.utils.split(uri2); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == + pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && + pieces1[goog.uri.utils.ComponentIndex.SCHEME] == + pieces2[goog.uri.utils.ComponentIndex.SCHEME] && + pieces1[goog.uri.utils.ComponentIndex.PORT] == + pieces2[goog.uri.utils.ComponentIndex.PORT]; +}; + + +/** + * Asserts that there are no fragment or query identifiers, only in uncompiled + * mode. + * @param {string} uri The URI to examine. + * @private + */ +goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) { + goog.asserts.assert( + uri.indexOf('#') < 0 && uri.indexOf('?') < 0, + 'goog.uri.utils: Fragment or query identifiers are not supported: [%s]', + uri); +}; + + +/** + * Supported query parameter values by the parameter serializing utilities. + * + * If a value is null or undefined, the key-value pair is skipped, as an easy + * way to omit parameters conditionally. Non-array parameters are converted + * to a string and URI encoded. Array values are expanded into multiple + * &key=value pairs, with each element stringized and URI-encoded. + * + * @typedef {*} + */ +goog.uri.utils.QueryValue; + + +/** + * An array representing a set of query parameters with alternating keys + * and values. + * + * Keys are assumed to be URI encoded already and live at even indices. See + * goog.uri.utils.QueryValue for details on how parameter values are encoded. + * + * Example: + * <pre> + * var data = [ + * // Simple param: ?name=BobBarker + * 'name', 'BobBarker', + * // Conditional param -- may be omitted entirely. + * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null, + * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null + * 'house', ['LosAngeles', 'NewYork', null] + * ]; + * </pre> + * + * @typedef {!Array<string|goog.uri.utils.QueryValue>} + */ +goog.uri.utils.QueryArray; + + +/** + * Parses encoded query parameters and calls callback function for every + * parameter found in the string. + * + * Missing value of parameter (e.g. “…&key&…”) is treated as if the value was an + * empty string. Keys may be empty strings (e.g. “…&=value&…”) which also means + * that “…&=&…” and “…&&…” will result in an empty key and value. + * + * @param {string} encodedQuery Encoded query string excluding question mark at + * the beginning. + * @param {function(string, string)} callback Function called for every + * parameter found in query string. The first argument (name) will not be + * urldecoded (so the function is consistent with buildQueryData), but the + * second will. If the parameter has no value (i.e. “=” was not present) + * the second argument (value) will be an empty string. + */ +goog.uri.utils.parseQueryData = function(encodedQuery, callback) { + if (!encodedQuery) { + return; + } + var pairs = encodedQuery.split('&'); + for (var i = 0; i < pairs.length; i++) { + var indexOfEquals = pairs[i].indexOf('='); + var name = null; + var value = null; + if (indexOfEquals >= 0) { + name = pairs[i].substring(0, indexOfEquals); + value = pairs[i].substring(indexOfEquals + 1); + } else { + name = pairs[i]; + } + callback(name, value ? goog.string.urlDecode(value) : ''); + } +}; + + +/** + * Split the URI into 3 parts where the [1] is the queryData without a leading + * '?'. For example, the URI http://foo.com/bar?a=b#abc returns + * ['http://foo.com/bar','a=b','#abc']. + * @param {string} uri The URI to parse. + * @return {!Array<string>} An array representation of uri of length 3 where the + * middle value is the queryData without a leading '?'. + * @private + */ +goog.uri.utils.splitQueryData_ = function(uri) { + // Find the query data and and hash. + var hashIndex = uri.indexOf('#'); + if (hashIndex < 0) { + hashIndex = uri.length; + } + var questionIndex = uri.indexOf('?'); + var queryData; + if (questionIndex < 0 || questionIndex > hashIndex) { + questionIndex = hashIndex; + queryData = ''; + } else { + queryData = uri.substring(questionIndex + 1, hashIndex); + } + return [uri.substr(0, questionIndex), queryData, uri.substr(hashIndex)]; +}; + + +/** + * Join an array created by splitQueryData_ back into a URI. + * @param {!Array<string>} parts A URI in the form generated by splitQueryData_. + * @return {string} The joined URI. + * @private + */ +goog.uri.utils.joinQueryData_ = function(parts) { + return parts[0] + (parts[1] ? '?' + parts[1] : '') + parts[2]; +}; + + +/** + * @param {string} queryData + * @param {string} newData + * @return {string} + * @private + */ +goog.uri.utils.appendQueryData_ = function(queryData, newData) { + if (!newData) { + return queryData; + } + return queryData ? queryData + '&' + newData : newData; +}; + + +/** + * @param {string} uri + * @param {string} queryData + * @return {string} + * @private + */ +goog.uri.utils.appendQueryDataToUri_ = function(uri, queryData) { + if (!queryData) { + return uri; + } + var parts = goog.uri.utils.splitQueryData_(uri); + parts[1] = goog.uri.utils.appendQueryData_(parts[1], queryData); + return goog.uri.utils.joinQueryData_(parts); +}; + + +/** + * Appends key=value pairs to an array, supporting multi-valued objects. + * @param {*} key The key prefix. + * @param {goog.uri.utils.QueryValue} value The value to serialize. + * @param {!Array<string>} pairs The array to which the 'key=value' strings + * should be appended. + * @private + */ +goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) { + goog.asserts.assertString(key); + if (goog.isArray(value)) { + // Convince the compiler it's an array. + goog.asserts.assertArray(value); + for (var j = 0; j < value.length; j++) { + // Convert to string explicitly, to short circuit the null and array + // logic in this function -- this ensures that null and undefined get + // written as literal 'null' and 'undefined', and arrays don't get + // expanded out but instead encoded in the default way. + goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs); + } + } else if (value != null) { + // Skip a top-level null or undefined entirely. + pairs.push( + key + + // Check for empty string. Zero gets encoded into the url as literal + // strings. For empty string, skip the equal sign, to be consistent + // with UriBuilder.java. + (value === '' ? '' : '=' + goog.string.urlEncode(value))); + } +}; + + +/** + * Builds a query data string from a sequence of alternating keys and values. + * Currently generates "&key&" for empty args. + * + * @param {!IArrayLike<string|goog.uri.utils.QueryValue>} keysAndValues + * Alternating keys and values. See the QueryArray typedef. + * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. + * @return {string} The encoded query string, in the form 'a=1&b=2'. + */ +goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) { + goog.asserts.assert( + Math.max(keysAndValues.length - (opt_startIndex || 0), 0) % 2 == 0, + 'goog.uri.utils: Key/value lists must be even in length.'); + + var params = []; + for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) { + var key = /** @type {string} */ (keysAndValues[i]); + goog.uri.utils.appendKeyValuePairs_(key, keysAndValues[i + 1], params); + } + return params.join('&'); +}; + + +/** + * Builds a query data string from a map. + * Currently generates "&key&" for empty args. + * + * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys + * are URI-encoded parameter keys, and the values are arbitrary types + * or arrays. Keys with a null value are dropped. + * @return {string} The encoded query string, in the form 'a=1&b=2'. + */ +goog.uri.utils.buildQueryDataFromMap = function(map) { + var params = []; + for (var key in map) { + goog.uri.utils.appendKeyValuePairs_(key, map[key], params); + } + return params.join('&'); +}; + + +/** + * Appends URI parameters to an existing URI. + * + * The variable arguments may contain alternating keys and values. Keys are + * assumed to be already URI encoded. The values should not be URI-encoded, + * and will instead be encoded by this function. + * <pre> + * appendParams('http://www.foo.com?existing=true', + * 'key1', 'value1', + * 'key2', 'value?willBeEncoded', + * 'key3', ['valueA', 'valueB', 'valueC'], + * 'key4', null); + * result: 'http://www.foo.com?existing=true&' + + * 'key1=value1&' + + * 'key2=value%3FwillBeEncoded&' + + * 'key3=valueA&key3=valueB&key3=valueC' + * </pre> + * + * A single call to this function will not exhibit quadratic behavior in IE, + * whereas multiple repeated calls may, although the effect is limited by + * fact that URL's generally can't exceed 2kb. + * + * @param {string} uri The original URI, which may already have query data. + * @param {...(goog.uri.utils.QueryArray|goog.uri.utils.QueryValue)} + * var_args + * An array or argument list conforming to goog.uri.utils.QueryArray. + * @return {string} The URI with all query parameters added. + */ +goog.uri.utils.appendParams = function(uri, var_args) { + var queryData = arguments.length == 2 ? + goog.uri.utils.buildQueryData(arguments[1], 0) : + goog.uri.utils.buildQueryData(arguments, 1); + return goog.uri.utils.appendQueryDataToUri_(uri, queryData); +}; + + +/** + * Appends query parameters from a map. + * + * @param {string} uri The original URI, which may already have query data. + * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are + * URI-encoded parameter keys, and the values are arbitrary types or arrays. + * Keys with a null value are dropped. + * @return {string} The new parameters. + */ +goog.uri.utils.appendParamsFromMap = function(uri, map) { + var queryData = goog.uri.utils.buildQueryDataFromMap(map); + return goog.uri.utils.appendQueryDataToUri_(uri, queryData); +}; + + +/** + * Appends a single URI parameter. + * + * Repeated calls to this can exhibit quadratic behavior in IE6 due to the + * way string append works, though it should be limited given the 2kb limit. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} key The key, which must already be URI encoded. + * @param {*=} opt_value The value, which will be stringized and encoded + * (assumed not already to be encoded). If omitted, undefined, or null, the + * key will be added as a valueless parameter. + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.appendParam = function(uri, key, opt_value) { + var value = goog.isDefAndNotNull(opt_value) ? + '=' + goog.string.urlEncode(opt_value) : + ''; + return goog.uri.utils.appendQueryDataToUri_(uri, key + value); +}; + + +/** + * Finds the next instance of a query parameter with the specified name. + * + * Does not instantiate any objects. + * + * @param {string} uri The URI to search. May contain a fragment identifier + * if opt_hashIndex is specified. + * @param {number} startIndex The index to begin searching for the key at. A + * match may be found even if this is one character after the ampersand. + * @param {string} keyEncoded The URI-encoded key. + * @param {number} hashOrEndIndex Index to stop looking at. If a hash + * mark is present, it should be its index, otherwise it should be the + * length of the string. + * @return {number} The position of the first character in the key's name, + * immediately after either a question mark or a dot. + * @private + */ +goog.uri.utils.findParam_ = function( + uri, startIndex, keyEncoded, hashOrEndIndex) { + var index = startIndex; + var keyLength = keyEncoded.length; + + // Search for the key itself and post-filter for surronuding punctuation, + // rather than expensively building a regexp. + while ((index = uri.indexOf(keyEncoded, index)) >= 0 && + index < hashOrEndIndex) { + var precedingChar = uri.charCodeAt(index - 1); + // Ensure that the preceding character is '&' or '?'. + if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND || + precedingChar == goog.uri.utils.CharCode_.QUESTION) { + // Ensure the following character is '&', '=', '#', or NaN + // (end of string). + var followingChar = uri.charCodeAt(index + keyLength); + if (!followingChar || followingChar == goog.uri.utils.CharCode_.EQUAL || + followingChar == goog.uri.utils.CharCode_.AMPERSAND || + followingChar == goog.uri.utils.CharCode_.HASH) { + return index; + } + } + index += keyLength + 1; + } + + return -1; +}; + + +/** + * Regular expression for finding a hash mark or end of string. + * @type {RegExp} + * @private + */ +goog.uri.utils.hashOrEndRe_ = /#|$/; + + +/** + * Determines if the URI contains a specific key. + * + * Performs no object instantiations. + * + * @param {string} uri The URI to process. May contain a fragment + * identifier. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {boolean} Whether the key is present. + */ +goog.uri.utils.hasParam = function(uri, keyEncoded) { + return goog.uri.utils.findParam_( + uri, 0, keyEncoded, uri.search(goog.uri.utils.hashOrEndRe_)) >= 0; +}; + + +/** + * Gets the first value of a query parameter. + * @param {string} uri The URI to process. May contain a fragment. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {?string} The first value of the parameter (URI-decoded), or null + * if the parameter is not found. + */ +goog.uri.utils.getParamValue = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var foundIndex = + goog.uri.utils.findParam_(uri, 0, keyEncoded, hashOrEndIndex); + + if (foundIndex < 0) { + return null; + } else { + var endPosition = uri.indexOf('&', foundIndex); + if (endPosition < 0 || endPosition > hashOrEndIndex) { + endPosition = hashOrEndIndex; + } + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > endPosition. + return goog.string.urlDecode( + uri.substr(foundIndex, endPosition - foundIndex)); + } +}; + + +/** + * Gets all values of a query parameter. + * @param {string} uri The URI to process. May contain a fragment. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {!Array<string>} All URI-decoded values with the given key. + * If the key is not found, this will have length 0, but never be null. + */ +goog.uri.utils.getParamValues = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var result = []; + + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Find where this parameter ends, either the '&' or the end of the + // query parameters. + position = uri.indexOf('&', foundIndex); + if (position < 0 || position > hashOrEndIndex) { + position = hashOrEndIndex; + } + + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > position. + result.push( + goog.string.urlDecode(uri.substr(foundIndex, position - foundIndex))); + } + + return result; +}; + + +/** + * Regexp to find trailing question marks and ampersands. + * @type {RegExp} + * @private + */ +goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/; + + +/** + * Removes all instances of a query parameter. + * @param {string} uri The URI to process. Must not contain a fragment. + * @param {string} keyEncoded The URI-encoded key. + * @return {string} The URI with all instances of the parameter removed. + */ +goog.uri.utils.removeParam = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var buffer = []; + + // Look for a query parameter. + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Get the portion of the query string up to, but not including, the ? + // or & starting the parameter. + buffer.push(uri.substring(position, foundIndex)); + // Progress to immediately after the '&'. If not found, go to the end. + // Avoid including the hash mark. + position = Math.min( + (uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex, hashOrEndIndex); + } + + // Append everything that is remaining. + buffer.push(uri.substr(position)); + + // Join the buffer, and remove trailing punctuation that remains. + return buffer.join('').replace( + goog.uri.utils.trailingQueryPunctuationRe_, '$1'); +}; + + +/** + * Replaces all existing definitions of a parameter with a single definition. + * + * Repeated calls to this can exhibit quadratic behavior due to the need to + * find existing instances and reconstruct the string, though it should be + * limited given the 2kb limit. Consider using appendParams or setParamsFromMap + * to update multiple parameters in bulk. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} keyEncoded The key, which must already be URI encoded. + * @param {*} value The value, which will be stringized and encoded (assumed + * not already to be encoded). + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.setParam = function(uri, keyEncoded, value) { + return goog.uri.utils.appendParam( + goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value); +}; + + +/** + * Effeciently set or remove multiple query parameters in a URI. Order of + * unchanged parameters will not be modified, all updated parameters will be + * appended to the end of the query. Params with values of null or undefined are + * removed. + * + * @param {string} uri The URI to process. + * @param {!Object<string, goog.uri.utils.QueryValue>} params A list of + * parameters to update. If null or undefined, the param will be removed. + * @return {string} An updated URI where the query data has been updated with + * the params. + */ +goog.uri.utils.setParamsFromMap = function(uri, params) { + var parts = goog.uri.utils.splitQueryData_(uri); + var queryData = parts[1]; + var buffer = []; + if (queryData) { + goog.array.forEach(queryData.split('&'), function(pair) { + var indexOfEquals = pair.indexOf('='); + var name = indexOfEquals >= 0 ? pair.substr(0, indexOfEquals) : pair; + if (!params.hasOwnProperty(name)) { + buffer.push(pair); + } + }); + } + parts[1] = goog.uri.utils.appendQueryData_( + buffer.join('&'), goog.uri.utils.buildQueryDataFromMap(params)); + return goog.uri.utils.joinQueryData_(parts); +}; + + +/** + * Generates a URI path using a given URI and a path with checks to + * prevent consecutive "//". The baseUri passed in must not contain + * query or fragment identifiers. The path to append may not contain query or + * fragment identifiers. + * + * @param {string} baseUri URI to use as the base. + * @param {string} path Path to append. + * @return {string} Updated URI. + */ +goog.uri.utils.appendPath = function(baseUri, path) { + goog.uri.utils.assertNoFragmentsOrQueries_(baseUri); + + // Remove any trailing '/' + if (goog.string.endsWith(baseUri, '/')) { + baseUri = baseUri.substr(0, baseUri.length - 1); + } + // Remove any leading '/' + if (goog.string.startsWith(path, '/')) { + path = path.substr(1); + } + return goog.string.buildString(baseUri, '/', path); +}; + + +/** + * Replaces the path. + * @param {string} uri URI to use as the base. + * @param {string} path New path. + * @return {string} Updated URI. + */ +goog.uri.utils.setPath = function(uri, path) { + // Add any missing '/'. + if (!goog.string.startsWith(path, '/')) { + path = '/' + path; + } + var parts = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + parts[goog.uri.utils.ComponentIndex.SCHEME], + parts[goog.uri.utils.ComponentIndex.USER_INFO], + parts[goog.uri.utils.ComponentIndex.DOMAIN], + parts[goog.uri.utils.ComponentIndex.PORT], path, + parts[goog.uri.utils.ComponentIndex.QUERY_DATA], + parts[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; + + +/** + * Standard supported query parameters. + * @enum {string} + */ +goog.uri.utils.StandardQueryParam = { + + /** Unused parameter for unique-ifying. */ + RANDOM: 'zx' +}; + + +/** + * Sets the zx parameter of a URI to a random value. + * @param {string} uri Any URI. + * @return {string} That URI with the "zx" parameter added or replaced to + * contain a random string. + */ +goog.uri.utils.makeUnique = function(uri) { + return goog.uri.utils.setParam( + uri, goog.uri.utils.StandardQueryParam.RANDOM, + goog.string.getRandomString()); +};
diff --git a/third_party/ink/closure/useragent/product.js b/third_party/ink/closure/useragent/product.js new file mode 100644 index 0000000..cc85e63 --- /dev/null +++ b/third_party/ink/closure/useragent/product.js
@@ -0,0 +1,182 @@ +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Detects the specific browser and not just the rendering engine. + * + * @author andybons@google.com (Andrew Bonventre) + */ + +goog.provide('goog.userAgent.product'); + +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.labs.userAgent.platform'); +goog.require('goog.userAgent'); + + +/** + * @define {boolean} Whether the code is running on the Firefox web browser. + */ +goog.define('goog.userAgent.product.ASSUME_FIREFOX', false); + + +/** + * @define {boolean} Whether we know at compile-time that the product is an + * iPhone. + */ +goog.define('goog.userAgent.product.ASSUME_IPHONE', false); + + +/** + * @define {boolean} Whether we know at compile-time that the product is an + * iPad. + */ +goog.define('goog.userAgent.product.ASSUME_IPAD', false); + + +/** + * @define {boolean} Whether we know at compile-time that the product is an + * AOSP browser or WebView inside a pre KitKat Android phone or tablet. + */ +goog.define('goog.userAgent.product.ASSUME_ANDROID', false); + + +/** + * @define {boolean} Whether the code is running on the Chrome web browser on + * any platform or AOSP browser or WebView in a KitKat+ Android phone or tablet. + */ +goog.define('goog.userAgent.product.ASSUME_CHROME', false); + + +/** + * @define {boolean} Whether the code is running on the Safari web browser. + */ +goog.define('goog.userAgent.product.ASSUME_SAFARI', false); + + +/** + * Whether we know the product type at compile-time. + * @type {boolean} + * @private + */ +goog.userAgent.product.PRODUCT_KNOWN_ = goog.userAgent.ASSUME_IE || + goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_OPERA || + goog.userAgent.product.ASSUME_FIREFOX || + goog.userAgent.product.ASSUME_IPHONE || + goog.userAgent.product.ASSUME_IPAD || + goog.userAgent.product.ASSUME_ANDROID || + goog.userAgent.product.ASSUME_CHROME || + goog.userAgent.product.ASSUME_SAFARI; + + +/** + * Whether the code is running on the Opera web browser. + * @type {boolean} + */ +goog.userAgent.product.OPERA = goog.userAgent.OPERA; + + +/** + * Whether the code is running on an IE web browser. + * @type {boolean} + */ +goog.userAgent.product.IE = goog.userAgent.IE; + + +/** + * Whether the code is running on an Edge web browser. + * @type {boolean} + */ +goog.userAgent.product.EDGE = goog.userAgent.EDGE; + + +/** + * Whether the code is running on the Firefox web browser. + * @type {boolean} + */ +goog.userAgent.product.FIREFOX = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_FIREFOX : + goog.labs.userAgent.browser.isFirefox(); + + +/** + * Whether the user agent is an iPhone or iPod (as in iPod touch). + * @return {boolean} + * @private + */ +goog.userAgent.product.isIphoneOrIpod_ = function() { + return goog.labs.userAgent.platform.isIphone() || + goog.labs.userAgent.platform.isIpod(); +}; + + +/** + * Whether the code is running on an iPhone or iPod touch. + * + * iPod touch is considered an iPhone for legacy reasons. + * @type {boolean} + */ +goog.userAgent.product.IPHONE = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_IPHONE : + goog.userAgent.product.isIphoneOrIpod_(); + + +/** + * Whether the code is running on an iPad. + * @type {boolean} + */ +goog.userAgent.product.IPAD = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_IPAD : + goog.labs.userAgent.platform.isIpad(); + + +/** + * Whether the code is running on AOSP browser or WebView inside + * a pre KitKat Android phone or tablet. + * @type {boolean} + */ +goog.userAgent.product.ANDROID = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_ANDROID : + goog.labs.userAgent.browser.isAndroidBrowser(); + + +/** + * Whether the code is running on the Chrome web browser on any platform + * or AOSP browser or WebView in a KitKat+ Android phone or tablet. + * @type {boolean} + */ +goog.userAgent.product.CHROME = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_CHROME : + goog.labs.userAgent.browser.isChrome(); + + +/** + * @return {boolean} Whether the browser is Safari on desktop. + * @private + */ +goog.userAgent.product.isSafariDesktop_ = function() { + return goog.labs.userAgent.browser.isSafari() && + !goog.labs.userAgent.platform.isIos(); +}; + + +/** + * Whether the code is running on the desktop Safari web browser. + * Note: the legacy behavior here is only true for Safari not running + * on iOS. + * @type {boolean} + */ +goog.userAgent.product.SAFARI = goog.userAgent.product.PRODUCT_KNOWN_ ? + goog.userAgent.product.ASSUME_SAFARI : + goog.userAgent.product.isSafariDesktop_();
diff --git a/third_party/ink/closure/useragent/useragent.js b/third_party/ink/closure/useragent/useragent.js new file mode 100644 index 0000000..007d571 --- /dev/null +++ b/third_party/ink/closure/useragent/useragent.js
@@ -0,0 +1,581 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Rendering engine detection. + * @see <a href="http://www.useragentstring.com/">User agent strings</a> + * For information on the browser brand (such as Safari versus Chrome), see + * goog.userAgent.product. + * @author pupius@google.com (Daniel Pupius) + * @author arv@google.com (Erik Arvidsson) + * @see ../demos/useragent.html + */ + +goog.provide('goog.userAgent'); + +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.labs.userAgent.engine'); +goog.require('goog.labs.userAgent.platform'); +goog.require('goog.labs.userAgent.util'); +goog.require('goog.reflect'); +goog.require('goog.string'); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is IE. + */ +goog.define('goog.userAgent.ASSUME_IE', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is EDGE. + */ +goog.define('goog.userAgent.ASSUME_EDGE', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is GECKO. + */ +goog.define('goog.userAgent.ASSUME_GECKO', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is WEBKIT. + */ +goog.define('goog.userAgent.ASSUME_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is a + * mobile device running WebKit e.g. iPhone or Android. + */ +goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is OPERA. + */ +goog.define('goog.userAgent.ASSUME_OPERA', false); + + +/** + * @define {boolean} Whether the + * {@code goog.userAgent.isVersionOrHigher} + * function will return true for any version. + */ +goog.define('goog.userAgent.ASSUME_ANY_VERSION', false); + + +/** + * Whether we know the browser engine at compile-time. + * @type {boolean} + * @private + */ +goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE || + goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_GECKO || + goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT || + goog.userAgent.ASSUME_OPERA; + + +/** + * Returns the userAgent string for the current browser. + * + * @return {string} The userAgent string. + */ +goog.userAgent.getUserAgentString = function() { + return goog.labs.userAgent.util.getUserAgent(); +}; + + +/** + * TODO(nnaze): Change type to "Navigator" and update compilation targets. + * @return {?Object} The native navigator object. + */ +goog.userAgent.getNavigator = function() { + // Need a local navigator reference instead of using the global one, + // to avoid the rare case where they reference different objects. + // (in a WorkerPool, for example). + return goog.global['navigator'] || null; +}; + + +/** + * Whether the user agent is Opera. + * @type {boolean} + */ +goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_OPERA : + goog.labs.userAgent.browser.isOpera(); + + +/** + * Whether the user agent is Internet Explorer. + * @type {boolean} + */ +goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_IE : + goog.labs.userAgent.browser.isIE(); + + +/** + * Whether the user agent is Microsoft Edge. + * @type {boolean} + */ +goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_EDGE : + goog.labs.userAgent.engine.isEdge(); + + +/** + * Whether the user agent is MS Internet Explorer or MS Edge. + * @type {boolean} + */ +goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE; + + +/** + * Whether the user agent is Gecko. Gecko is the rendering engine used by + * Mozilla, Firefox, and others. + * @type {boolean} + */ +goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_GECKO : + goog.labs.userAgent.engine.isGecko(); + + +/** + * Whether the user agent is WebKit. WebKit is the rendering engine that + * Safari, Android and others use. + * @type {boolean} + */ +goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : + goog.labs.userAgent.engine.isWebKit(); + + +/** + * Whether the user agent is running on a mobile device. + * + * This is a separate function so that the logic can be tested. + * + * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile(). + * + * @return {boolean} Whether the user agent is running on a mobile device. + * @private + */ +goog.userAgent.isMobile_ = function() { + return goog.userAgent.WEBKIT && + goog.labs.userAgent.util.matchUserAgent('Mobile'); +}; + + +/** + * Whether the user agent is running on a mobile device. + * + * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent + * is promoted as the gecko/webkit logic is likely inaccurate. + * + * @type {boolean} + */ +goog.userAgent.MOBILE = + goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.isMobile_(); + + +/** + * Used while transitioning code to use WEBKIT instead. + * @type {boolean} + * @deprecated Use {@link goog.userAgent.product.SAFARI} instead. + * TODO(nicksantos): Delete this from goog.userAgent. + */ +goog.userAgent.SAFARI = goog.userAgent.WEBKIT; + + +/** + * @return {string} the platform (operating system) the user agent is running + * on. Default to empty string because navigator.platform may not be defined + * (on Rhino, for example). + * @private + */ +goog.userAgent.determinePlatform_ = function() { + var navigator = goog.userAgent.getNavigator(); + return navigator && navigator.platform || ''; +}; + + +/** + * The platform (operating system) the user agent is running on. Default to + * empty string because navigator.platform may not be defined (on Rhino, for + * example). + * @type {string} + */ +goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); + + +/** + * @define {boolean} Whether the user agent is running on a Macintosh operating + * system. + */ +goog.define('goog.userAgent.ASSUME_MAC', false); + + +/** + * @define {boolean} Whether the user agent is running on a Windows operating + * system. + */ +goog.define('goog.userAgent.ASSUME_WINDOWS', false); + + +/** + * @define {boolean} Whether the user agent is running on a Linux operating + * system. + */ +goog.define('goog.userAgent.ASSUME_LINUX', false); + + +/** + * @define {boolean} Whether the user agent is running on a X11 windowing + * system. + */ +goog.define('goog.userAgent.ASSUME_X11', false); + + +/** + * @define {boolean} Whether the user agent is running on Android. + */ +goog.define('goog.userAgent.ASSUME_ANDROID', false); + + +/** + * @define {boolean} Whether the user agent is running on an iPhone. + */ +goog.define('goog.userAgent.ASSUME_IPHONE', false); + + +/** + * @define {boolean} Whether the user agent is running on an iPad. + */ +goog.define('goog.userAgent.ASSUME_IPAD', false); + + +/** + * @define {boolean} Whether the user agent is running on an iPod. + */ +goog.define('goog.userAgent.ASSUME_IPOD', false); + + +/** + * @type {boolean} + * @private + */ +goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC || + goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX || + goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID || + goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD || + goog.userAgent.ASSUME_IPOD; + + +/** + * Whether the user agent is running on a Macintosh operating system. + * @type {boolean} + */ +goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_MAC : + goog.labs.userAgent.platform.isMacintosh(); + + +/** + * Whether the user agent is running on a Windows operating system. + * @type {boolean} + */ +goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_WINDOWS : + goog.labs.userAgent.platform.isWindows(); + + +/** + * Whether the user agent is Linux per the legacy behavior of + * goog.userAgent.LINUX, which considered ChromeOS to also be + * Linux. + * @return {boolean} + * @private + */ +goog.userAgent.isLegacyLinux_ = function() { + return goog.labs.userAgent.platform.isLinux() || + goog.labs.userAgent.platform.isChromeOS(); +}; + + +/** + * Whether the user agent is running on a Linux operating system. + * + * Note that goog.userAgent.LINUX considers ChromeOS to be Linux, + * while goog.labs.userAgent.platform considers ChromeOS and + * Linux to be different OSes. + * + * @type {boolean} + */ +goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_LINUX : + goog.userAgent.isLegacyLinux_(); + + +/** + * @return {boolean} Whether the user agent is an X11 windowing system. + * @private + */ +goog.userAgent.isX11_ = function() { + var navigator = goog.userAgent.getNavigator(); + return !!navigator && + goog.string.contains(navigator['appVersion'] || '', 'X11'); +}; + + +/** + * Whether the user agent is running on a X11 windowing system. + * @type {boolean} + */ +goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_X11 : + goog.userAgent.isX11_(); + + +/** + * Whether the user agent is running on Android. + * @type {boolean} + */ +goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_ANDROID : + goog.labs.userAgent.platform.isAndroid(); + + +/** + * Whether the user agent is running on an iPhone. + * @type {boolean} + */ +goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPHONE : + goog.labs.userAgent.platform.isIphone(); + + +/** + * Whether the user agent is running on an iPad. + * @type {boolean} + */ +goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPAD : + goog.labs.userAgent.platform.isIpad(); + + +/** + * Whether the user agent is running on an iPod. + * @type {boolean} + */ +goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPOD : + goog.labs.userAgent.platform.isIpod(); + + +/** + * Whether the user agent is running on iOS. + * @type {boolean} + */ +goog.userAgent.IOS = goog.userAgent.PLATFORM_KNOWN_ ? + (goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD || + goog.userAgent.ASSUME_IPOD) : + goog.labs.userAgent.platform.isIos(); + +/** + * @return {string} The string that describes the version number of the user + * agent. + * @private + */ +goog.userAgent.determineVersion_ = function() { + // All browsers have different ways to detect the version and they all have + // different naming schemes. + // version is a string rather than a number because it may contain 'b', 'a', + // and so on. + var version = ''; + var arr = goog.userAgent.getVersionRegexResult_(); + if (arr) { + version = arr ? arr[1] : ''; + } + + if (goog.userAgent.IE) { + // IE9 can be in document mode 9 but be reporting an inconsistent user agent + // version. If it is identifying as a version lower than 9 we take the + // documentMode as the version instead. IE8 has similar behavior. + // It is recommended to set the X-UA-Compatible header to ensure that IE9 + // uses documentMode 9. + var docMode = goog.userAgent.getDocumentMode_(); + if (docMode != null && docMode > parseFloat(version)) { + return String(docMode); + } + } + + return version; +}; + + +/** + * @return {?Array|undefined} The version regex matches from parsing the user + * agent string. These regex statements must be executed inline so they can + * be compiled out by the closure compiler with the rest of the useragent + * detection logic when ASSUME_* is specified. + * @private + */ +goog.userAgent.getVersionRegexResult_ = function() { + var userAgent = goog.userAgent.getUserAgentString(); + if (goog.userAgent.GECKO) { + return /rv\:([^\);]+)(\)|;)/.exec(userAgent); + } + if (goog.userAgent.EDGE) { + return /Edge\/([\d\.]+)/.exec(userAgent); + } + if (goog.userAgent.IE) { + return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent); + } + if (goog.userAgent.WEBKIT) { + // WebKit/125.4 + return /WebKit\/(\S+)/.exec(userAgent); + } + if (goog.userAgent.OPERA) { + // If none of the above browsers were detected but the browser is Opera, the + // only string that is of interest is 'Version/<number>'. + return /(?:Version)[ \/]?(\S+)/.exec(userAgent); + } + return undefined; +}; + + +/** + * @return {number|undefined} Returns the document mode (for testing). + * @private + */ +goog.userAgent.getDocumentMode_ = function() { + // NOTE(pupius): goog.userAgent may be used in context where there is no DOM. + var doc = goog.global['document']; + return doc ? doc['documentMode'] : undefined; +}; + + +/** + * The version of the user agent. This is a string because it might contain + * 'b' (as in beta) as well as multiple dots. + * @type {string} + */ +goog.userAgent.VERSION = goog.userAgent.determineVersion_(); + + +/** + * Compares two version numbers. + * + * @param {string} v1 Version of first item. + * @param {string} v2 Version of second item. + * + * @return {number} 1 if first argument is higher + * 0 if arguments are equal + * -1 if second argument is higher. + * @deprecated Use goog.string.compareVersions. + */ +goog.userAgent.compare = function(v1, v2) { + return goog.string.compareVersions(v1, v2); +}; + + +/** + * Cache for {@link goog.userAgent.isVersionOrHigher}. + * Calls to compareVersions are surprisingly expensive and, as a browser's + * version number is unlikely to change during a session, we cache the results. + * @const + * @private + */ +goog.userAgent.isVersionOrHigherCache_ = {}; + + +/** + * Whether the user agent version is higher or the same as the given version. + * NOTE: When checking the version numbers for Firefox and Safari, be sure to + * use the engine's version, not the browser's version number. For example, + * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11. + * Opera and Internet Explorer versions match the product release number.<br> + * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history"> + * Webkit</a> + * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a> + * + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. + */ +goog.userAgent.isVersionOrHigher = function(version) { + return goog.userAgent.ASSUME_ANY_VERSION || + goog.reflect.cache( + goog.userAgent.isVersionOrHigherCache_, version, function() { + return goog.string.compareVersions( + goog.userAgent.VERSION, version) >= 0; + }); +}; + + +/** + * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}. + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. + * @deprecated Use goog.userAgent.isVersionOrHigher(). + */ +goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher; + + +/** + * Whether the IE effective document mode is higher or the same as the given + * document mode version. + * NOTE: Only for IE, return false for another browser. + * + * @param {number} documentMode The document mode version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + */ +goog.userAgent.isDocumentModeOrHigher = function(documentMode) { + return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode; +}; + + +/** + * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}. + * @param {number} version The version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + * @deprecated Use goog.userAgent.isDocumentModeOrHigher(). + */ +goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher; + + +/** + * For IE version < 7, documentMode is undefined, so attempt to use the + * CSS1Compat property to see if we are in standards mode. If we are in + * standards mode, treat the browser version as the document mode. Otherwise, + * IE is emulating version 5. + * @type {number|undefined} + * @const + */ +goog.userAgent.DOCUMENT_MODE = (function() { + var doc = goog.global['document']; + var mode = goog.userAgent.getDocumentMode_(); + if (!doc || !goog.userAgent.IE) { + return undefined; + } + return mode || (doc['compatMode'] == 'CSS1Compat' ? + parseInt(goog.userAgent.VERSION, 10) : + 5); +})();
diff --git a/third_party/ink/ink/web/js/canvas_manager/canvas_manager.js b/third_party/ink/ink/web/js/canvas_manager/canvas_manager.js new file mode 100644 index 0000000..c4db2c8 --- /dev/null +++ b/third_party/ink/ink/web/js/canvas_manager/canvas_manager.js
@@ -0,0 +1,598 @@ +// Copyright 2017 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. +goog.provide('ink.CanvasManager'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.structs.Set'); +goog.require('goog.ui.Component'); +goog.require('ink.BrushModel'); +goog.require('ink.Color'); +goog.require('ink.ElementListener'); +goog.require('ink.SketchologyEngineWrapper'); +goog.require('ink.embed.events'); +goog.require('ink.util'); +goog.require('sketchology.proto.BackgroundImageInfo'); +goog.require('sketchology.proto.Border'); +goog.require('sketchology.proto.ImageExport'); +goog.require('sketchology.proto.Rect'); +goog.require('sketchology.proto.SetCallbackFlags'); + + + +/** + * The controller of the canvas used for drawing. + * + * @param {?string} engineUrl + * @param {ink.util.SEngineType} sengineType + * @struct + * @constructor + * @extends {goog.ui.Component} + * @implements {ink.ElementListener} + */ +ink.CanvasManager = function(engineUrl, sengineType) { + ink.CanvasManager.base(this, 'constructor'); + + /** @private {ink.BrushModel} */ + this.brushModel_ = null; + + /** @private {!ink.SketchologyEngineWrapper} */ + this.engine_ = new ink.SketchologyEngineWrapper( + engineUrl, this, goog.bind(this.onPngExportComplete_, this), sengineType); + this.addChild(this.engine_); + this.getHandler().listenOnce( + this.engine_, ink.SketchologyEngineWrapper.EventType.CANVAS_INITIALIZED, + goog.bind(function() { + this.brushUpdate_(); + this.setBorderImage_(); + this.dispatchEvent(ink.embed.events.EventType.CANVAS_INITIALIZED); + }, this)); + + // Redispatch CANVAS_FATAL_ERROR events as the top level FATAL_ERROR. + this.getHandler().listen( + this.engine_, ink.SketchologyEngineWrapper.EventType.CANVAS_FATAL_ERROR, + this.dispatchFatalError_); + + this.getHandler().listen( + this.engine_, ink.SketchologyEngineWrapper.EventType.PEN_MODE_ENABLED, + (ev) => { + this.dispatchEvent(new ink.embed.events.PenModeEnabled(ev.enabled)); + }); + + /** + * Known element UUIDs from bottom to top + * @private {Array.<string>} + */ + this.UUIDs_ = []; + + /** + * Set of UUIDs created by the engine but not yet acknowledged by Brix. + * @private {goog.structs.Set}} + */ + this.pendingUUIDs_ = new goog.structs.Set(); + + /** + * Next local ID to use for Brix element bundles missing IDs. + * @private {number} + */ + this.nextLocalId_ = 0; + + /** + * @const + * @type {string} + */ + this.FAKE_UUID = 'fake'; + + /** + * Background counter + * @type {number} + * @private + */ + this.bgCount_ = 0; + + /** @private {?function(!goog.html.SafeUrl)} */ + this.onPngExportCompleteCallback_ = null; + + /** @private {boolean} */ + this.exportAsBlob_ = false; +}; +goog.inherits(ink.CanvasManager, goog.ui.Component); + + +/** @const */ +ink.CanvasManager.BORDER_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEU' + + 'gAAAFgAAABYCAYAAABxlTA0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAm' + + 'pwYAAAAB3RJTUUH4AgPEBYrHoEFUgAAAw1JREFUeNrt3b9uE0EQBvBvZvd8LURUkVwEkEDAm' + + '9AgQYOQKHgZWloqUMoUaVJSpAivQBEhJGhSWiAlKXz7JwXey9nE9vrOgHT3fZKVFM5F/mUyN' + + '5tIY8F2EtG/yNYucnZ21keg/57d3V1RMvzdEJjABGYITGACM+1iNz5RxGGPzCIbnT+i3RA1A' + + 'jgFcADgR4zRDwTVABjHGF8AeJwOaDnYNgd29jGGED6JyMvxeDwZYvVOJpN3FxcXH0TkWfMov' + + 'Qpac6p39jgdMi4A7Ozs/HLOvQkhfEkurW9yDVh47+GcOxgybsre3t5P59y+937OqHUFhxAQQ' + + 'kBVVd85E/yOc+5bcuk0pqWLeO/hvQ+krV289x45yLqqPSRk5xym0yllZ5lOp6iqCiGEtW3C5' + + 'lZwzq/DUOK9h4jUwK3GtGYFz1oEZa97cA2crJaNapozoiVk5rqCm+2h898ihn487uKiWPMPy' + + '2arYG7G7TQHM91CYAITmCEwgQnMEJjABGYITGCGwAQmMENgAhOYITCBCUwCAhOYITCBCcwQm' + + 'MAEZghMYIbABCYwQ2ACE5ghMIEJzBCYwAyBCUxghsAEJjBDYAIzBP63wFkLcUX4lhAp3nu79' + + 'Qr23t8lbZ37WwNuLFx7fnJycnvossfHx7dijK+WGLWrYBFBjPHh5eXl/tHR0Z0h41ZV9T7G+' + + 'EREum/AThcREaiqqOpTAJ8PDw8/VlX11TnnhgArIrYsy3vn5+evy7J8NLNADrJd1xpUFcaY9' + + 'BBVfSAib2foAPq7GTAZGGNSkcFaC2stjDH161+BLGsrOAEXRYGyLOsNgKqK5hbovgIng/T6R' + + '6MRiqKogVtVcPqi5k+tKIo5XOfcYICNMbDWYjQaoSzLP4BXtYqVFayqiDHCWlsjJnDnXPYG0' + + 'j5UcCqy9LDWtq/gZcipHxVFMbfitec3uMX70FwP7nyTW2zyaSt236v3pipO7UJVs9pDVgU3p' + + '4n0jZqwQwBeHFlzYLPn4MXPb4Lt+5i2CJ1zgsuu4GUX2vBNk3qJnvX8jOdwv3h7O1wBYIqaD' + + '5lCtYoAAAAASUVORK5CYII='; + + +/** + * This color needs to match BORDER_IMAGE. + * @const + */ +ink.CanvasManager.OUT_OF_BOUNDS_COLOR = 0xe6e6e6ff; + + +/** @override */ +ink.CanvasManager.prototype.enterDocument = function() { + ink.CanvasManager.base(this, 'enterDocument'); + + this.engine_.render(this.getElement()); + + var handler = this.getHandler(); + goog.asserts.assert(handler); + + this.brushModel_ = ink.BrushModel.getInstance(this); + + handler.listen( + this.brushModel_, ink.BrushModel.EventType.CHANGE, this.brushUpdate_); +}; + +/** + * Sets or unsets readOnly on the engine. + * @param {boolean} readOnly + */ +ink.CanvasManager.prototype.setReadOnly = function(readOnly) { + this.engine_.setReadOnly(readOnly); +}; + + +/** + * Export the scene as a PNG from the engine. + * @param {number} maxWidth + * @param {boolean} drawBackground + * @param {function(!goog.html.SafeUrl)} callback + * @param {boolean=} opt_asBlob + */ +ink.CanvasManager.prototype.exportPng = function( + maxWidth, drawBackground, callback, opt_asBlob) { + this.onPngExportCompleteCallback_ = callback; + this.exportAsBlob_ = !!opt_asBlob; + var exportProto = new sketchology.proto.ImageExport(); + exportProto.setMaxDimensionPx(maxWidth); + exportProto.setShouldDrawBackground(drawBackground); + this.engine_.exportPng(exportProto); +}; + + +/** + * @private + * @param {number} width + * @param {number} height + * @param {Uint8ClampedArray} bytesArr + */ +ink.CanvasManager.prototype.onPngExportComplete_ = function( + width, height, bytesArr) { + if (this.onPngExportCompleteCallback_) { + try { + var imageData = new ImageData(bytesArr, width, height); + var scratchCanvas = + /** @type {!HTMLCanvasElement} */ (document.createElement('canvas')); + var scratchContext = + /** @type {!CanvasRenderingContext2D} */ ( + scratchCanvas.getContext('2d')); + scratchCanvas.width = width; + scratchCanvas.height = height; + scratchContext.putImageData(imageData, 0, 0); + } catch (ex) { + this.dispatchFatalError_(ex); + return; + } + if (this.exportAsBlob_) { + var cb = (blob) => { + this.onPngExportCompleteCallback_(goog.html.SafeUrl.fromBlob(blob)); + }; + + if (scratchCanvas['msToBlob']) { + scratchCanvas['msToBlob'](cb, 'image/png'); + } else { + scratchCanvas.toBlob(cb, 'image/png'); + } + } else { + this.onPngExportCompleteCallback_( + goog.html.SafeUrl.fromDataUrl(scratchCanvas.toDataURL())); + } + } +}; + + +/** + * Sets a background image, and sets the page bounds to match the image size. + * @param {Uint8ClampedArray} data The image data in RGBA 8888. + * @param {goog.math.Size} size The image dimensions. + */ +ink.CanvasManager.prototype.setBackgroundImage = function(data, size) { + this.setBackgroundImage_(data, size); +}; + + +/** + * Sets a background image and scales the image to match the existing page + * bounds. Will not display the background if no page bounds are set. + * @param {Uint8ClampedArray} data The image data in RGBA 8888. + * @param {goog.math.Size} size The image dimensions. + */ +ink.CanvasManager.prototype.setImageToUseForPageBackground = function( + data, size) { + this.setBackgroundImage_(data, size, {'bounds': 'none'}); +}; + + +/** + * @private + * @param {Uint8ClampedArray} data The image data in RGBA 8888. + * @param {goog.math.Size} size The image dimensions. + * @param {Object<string, *>=} opt_options + */ +ink.CanvasManager.prototype.setBackgroundImage_ = function( + data, size, opt_options) { + opt_options = opt_options || {}; + + var nextUri = 'sketchology://background_' + this.bgCount_; + this.bgCount_++; + + var bgImageProto = new sketchology.proto.BackgroundImageInfo(); + bgImageProto.setUri(nextUri); + if (opt_options['bounds'] != 'none') { + var optBounds = opt_options['bounds'] || + {'xlow': 0, 'ylow': 0, 'xhigh': size.width, 'yhigh': size.height}; + var bounds = new sketchology.proto.Rect(); + bounds.setXlow(optBounds['xlow']); + bounds.setYlow(optBounds['ylow']); + bounds.setXhigh(optBounds['xhigh']); + bounds.setYhigh(optBounds['yhigh']); + bgImageProto.setBounds(bounds); + } + this.engine_.setBackgroundImage(data, size, nextUri, bgImageProto); +}; + + +/** + * Set background color. + * @param {ink.Color} color + */ +ink.CanvasManager.prototype.setBackgroundColor = function(color) { + this.engine_.setBackgroundColor(color); +}; + + +/** @private */ +ink.CanvasManager.prototype.brushUpdate_ = function() { + goog.asserts.assert(this.brushModel_); + + var tool_type = this.brushModel_.getToolType(); + var brush_type = this.brushModel_.getBrushType(); + var strokeWidth = this.brushModel_.getStrokeWidth(); + + var colorString = this.brushModel_.getColor().substring(1); + var color = new ink.Color(parseInt(colorString, 16)); + // Using this alpha channel would make calligraphy or marker brushes + // translucent; highlighter, watercolor, and airbrush have hard-coded alpha + // values in the engine. + color.a = 0xFF; // Set alpha to opaque + + this.engine_.brushUpdate( + color.getRgbaUint32(), strokeWidth, tool_type, brush_type); +}; + + +/** + * Handles a new element created in the engine. + * @param {string} uuid + * @param {string} encodedElement + * @param {string} encodedTransform + * @override + */ +ink.CanvasManager.prototype.onElementCreated = function( + uuid, encodedElement, encodedTransform) { + this.pendingUUIDs_.add(uuid); + this.dispatchEvent(new ink.embed.events.ElementCreatedEvent( + uuid, encodedElement, encodedTransform)); +}; + + +/** + * Handles an element being transformed. + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + * @override + */ +ink.CanvasManager.prototype.onElementsMutated = function( + uuids, encodedTransforms) { + this.dispatchEvent( + new ink.embed.events.ElementsMutatedEvent(uuids, encodedTransforms)); +}; + + +/** + * Handles elements being removed. + * @param {Array.<string>} uuids + * @override + */ +ink.CanvasManager.prototype.onElementsRemoved = function(uuids) { + this.dispatchEvent(new ink.embed.events.ElementsRemovedEvent(uuids)); +}; + + +/** + * Handle an addElement request from Brix. If this is a remote add or an + * add-by-undo or redo, adds the element to the engine and the local list of + * elements (this.UUIDs_) at the specified index. If this is a local add + * originated by the engine, the element is already present in the engine and is + * simply removed from this.pendingUUIDs_. + * + * @param {!Object<string, string>} bundle + * @param {number} idx index to add the element at + * @param {boolean} isLocal + */ +ink.CanvasManager.prototype.addElement = function(bundle, idx, isLocal) { + if (!bundle['id']) { + bundle['id'] = 'local-' + this.nextLocalId_++; + } + + var uuid = bundle['id']; + goog.array.insertAt(this.UUIDs_, uuid, idx); + + // If the element originated in the engine, it should be present in the + // pending UUID set, so we just remove it from that set so that future adds by + // undo/redo will work as expected. + if (this.pendingUUIDs_.contains(uuid)) { + this.pendingUUIDs_.remove(uuid); + } else { + // If the element is a remote add or an add by undo or redo, it may not be + // the top element, in which case we add it below the element after it. + if (idx < this.UUIDs_.length - 1) { + this.engine_.addElementBelow(bundle, this.UUIDs_[idx + 1]); + } else { + this.engine_.addElement(bundle); + } + } + + // TODO(wfurr): Figure out how to have the engine wake itself up. + // See b/18830720. + this.engine_.poke(); +}; + + +/** + * Removes a number of elements. + * @param {number} idx index to start removing. + * @param {number} count number of items to remove. + */ +ink.CanvasManager.prototype.removeElements = function(idx, count) { + for (var i = 0; i < count; i++) { + var uuid = this.UUIDs_[idx]; + this.engine_.removeElement(uuid); + goog.array.removeAt(this.UUIDs_, idx); + } + + // TODO(wfurr): Figure out how to have the engine wake itself up. + // See b/18830720. + this.engine_.poke(); +}; + + +/** + * Resets the engine but does not dispatch any Brix related events. Used to + * clear the canvas to reuse it to display another drawing. + */ +ink.CanvasManager.prototype.resetCanvas = function() { + this.engine_.clear(); + this.engine_.setBackgroundColor(ink.Color.DEFAULT_BACKGROUND_COLOR); + this.pendingUUIDs_.clear(); + this.UUIDs_ = []; + this.engine_.poke(); +}; + + +/** + * Clears the canvas. + */ +ink.CanvasManager.prototype.clear = function() { + this.engine_.removeAll(); +}; + + +/** + * Sets element transforms. + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + */ +ink.CanvasManager.prototype.setElementTransforms = function( + uuids, encodedTransforms) { + this.engine_.setElementTransforms(uuids, encodedTransforms); +}; + + +/** + * Set callback flags + * @param {!sketchology.proto.SetCallbackFlags} setCallbackFlags + */ +ink.CanvasManager.prototype.setCallbackFlags = function(setCallbackFlags) { + this.engine_.setCallbackFlags(setCallbackFlags); +}; + + +/** + * Sets the size of the page. + * @param {number} left + * @param {number} top + * @param {number} right + * @param {number} bottom + */ +ink.CanvasManager.prototype.setPageBounds = function(left, top, right, bottom) { + this.engine_.setPageBounds(left, top, right, bottom); +}; + + +/** + * Deselects anything selected with the edit tool. + */ +ink.CanvasManager.prototype.deselectAll = function() { + this.engine_.deselectAll(); +}; + + +/** + * Sets the border image. + * @private + */ +ink.CanvasManager.prototype.setBorderImage_ = function() { + var self = this; + var uri = 'sketchology://border0'; + var borderImageProto = new sketchology.proto.Border(); + borderImageProto.setUri(uri); + borderImageProto.setScale(1); + + ink.util.getImageBytes(ink.CanvasManager.BORDER_IMAGE, function(data, size) { + self.engine_.setBorderImage( + data, size, uri, borderImageProto, + ink.CanvasManager.OUT_OF_BOUNDS_COLOR); + }); +}; + + +/** + * Dispatches a FATAL_ERROR event, and throws an Error if it isn't handled. + * @param {Error=} opt_cause + * @private + */ +ink.CanvasManager.prototype.dispatchFatalError_ = function(opt_cause) { + if (this.dispatchEvent(new ink.embed.events.FatalErrorEvent(opt_cause))) { + // Unless one of the listeners returns false or preventDefaults, throw an + // error to trigger default exception handlers on the page. + throw opt_cause || new Error('Unhandled fatal ink error'); + } +}; + + +/** + * Enable or disable an engine flag. + * @param {sketchology.proto.Flag} which + * @param {boolean} enable + */ +ink.CanvasManager.prototype.assignFlag = function(which, enable) { + this.engine_.assignFlag(which, enable); +}; + + +/** + * Simple undo. This only works if the SEngine was constructed with a + * SingleUserDocument with InMemoryStorage. + */ +ink.CanvasManager.prototype.undo = function() { + this.engine_.undo(); +}; + + +/** + * Simple redo. This only works if the SEngine was constructed with a + * SingleUserDocument with InMemoryStorage. + */ +ink.CanvasManager.prototype.redo = function() { + this.engine_.redo(); +}; + + +/** + * Returns the current snapshot. + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.CanvasManager.prototype.getSnapshot = function(callback) { + this.engine_.getSnapshot(callback); +}; + + +/** + * Loads a document from a snapshot. + * + * @param {!sketchology.proto.Snapshot} snapshotProto + */ +ink.CanvasManager.prototype.loadFromSnapshot = function(snapshotProto) { + this.engine_.loadFromSnapshot(snapshotProto); +}; + + +/** + * Allows the user to execute arbitrary commands on the engine. + * @param {!sketchology.proto.Command} command + */ +ink.CanvasManager.prototype.handleCommand = function(command) { + this.engine_.handleCommand(command); +}; + + +/** + * Gets the raw engine object. Do not use this. + * @return {Object} + */ +ink.CanvasManager.prototype.getRawEngineObject = function() { + return this.engine_.getRawEngineObject(); +}; + + +/** + * Generates a snapshot based on a brix document. + * @param {!ink.util.RealtimeDocument} brixDoc + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.CanvasManager.prototype.convertBrixDocumentToSnapshot = + function(brixDoc, callback) { + this.engine_.convertBrixDocumentToSnapshot(brixDoc, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(boolean)} callback + */ +ink.CanvasManager.prototype.snapshotHasPendingMutations = + function(snapshot, callback) { + this.engine_.snapshotHasPendingMutations(snapshot, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.MutationPacket)} callback + */ +ink.CanvasManager.prototype.extractMutationPacket = + function(snapshot, callback) { + this.engine_.extractMutationPacket(snapshot, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.Snapshot)} callback + */ +ink.CanvasManager.prototype.clearPendingMutations = + function(snapshot, callback) { + this.engine_.clearPendingMutations(snapshot, callback); +}; + + +/** + * Calls the given callback once all previous asynchronous engine operations + * have been applied. + * @param {!Function} callback + */ +ink.CanvasManager.prototype.flush = function(callback) { + this.engine_.flush(callback); +};
diff --git a/third_party/ink/ink/web/js/cursor_updater.js b/third_party/ink/ink/web/js/cursor_updater.js new file mode 100644 index 0000000..5f4803e --- /dev/null +++ b/third_party/ink/ink/web/js/cursor_updater.js
@@ -0,0 +1,124 @@ +// Copyright 2017 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. +goog.provide('ink.CursorUpdater'); + +goog.require('goog.ui.Component'); +goog.require('ink.BrushModel'); +goog.require('ink.Color'); +goog.require('ink.embed.events'); +goog.require('ink.util'); + + + +/** + * @constructor + * @extends {goog.ui.Component} + * @struct + */ +ink.CursorUpdater = function() { + ink.CursorUpdater.base(this, 'constructor'); + + /** @private {ink.BrushModel} */ + this.brushModel_ = null; +}; +goog.inherits(ink.CursorUpdater, goog.ui.Component); + + +/** @override */ +ink.CursorUpdater.prototype.enterDocument = function() { + ink.CursorUpdater.base(this, 'enterDocument'); + + this.brushModel_ = ink.BrushModel.getInstance(this); + + var handler = this.getHandler(); + + handler.listen(this.brushModel_, + ink.BrushModel.EventType.CHANGE, + this.updateCursor_); + + handler.listen(ink.util.getRootParentComponent(this), + ink.embed.events.EventType.DONE_LOADING, + this.handleDoneLoadingEvent_); +}; + + +/** + * @param {!ink.embed.events.DoneLoadingEvent} evt + * @private + */ +ink.CursorUpdater.prototype.handleDoneLoadingEvent_ = function(evt) { + if (evt.isReadOnly) { + // If we aren't editable, use a default cursor. + this.getElement().style.cursor = ''; + } else { + this.updateCursor_(); + } +}; + + +/** + * Updates the cursor icon for the drawable area based on the current selection. + * @private + */ +ink.CursorUpdater.prototype.updateCursor_ = function() { + var rgb = this.brushModel_.getActiveColorNumericRbg(); + var r = 8; + + var url = 'url(' + this.getCursorDataUrlImage(r, rgb) + ')'; + var target = r + ' ' + r; // target is center of cursor + var fallback = ', auto'; + var cursorStyle = url + target + fallback; + + this.getElement().style.cursor = cursorStyle; +}; + +/** + * @param {number} radius + * @param {number} rgb + * @return {string} A data url for a cursor with the provided radius and color. + */ +ink.CursorUpdater.prototype.getCursorDataUrlImage = function(radius, rgb) { + + // TODO(esrauch): We avoid initializing with a fixed brush width for normal + // rendering ahead of time since the android client has the same brush at a + // very large number of different radiuses. Since this is only used for + // cursors, we should really just precompute these as images offline and just + // splice in the colors. + var scratchCanvas = + /** @type {!HTMLCanvasElement} */ (document.createElement('canvas')); + + var context = + /** @type {!CanvasRenderingContext2D} */ (scratchCanvas.getContext('2d')); + + // Make the cursors opaque. + var color = new ink.Color(rgb | 0xFF000000); + + // Cap the minimum radius at 2. + radius = Math.max(radius, 2); + var diameter = Math.ceil(2 * radius); + scratchCanvas.width = diameter; + scratchCanvas.height = diameter; + + // If we have a dark color, use a white outline. For a light color, use a + // black outline. + // Compute the lightness value as defined by HSL. + var max = Math.max(color.r, color.g, color.b); + var min = Math.min(color.r, color.g, color.b); + var lightness = 0.5 * (max + min); + var outlineColor = lightness > 127 ? ink.Color.BLACK : ink.Color.WHITE; + + context.fillStyle = outlineColor.getRgbString(); + context.beginPath(); + context.arc(radius, radius, radius, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + + context.fillStyle = color.getRgbString(); + context.beginPath(); + context.arc(radius, radius, radius - 1, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + + return scratchCanvas.toDataURL(); +};
diff --git a/third_party/ink/ink/web/js/embed/embed.js b/third_party/ink/ink/web/js/embed/embed.js new file mode 100644 index 0000000..1c159444c --- /dev/null +++ b/third_party/ink/ink/web/js/embed/embed.js
@@ -0,0 +1,67 @@ +// Copyright 2017 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. +goog.provide('ink.embed.Config'); + +goog.require('goog.ui.Component'); +goog.require('ink.util'); +goog.require('protos.research.ink.InkEvent'); + + + +/** + * @constructor + * @struct + */ +ink.embed.Config = function() { + /** + * The parent element to render into (required). + * @type {?Element} + */ + this.parentEl = null; + + /** + * The parent component to set (optional); + * TODO(esrauch): This is only necessary because of the cross-package events + * that are currently going in both directions. Remove this from the config + * after this is cleaned up to avoid Whiteboard events being listened to + * directly in Embed code. + * @type {?goog.ui.Component} + */ + this.parentComponent = null; + + /** + * If true, allows ink to show its own error dialogs for certain cases. + * @type {boolean} + */ + this.allowDialogs = false; + + /** + * Path to NaCl binary. + * + * If you are using the Native Client build, you must specify the url for the + * Ink Native Client NMF file. + * + * @type {?string} + */ + this.nativeClientManifestUrl = null; + + /** + * The source of the embedder. + * + * From //logs/proto/research/ink/ink_event.proto + * + * @type {protos.research.ink.InkEvent.Host} + */ + this.logsHost = protos.research.ink.InkEvent.Host.UNKNOWN_HOST; + + /** + * The type of the document the SEngine should be constructed with. + * + * For Brix documents, this should be PASSTHROUGH_DOCUMENT. + * + * @type {ink.util.SEngineType} + */ + this.sengineType = + ink.util.SEngineType.PASSTHROUGH_DOCUMENT; +};
diff --git a/third_party/ink/ink/web/js/embed/embed_component.js b/third_party/ink/ink/web/js/embed/embed_component.js new file mode 100644 index 0000000..60206d9 --- /dev/null +++ b/third_party/ink/ink/web/js/embed/embed_component.js
@@ -0,0 +1,417 @@ +// Copyright 2017 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. +/** + * @fileoverview The embeddable ink component. Generally should be constructed + * by using {@code ink.embed.Config.execute()}. + */ +goog.provide('ink.embed.EmbedComponent'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.math.Size'); +goog.require('goog.soy'); +goog.require('goog.ui.Component'); +goog.require('ink.CanvasManager'); +goog.require('ink.Color'); +goog.require('ink.CursorUpdater'); +goog.require('ink.embed.Config'); +goog.require('ink.embed.events'); +goog.require('ink.soy.embedContent'); +goog.require('ink.util'); +goog.require('protos.research.ink.InkEvent'); +goog.require('sketchology.proto.SetCallbackFlags'); + + + +/** + * @param {!ink.embed.Config} config + * @param {!Function} callback + * @constructor + * @extends {goog.ui.Component} + * @struct + */ +ink.embed.EmbedComponent = function(config, callback) { + ink.embed.EmbedComponent.base(this, 'constructor'); + + /** @private {!ink.embed.Config} */ + this.config_ = config; + + /** @public {boolean} */ + this.allowDialogs = config.allowDialogs; + + /** @private {!ink.CursorUpdater} */ + this.cursorUpdater_ = new ink.CursorUpdater(); + this.addChild(this.cursorUpdater_); + + /** @private {!ink.CanvasManager} */ + this.canvasManager_ = + new ink.CanvasManager(config.nativeClientManifestUrl, config.sengineType); + this.addChild(this.canvasManager_); + + /** @private {!Function} */ + this.callback_ = callback; +}; +goog.inherits(ink.embed.EmbedComponent, goog.ui.Component); + + +//////////////////////////////////////////////////////////////// +// Public API for embedders. +//////////////////////////////////////////////////////////////// + + +/** + * @param {!ink.embed.Config} config + * @param {function(ink.embed.EmbedComponent)} callback Callback function that + * returns the component that is configured based on the settings in this + * Config. This component will raise the relevant ink.embed.Events and provides + * an interface to change the brush color, size, etc. Null is returned if the + * config is invalid. + */ +ink.embed.EmbedComponent.execute = function(config, callback) { + var embed = new ink.embed.EmbedComponent(config, callback); + embed.setParent(config.parentComponent); + embed.render(config.parentEl); +}; + + +/** Removes all elements from the drawing space. */ +ink.embed.EmbedComponent.prototype.clear = function() { + var e = new goog.events.Event(ink.embed.events.EventType.CLEAR_REQUESTED); + this.dispatchEvent(e); + if (!e.defaultPrevented) { + this.canvasManager_.clear(); + } +}; + + +/** Undoes the last modification to the document taken by the user. */ +ink.embed.EmbedComponent.prototype.undo = function() { + var e = new goog.events.Event(ink.embed.events.EventType.UNDO_REQUESTED); + this.dispatchEvent(e); + if (!e.defaultPrevented) { + this.canvasManager_.undo(); + } + var eventProto = ink.util.createDocumentEvent( + this.getLogsHost(), + protos.research.ink.InkEvent.DocumentEvent.DocumentEventType.UNDO); + var logEvent = new ink.embed.events.LogEvent(eventProto); + this.dispatchEvent(logEvent); +}; + + +/** Redoes the last undone action. */ +ink.embed.EmbedComponent.prototype.redo = function() { + var e = new goog.events.Event(ink.embed.events.EventType.REDO_REQUESTED); + this.dispatchEvent(e); + if (!e.defaultPrevented) { + this.canvasManager_.redo(); + } + var eventProto = ink.util.createDocumentEvent( + this.getLogsHost(), + protos.research.ink.InkEvent.DocumentEvent.DocumentEventType.REDO); + var logEvent = new ink.embed.events.LogEvent(eventProto); + this.dispatchEvent(logEvent); +}; + + +/** + * Adds a background image. Sets page bounds to match the given image. + * @param {string} imgSrc + * @param {Function=} opt_cb callback on image load complete + */ +ink.embed.EmbedComponent.prototype.setBackgroundImage = + function(imgSrc, opt_cb) { + ink.util.getImageBytes(imgSrc, + (data, size) => { + this.canvasManager_.setBackgroundImage(data, size); + if (opt_cb) opt_cb(); + }); +}; + + +/** + * Sets a background image to match the existing page bounds. Will not + * display if no page bounds are set. + * @param {string} imgSrc + * @param {Function=} opt_cb callback on image load complete + */ +ink.embed.EmbedComponent.prototype.setImageToUseForPageBackground = + function(imgSrc, opt_cb) { + ink.util.getImageBytes(imgSrc, + (data, size) => { + this.canvasManager_.setImageToUseForPageBackground(data, size); + if (opt_cb) opt_cb(); + }); +}; + + +/** + * Set background color. + * @param {ink.Color} color + */ +ink.embed.EmbedComponent.prototype.setBackgroundColor = function(color) { + this.canvasManager_.setBackgroundColor(color); +}; + + +/** + * Export the scene as a PNG from the engine. + * @param {number} maxWidth + * @param {boolean} drawBackground + * @param {function(!goog.html.SafeUrl)} callback + * @param {boolean=} opt_asBlob If true, returns a blob uri. + */ +ink.embed.EmbedComponent.prototype.exportPng = function( + maxWidth, drawBackground, callback, opt_asBlob) { + this.canvasManager_.exportPng(maxWidth, drawBackground, callback, opt_asBlob); +}; + + +/** + * Set callback flags, for whether to receive callbacks and what data to attach. + * @param {!sketchology.proto.SetCallbackFlags} setCallbackFlags + */ +ink.embed.EmbedComponent.prototype.setCallbackFlags = + function(setCallbackFlags) { + this.canvasManager_.setCallbackFlags(setCallbackFlags); +}; + + +/** + * Sets the size of the page. + * @param {number} left + * @param {number} top + * @param {number} right + * @param {number} bottom + */ +ink.embed.EmbedComponent.prototype.setPageBounds = + function(left, top, right, bottom) { + this.canvasManager_.setPageBounds(left, top, right, bottom); +}; + + +/** + * Deselects anything selected with the edit tool. + */ +ink.embed.EmbedComponent.prototype.deselectAll = function() { + this.canvasManager_.deselectAll(); +}; + + +//////////////////////////////////////////////////////////////// +// Internal code. +//////////////////////////////////////////////////////////////// + + +/** @override */ +ink.embed.EmbedComponent.prototype.createDom = function() { + this.setElementInternal(goog.soy.renderAsElement(ink.soy.embedContent)); +}; + + +/** @override */ +ink.embed.EmbedComponent.prototype.enterDocument = function() { + ink.embed.EmbedComponent.base(this, 'enterDocument'); + + var container = goog.dom.getElement('layer-container'); + + this.canvasManager_.render(container); + this.cursorUpdater_.decorate(container); + + this.getHandler().listen( + this.canvasManager_, ink.embed.events.EventType.CANVAS_INITIALIZED, + goog.bind(this.callback_, this, this)); +}; + + +/** + * Manually adds an element. + * @param {!sketchology.proto.Element} elem + * @param {number} idx index to add the element at + * @param {boolean} isLocal + */ +ink.embed.EmbedComponent.prototype.addElement = function(elem, idx, isLocal) { + this.canvasManager_.addElement(elem, idx, isLocal); +}; + + +/** + * Manually removes a number of elements. + * @param {number} idx index to start removing. + * @param {number} count number of items to remove. + */ +ink.embed.EmbedComponent.prototype.removeElements = function(idx, count) { + this.canvasManager_.removeElements(idx, count); +}; + + +/** + * Resets the canvas associated with the embed component. + * + * Note: Does not affect any attached Brix documents. + */ +ink.embed.EmbedComponent.prototype.resetCanvas = function() { + this.canvasManager_.resetCanvas(); +}; + + +/** + * Sets or unsets readOnly on the canvas. + * @param {boolean} readOnly + */ +ink.embed.EmbedComponent.prototype.setReadOnly = function(readOnly) { + this.canvasManager_.setReadOnly(readOnly); +}; + + +/** + * Sets element transforms. + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + */ +ink.embed.EmbedComponent.prototype.setElementTransforms = function( + uuids, encodedTransforms) { + this.canvasManager_.setElementTransforms(uuids, encodedTransforms); +}; + + +/** + * Returns true if the document is empty, false if it has content, and + * undefined if not a brix document. + * @param {Function} callback + */ +ink.embed.EmbedComponent.prototype.isEmpty = function(callback) { + var e = new ink.embed.events.EmptyStatusRequestedEvent(callback); + this.dispatchEvent(e); + if (!e.defaultPrevented) { + callback(undefined); + } +}; + + +/** + * Returns the current dimensions of the canvas element. + * @return {goog.math.Size} The width and height of the canvas. + */ +ink.embed.EmbedComponent.prototype.getCanvasDimensions = function() { + var element = + this.canvasManager_.getElementStrict().querySelector('canvas,embed'); + return new goog.math.Size(element.clientWidth, element.clientHeight); +}; + + +/** + * Returns the logs host id. + * @return {protos.research.ink.InkEvent.Host} + */ +ink.embed.EmbedComponent.prototype.getLogsHost = function() { + return this.config_.logsHost; +}; + + +/** + * Enable or disable an engine flag. + * @param {sketchology.proto.Flag} which + * @param {boolean} enable + */ +ink.embed.EmbedComponent.prototype.assignFlag = function(which, enable) { + this.canvasManager_.assignFlag(which, enable); +}; + + +/** + * Returns the current snapshot. Only works if sengineType is set to + * ink.util.SEngineType.IN_MEMORY. + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.embed.EmbedComponent.prototype.getSnapshot = function(callback) { + if (this.config_.sengineType !== ink.util.SEngineType.IN_MEMORY) { + throw new Error(`Can't getSnapshot without sengineType IN_MEMORY.`); + } + this.canvasManager_.getSnapshot(callback); +}; + + +/** + * Loads a document from a snapshot. Only works if sengineType is set to + * ink.util.SEngineType.IN_MEMORY. + * + * @param {!sketchology.proto.Snapshot} snapshotProto + */ +ink.embed.EmbedComponent.prototype.loadFromSnapshot = function(snapshotProto) { + if (this.config_.sengineType !== ink.util.SEngineType.IN_MEMORY) { + throw new Error(`Can't loadFromSnapshot without sengineType IN_MEMORY.`); + } + this.canvasManager_.loadFromSnapshot(snapshotProto); +}; + + +/** + * Allows the user to execute arbitrary commands on the engine. + * @param {!sketchology.proto.Command} command + */ +ink.embed.EmbedComponent.prototype.handleCommand = function(command) { + this.canvasManager_.handleCommand(command); +}; + + +/** + * Gets the raw engine object. Do not use this. + * @return {Object} + */ +ink.embed.EmbedComponent.prototype.getRawEngineObject = function() { + return this.canvasManager_.getRawEngineObject(); +}; + + +/** + * Generates a snapshot based on a brix document. + * @param {!ink.util.RealtimeDocument} brixDoc + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.embed.EmbedComponent.prototype.convertBrixDocumentToSnapshot = + function(brixDoc, callback) { + this.canvasManager_.convertBrixDocumentToSnapshot(brixDoc, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(boolean)} callback + */ +ink.embed.EmbedComponent.prototype.snapshotHasPendingMutations = + function(snapshot, callback) { + this.canvasManager_.snapshotHasPendingMutations(snapshot, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.MutationPacket)} callback + */ +ink.embed.EmbedComponent.prototype.extractMutationPacket = + function(snapshot, callback) { + this.canvasManager_.extractMutationPacket(snapshot, callback); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.Snapshot)} callback + */ +ink.embed.EmbedComponent.prototype.clearPendingMutations = + function(snapshot, callback) { + this.canvasManager_.clearPendingMutations(snapshot, callback); +}; + + +/** + * Calls the given callback once all previous asynchronous engine operations + * have been applied. + * @param {!Function} callback + */ +ink.embed.EmbedComponent.prototype.flush = function(callback) { + this.canvasManager_.flush(callback); +};
diff --git a/third_party/ink/ink/web/js/embed/events.js b/third_party/ink/ink/web/js/embed/events.js new file mode 100644 index 0000000..2997c5e --- /dev/null +++ b/third_party/ink/ink/web/js/embed/events.js
@@ -0,0 +1,225 @@ +// Copyright 2017 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. +/** + * @fileoverview Events that embedders can listen to. Note: currently embedders + * are able to listen to other internal events, but only these events should be + * treated as a public API. + */ +goog.provide('ink.embed.events'); +goog.provide('ink.embed.events.DoneLoadingEvent'); +goog.provide('ink.embed.events.ElementCreatedEvent'); +goog.provide('ink.embed.events.ElementsMutatedEvent'); +goog.provide('ink.embed.events.ElementsRemovedEvent'); +goog.provide('ink.embed.events.EmptyStatusRequestedEvent'); +goog.provide('ink.embed.events.EventType'); +goog.provide('ink.embed.events.FatalErrorEvent'); +goog.provide('ink.embed.events.LogEvent'); + +goog.require('goog.events'); +goog.require('protos.research.ink.InkEvent'); + + +/** @enum {string} */ +ink.embed.events.EventType = { + // Dispatched when the space is ready to be drawn onto. + DONE_LOADING: goog.events.getUniqueId('done_loading'), + + // Dispatched when pen mode is enabled. + PEN_MODE_ENABLED: goog.events.getUniqueId('pen_mode_enabled'), + + // Dispatched when the GL canvas is initialized. + CANVAS_INITIALIZED: goog.events.getUniqueId('canvas_initialized'), + + // Dispatched when a new element has been created on the canvas. + ELEMENT_CREATED: goog.events.getUniqueId('element_created'), + + // Dispatched when an element is mutaed. + ELEMENTS_MUTATED: goog.events.getUniqueId('elements_mutated'), + + // Dispatched when elements are removed. + ELEMENTS_REMOVED: goog.events.getUniqueId('elements_removed'), + + // Dispatched when something wants to know if the document is empty. + EMPTY_STATUS_REQUESTED: goog.events.getUniqueId('empty_status_requested'), + + // Dispatched when something needs to be logged. + LOG: goog.events.getUniqueId('log'), + + // Dispatched to request an undo. + UNDO_REQUESTED: goog.events.getUniqueId('undo_requested'), + + // Dispatched to request a redo. + REDO_REQUESTED: goog.events.getUniqueId('redo_requested'), + + // Dispatched to request a clear. + CLEAR_REQUESTED: goog.events.getUniqueId('clear_requested'), + + // Dispatched when a fatal error occurs and the canvas is no longer valid. + // If this is not handled, an Error is thrown (or re-raised). The canvas + // should be discarded and a new one constructed. + FATAL_ERROR: goog.events.getUniqueId('fatal_error') +}; + + + +/** + * An event fired when the embed is ready to be drawn onto. + * + * @param {Object} brixDoc The brix realtime document associated with + * the document that has been loaded. + * @param {boolean} isReadOnly Whether the document is read only. + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.DoneLoadingEvent = function(brixDoc, isReadOnly) { + ink.embed.events.DoneLoadingEvent.base( + this, 'constructor', ink.embed.events.EventType.DONE_LOADING); + + /** @type {Object} */ + this.brixDoc = brixDoc; + + /** @type {boolean} */ + this.isReadOnly = isReadOnly; +}; +goog.inherits(ink.embed.events.DoneLoadingEvent, goog.events.Event); + + +/** + * An event fired when pen mode is enabled or disabled. + * + * @param {boolean} enabled + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.PenModeEnabled = function(enabled) { + ink.embed.events.PenModeEnabled.base(this, 'constructor', + ink.embed.events.EventType.PEN_MODE_ENABLED); + + /** @type {boolean} */ + this.enabled = enabled; +}; +goog.inherits(ink.embed.events.PenModeEnabled, goog.events.Event); + + +/** + * An event fired when a new element is created in the embed. + * + * @param {string} uuid + * @param {string} encodedElement + * @param {string} encodedTransform + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.ElementCreatedEvent = function(uuid, encodedElement, + encodedTransform) { + ink.embed.events.ElementCreatedEvent.base( + this, 'constructor', ink.embed.events.EventType.ELEMENT_CREATED); + + this.uuid = uuid; + this.encodedElement = encodedElement; + this.encodedTransform = encodedTransform; +}; +goog.inherits(ink.embed.events.ElementCreatedEvent, goog.events.Event); + + +/** + * An event fired when an element is mutated. + * + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.ElementsMutatedEvent = function(uuids, encodedTransforms) { + ink.embed.events.ElementsMutatedEvent.base( + this, 'constructor', ink.embed.events.EventType.ELEMENTS_MUTATED); + + this.uuids = uuids; + this.encodedTransforms = encodedTransforms; +}; +goog.inherits(ink.embed.events.ElementsMutatedEvent, goog.events.Event); + + +/** + * An event fired when elements are removed. + * + * @param {Array.<string>} uuids + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.ElementsRemovedEvent = function(uuids) { + ink.embed.events.ElementsRemovedEvent.base( + this, 'constructor', ink.embed.events.EventType.ELEMENTS_REMOVED); + + this.uuids = uuids; +}; +goog.inherits(ink.embed.events.ElementsRemovedEvent, goog.events.Event); + + +/** + * An event fired when something wants to know if the document is empty. + * + * @param {Function} callback + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.EmptyStatusRequestedEvent = function(callback) { + ink.embed.events.EmptyStatusRequestedEvent.base( + this, 'constructor', ink.embed.events.EventType.EMPTY_STATUS_REQUESTED); + + this.callback = callback; +}; +goog.inherits(ink.embed.events.EmptyStatusRequestedEvent, goog.events.Event); + + +/** + * An event fired when an event should be logged by the embedder. + * + * @param {protos.research.ink.InkEvent} proto The ink event proto. + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.LogEvent = function(proto) { + ink.embed.events.LogEvent.base( + this, 'constructor', ink.embed.events.EventType.LOG); + + this.proto = proto; +}; +goog.inherits(ink.embed.events.LogEvent, goog.events.Event); + + +/** + * An event fired when a fatal error has occured. + * + * If this error is not handled by the embedder, the component will throw an + * error. The embedder should discard this component and construct a new one. + * + * @param {Error=} opt_cause Optional cause of the fatal error + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.embed.events.FatalErrorEvent = function(opt_cause) { + ink.embed.events.FatalErrorEvent.base( + this, 'constructor', ink.embed.events.EventType.FATAL_ERROR); + + /** @type {Error} */ + this.cause = opt_cause || null; +}; +goog.inherits(ink.embed.events.FatalErrorEvent, goog.events.Event);
diff --git a/third_party/ink/ink/web/js/main.soy.js b/third_party/ink/ink/web/js/main.soy.js new file mode 100644 index 0000000..3883d56 --- /dev/null +++ b/third_party/ink/ink/web/js/main.soy.js
@@ -0,0 +1,31 @@ +// Copyright 2017 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 was automatically generated from main.soy. +// Please don't edit this file by hand. + +/** + * @fileoverview Templates in namespace ink.soy. + * @public + */ + +goog.provide('ink.soy.embedContent'); + +goog.require('soy'); +goog.require('soydata.VERY_UNSAFE'); + + +/** + * @param {Object<string, *>=} opt_data + * @param {Object<string, *>=} opt_ijData + * @param {Object<string, *>=} opt_ijData_deprecated + * @return {!goog.soy.data.SanitizedHtml} + * @suppress {checkTypes} + */ +ink.soy.embedContent = function(opt_data, opt_ijData, opt_ijData_deprecated) { + opt_ijData = opt_ijData_deprecated || opt_ijData; + return soydata.VERY_UNSAFE.ordainSanitizedHtml(((goog.DEBUG && soy.$$debugSoyTemplateInfo) ? '<!--dta_of(ink.soy.embedContent, research/ink/web/js/main.soy, 7)-->' : '') + '<div id="canvas-parent"><style' + (opt_ijData && opt_ijData.csp_nonce ? ' nonce="' + soy.$$escapeHtmlAttribute(opt_ijData && opt_ijData.csp_nonce) + '"' : '') + '>\n #canvas-parent {\n height: 100%;\n position: relative;\n width: 100%;\n }\n #layer-container {\n height: 100%;\n position: relative;\n width: 100%;\n }\n #ink-engine {\n height: 100%;\n left: 0;\n position: absolute;\n top: 0;\n width: 100%;\n touch-action: none;\n }\n .above-ink-canvas {\n display: none;\n }\n </style><div class="above-ink-canvas"></div><div id="layer-container"></div><div class="below-ink-canvas"></div></div>' + ((goog.DEBUG && soy.$$debugSoyTemplateInfo) ? '<!--dta_cf(ink.soy.embedContent)-->' : '')); +}; +if (goog.DEBUG) { + ink.soy.embedContent.soyTemplateName = 'ink.soy.embedContent'; +}
diff --git a/third_party/ink/ink_event.pb.js b/third_party/ink/ink_event.pb.js new file mode 100644 index 0000000..26cd567 --- /dev/null +++ b/third_party/ink/ink_event.pb.js
@@ -0,0 +1,2654 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * logs/proto/research/ink/ink_event.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('protos.research.ink.InkEvent'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.OpenedEvent'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.DocumentState'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField'); +goog.provide('protos.research.ink.InkEvent.DocumentEvent.DocumentEventType'); +goog.provide('protos.research.ink.InkEvent.ToolbarEvent'); +goog.provide('protos.research.ink.InkEvent.ToolbarEvent.ToolEventType'); +goog.provide('protos.research.ink.InkEvent.ToolbarEvent.ToolType'); +goog.provide('protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod'); +goog.provide('protos.research.ink.InkEvent.EngineEvent'); +goog.provide('protos.research.ink.InkEvent.EngineEvent.EngineEventType'); +goog.provide('protos.research.ink.InkEvent.GmsEvent'); +goog.provide('protos.research.ink.InkEvent.GmsEvent.GmsEventType'); +goog.provide('protos.research.ink.InkEvent.Host'); +goog.provide('protos.research.ink.InkEvent.EventType'); + +goog.require('goog.proto2.Message'); + + + +/** + * Message InkEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.prototype.clone; + + +/** + * Gets the value of the host field. + * @return {?protos.research.ink.InkEvent.Host} The value. + */ +protos.research.ink.InkEvent.prototype.getHost = function() { + return /** @type {?protos.research.ink.InkEvent.Host} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the host field or the default value if not set. + * @return {!protos.research.ink.InkEvent.Host} The value. + */ +protos.research.ink.InkEvent.prototype.getHostOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.Host} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the host field. + * @param {!protos.research.ink.InkEvent.Host} value The value. + */ +protos.research.ink.InkEvent.prototype.setHost = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the host field has a value. + */ +protos.research.ink.InkEvent.prototype.hasHost = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the host field. + */ +protos.research.ink.InkEvent.prototype.hostCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the host field. + */ +protos.research.ink.InkEvent.prototype.clearHost = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the event_type field. + * @return {?protos.research.ink.InkEvent.EventType} The value. + */ +protos.research.ink.InkEvent.prototype.getEventType = function() { + return /** @type {?protos.research.ink.InkEvent.EventType} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the event_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.EventType} The value. + */ +protos.research.ink.InkEvent.prototype.getEventTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.EventType} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the event_type field. + * @param {!protos.research.ink.InkEvent.EventType} value The value. + */ +protos.research.ink.InkEvent.prototype.setEventType = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the event_type field has a value. + */ +protos.research.ink.InkEvent.prototype.hasEventType = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the event_type field. + */ +protos.research.ink.InkEvent.prototype.eventTypeCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the event_type field. + */ +protos.research.ink.InkEvent.prototype.clearEventType = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the document_event field. + * @return {?protos.research.ink.InkEvent.DocumentEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getDocumentEvent = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the document_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getDocumentEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the document_event field. + * @param {!protos.research.ink.InkEvent.DocumentEvent} value The value. + */ +protos.research.ink.InkEvent.prototype.setDocumentEvent = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the document_event field has a value. + */ +protos.research.ink.InkEvent.prototype.hasDocumentEvent = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the document_event field. + */ +protos.research.ink.InkEvent.prototype.documentEventCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the document_event field. + */ +protos.research.ink.InkEvent.prototype.clearDocumentEvent = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the toolbar_event field. + * @return {?protos.research.ink.InkEvent.ToolbarEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getToolbarEvent = function() { + return /** @type {?protos.research.ink.InkEvent.ToolbarEvent} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the toolbar_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.ToolbarEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getToolbarEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.ToolbarEvent} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the toolbar_event field. + * @param {!protos.research.ink.InkEvent.ToolbarEvent} value The value. + */ +protos.research.ink.InkEvent.prototype.setToolbarEvent = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the toolbar_event field has a value. + */ +protos.research.ink.InkEvent.prototype.hasToolbarEvent = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the toolbar_event field. + */ +protos.research.ink.InkEvent.prototype.toolbarEventCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the toolbar_event field. + */ +protos.research.ink.InkEvent.prototype.clearToolbarEvent = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the engine_event field. + * @return {?protos.research.ink.InkEvent.EngineEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getEngineEvent = function() { + return /** @type {?protos.research.ink.InkEvent.EngineEvent} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the engine_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.EngineEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getEngineEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.EngineEvent} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the engine_event field. + * @param {!protos.research.ink.InkEvent.EngineEvent} value The value. + */ +protos.research.ink.InkEvent.prototype.setEngineEvent = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the engine_event field has a value. + */ +protos.research.ink.InkEvent.prototype.hasEngineEvent = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the engine_event field. + */ +protos.research.ink.InkEvent.prototype.engineEventCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the engine_event field. + */ +protos.research.ink.InkEvent.prototype.clearEngineEvent = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the gms_event field. + * @return {?protos.research.ink.InkEvent.GmsEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getGmsEvent = function() { + return /** @type {?protos.research.ink.InkEvent.GmsEvent} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the gms_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.GmsEvent} The value. + */ +protos.research.ink.InkEvent.prototype.getGmsEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.GmsEvent} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the gms_event field. + * @param {!protos.research.ink.InkEvent.GmsEvent} value The value. + */ +protos.research.ink.InkEvent.prototype.setGmsEvent = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the gms_event field has a value. + */ +protos.research.ink.InkEvent.prototype.hasGmsEvent = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the gms_event field. + */ +protos.research.ink.InkEvent.prototype.gmsEventCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the gms_event field. + */ +protos.research.ink.InkEvent.prototype.clearGmsEvent = function() { + this.clear$Field(6); +}; + + +/** + * Enumeration Host. + * @enum {number} + */ +protos.research.ink.InkEvent.Host = { + UNKNOWN_HOST: 0, + FISHFOOD: 1, + KEEP: 2, + CLASSROOM: 3, + FIREBALL: 4 +}; + + +/** + * Enumeration EventType. + * @enum {number} + */ +protos.research.ink.InkEvent.EventType = { + UNKNOWN_TYPE: 0, + DOCUMENT_EVENT: 1, + TOOLBAR_EVENT: 2, + ENGINE_EVENT: 3, + GMS_EVENT: 4 +}; + + + +/** + * Message DocumentEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clone; + + +/** + * Gets the value of the event_type field. + * @return {?protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getEventType = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the event_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getEventTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the event_type field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setEventType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the event_type field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasEventType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the event_type field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.eventTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the event_type field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearEventType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the opened_event field. + * @return {?protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getOpenedEvent = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the opened_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getOpenedEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the opened_event field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setOpenedEvent = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the opened_event field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasOpenedEvent = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the opened_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.openedEventCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the opened_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearOpenedEvent = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the open_cancelled_event field. + * @return {?protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getOpenCancelledEvent = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the open_cancelled_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getOpenCancelledEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the open_cancelled_event field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setOpenCancelledEvent = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the open_cancelled_event field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasOpenCancelledEvent = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the open_cancelled_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.openCancelledEventCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the open_cancelled_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearOpenCancelledEvent = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the error_code field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getErrorCode = function() { + return /** @type {?string} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the error_code field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getErrorCodeOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the error_code field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setErrorCode = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the error_code field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasErrorCode = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the error_code field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.errorCodeCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the error_code field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearErrorCode = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the brix_error_code field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getBrixErrorCode = function() { + return /** @type {?string} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the brix_error_code field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getBrixErrorCodeOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the brix_error_code field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setBrixErrorCode = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the brix_error_code field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasBrixErrorCode = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the brix_error_code field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.brixErrorCodeCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the brix_error_code field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearBrixErrorCode = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the collaborator_joined_event field. + * @return {?protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getCollaboratorJoinedEvent = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the collaborator_joined_event field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getCollaboratorJoinedEventOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the collaborator_joined_event field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setCollaboratorJoinedEvent = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the collaborator_joined_event field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasCollaboratorJoinedEvent = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the collaborator_joined_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.collaboratorJoinedEventCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the collaborator_joined_event field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearCollaboratorJoinedEvent = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the document_state field. + * @return {?protos.research.ink.InkEvent.DocumentEvent.DocumentState} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getDocumentState = function() { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.DocumentState} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the document_state field or the default value if not set. + * @return {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getDocumentStateOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the document_state field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.setDocumentState = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the document_state field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.hasDocumentState = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the document_state field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.documentStateCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the document_state field. + */ +protos.research.ink.InkEvent.DocumentEvent.prototype.clearDocumentState = function() { + this.clear$Field(7); +}; + + +/** + * Enumeration DocumentEventType. + * @enum {number} + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentEventType = { + UNKNOWN_DOCUMENT_EVENT: 0, + CREATED: 1, + OPENED: 2, + OPEN_FAILED: 3, + KICKED_USER_OUT: 4, + OPEN_CANCELLED: 5, + BRIX_DOCUMENT_CONNECT: 6, + UNDO: 7, + REDO: 8, + COLLABORATOR_JOINED: 9, + SEND: 10, + ABANDON: 11, + EXTERNAL_SHARE: 12 +}; + + + +/** + * Message OpenedEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent.OpenedEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent.OpenedEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clone; + + +/** + * Gets the value of the millis_until_first_byte_loaded field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMillisUntilFirstByteLoaded = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the millis_until_first_byte_loaded field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMillisUntilFirstByteLoadedOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the millis_until_first_byte_loaded field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.setMillisUntilFirstByteLoaded = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the millis_until_first_byte_loaded field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.hasMillisUntilFirstByteLoaded = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the millis_until_first_byte_loaded field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.millisUntilFirstByteLoadedCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the millis_until_first_byte_loaded field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clearMillisUntilFirstByteLoaded = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the millis_until_editable field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMillisUntilEditable = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the millis_until_editable field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMillisUntilEditableOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the millis_until_editable field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.setMillisUntilEditable = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the millis_until_editable field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.hasMillisUntilEditable = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the millis_until_editable field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.millisUntilEditableCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the millis_until_editable field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clearMillisUntilEditable = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the missing_document_bounds field. + * @return {?boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMissingDocumentBounds = function() { + return /** @type {?boolean} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the missing_document_bounds field or the default value if not set. + * @return {boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getMissingDocumentBoundsOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the missing_document_bounds field. + * @param {boolean} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.setMissingDocumentBounds = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the missing_document_bounds field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.hasMissingDocumentBounds = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the missing_document_bounds field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.missingDocumentBoundsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the missing_document_bounds field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clearMissingDocumentBounds = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the was_opened_by_cosmoid field. + * @return {?boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getWasOpenedByCosmoid = function() { + return /** @type {?boolean} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the was_opened_by_cosmoid field or the default value if not set. + * @return {boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getWasOpenedByCosmoidOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the was_opened_by_cosmoid field. + * @param {boolean} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.setWasOpenedByCosmoid = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the was_opened_by_cosmoid field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.hasWasOpenedByCosmoid = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the was_opened_by_cosmoid field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.wasOpenedByCosmoidCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the was_opened_by_cosmoid field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clearWasOpenedByCosmoid = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the active_users field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getActiveUsers = function() { + return /** @type {?string} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the active_users field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getActiveUsersOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the active_users field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.setActiveUsers = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the active_users field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.hasActiveUsers = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the active_users field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.activeUsersCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the active_users field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.clearActiveUsers = function() { + this.clear$Field(5); +}; + + + +/** + * Message OpenCancelledEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.clone; + + +/** + * Gets the value of the time_until_cancelled field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.getTimeUntilCancelled = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the time_until_cancelled field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.getTimeUntilCancelledOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the time_until_cancelled field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.setTimeUntilCancelled = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the time_until_cancelled field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.hasTimeUntilCancelled = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the time_until_cancelled field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.timeUntilCancelledCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the time_until_cancelled field. + */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.clearTimeUntilCancelled = function() { + this.clear$Field(1); +}; + + + +/** + * Message CollaboratorJoined. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.clone; + + +/** + * Gets the value of the is_me field. + * @return {?boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.getIsMe = function() { + return /** @type {?boolean} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the is_me field or the default value if not set. + * @return {boolean} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.getIsMeOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the is_me field. + * @param {boolean} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.setIsMe = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the is_me field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.hasIsMe = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the is_me field. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.isMeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the is_me field. + */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.clearIsMe = function() { + this.clear$Field(1); +}; + + + +/** + * Message DocumentState. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent.DocumentState, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.clone; + + +/** + * Gets the value of the stroke_count field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getStrokeCount = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the stroke_count field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getStrokeCountOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the stroke_count field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.setStrokeCount = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the stroke_count field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.hasStrokeCount = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the stroke_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.strokeCountCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the stroke_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.clearStrokeCount = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the text_field field at the index given. + * @param {number} index The index to lookup. + * @return {?protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getTextField = function(index) { + return /** @type {?protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the text_field field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getTextFieldOrDefault = function(index) { + return /** @type {!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the text_field field. + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} value The value to add. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.addTextField = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the text_field field. + * @return {!Array<!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField>} The values in the field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.textFieldArray = function() { + return /** @type {!Array<!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the text_field field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.hasTextField = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the text_field field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.textFieldCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the text_field field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.clearTextField = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the sticker_count field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getStickerCount = function() { + return /** @type {?string} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the sticker_count field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getStickerCountOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the sticker_count field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.setStickerCount = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the sticker_count field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.hasStickerCount = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the sticker_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.stickerCountCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the sticker_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.clearStickerCount = function() { + this.clear$Field(3); +}; + + + +/** + * Message TextField. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField} The cloned message. + * @override + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.clone; + + +/** + * Gets the value of the character_count field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getCharacterCount = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the character_count field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getCharacterCountOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the character_count field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.setCharacterCount = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the character_count field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.hasCharacterCount = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the character_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.characterCountCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the character_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.clearCharacterCount = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the line_count field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getLineCount = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the line_count field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getLineCountOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the line_count field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.setLineCount = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the line_count field has a value. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.hasLineCount = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the line_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.lineCountCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the line_count field. + */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.clearLineCount = function() { + this.clear$Field(2); +}; + + + +/** + * Message ToolbarEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.ToolbarEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.ToolbarEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.ToolbarEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.ToolbarEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.clone; + + +/** + * Gets the value of the tool_event_type field. + * @return {?protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getToolEventType = function() { + return /** @type {?protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the tool_event_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getToolEventTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the tool_event_type field. + * @param {!protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} value The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.setToolEventType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the tool_event_type field has a value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.hasToolEventType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the tool_event_type field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.toolEventTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the tool_event_type field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.clearToolEventType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the tool_type field. + * @return {?protos.research.ink.InkEvent.ToolbarEvent.ToolType} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getToolType = function() { + return /** @type {?protos.research.ink.InkEvent.ToolbarEvent.ToolType} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the tool_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.ToolbarEvent.ToolType} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getToolTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.ToolbarEvent.ToolType} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the tool_type field. + * @param {!protos.research.ink.InkEvent.ToolbarEvent.ToolType} value The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.setToolType = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the tool_type field has a value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.hasToolType = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the tool_type field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.toolTypeCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the tool_type field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.clearToolType = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the expand_method field. + * @return {?protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getExpandMethod = function() { + return /** @type {?protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the expand_method field or the default value if not set. + * @return {!protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getExpandMethodOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the expand_method field. + * @param {!protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod} value The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.setExpandMethod = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the expand_method field has a value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.hasExpandMethod = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the expand_method field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.expandMethodCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the expand_method field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.clearExpandMethod = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the color field. + * @return {?number} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getColor = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the color field or the default value if not set. + * @return {number} The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getColorOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the color field. + * @param {number} value The value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.setColor = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the color field has a value. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.hasColor = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the color field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.colorCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the color field. + */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.clearColor = function() { + this.clear$Field(4); +}; + + +/** + * Enumeration ToolEventType. + * @enum {number} + */ +protos.research.ink.InkEvent.ToolbarEvent.ToolEventType = { + UNKNOWN_TOOL_EVENT: 0, + TOOLBAR_EXPANDED: 1, + TOOLBAR_EXTRA_COLORS_EXPANDED: 2, + TOOLBAR_CONTRACTED_BY_USER: 3, + TOOL_TYPE_CHANGED: 4, + TOOL_COLOR_SELECTED: 5, + TOOL_SIZE_SELECTED: 6, + CLEAR_CANVAS: 7, + SELECT_NONE: 8 +}; + + +/** + * Enumeration ToolType. + * @enum {number} + */ +protos.research.ink.InkEvent.ToolbarEvent.ToolType = { + UNKNOWN_TOOL_TYPE: 0, + EDIT_TOOL: 1, + CALLIGRAPHY: 2, + MARKER: 3, + HIGHLIGHTER: 4, + MAGIC_ERASER: 5 +}; + + +/** + * Enumeration ExpandMethod. + * @enum {number} + */ +protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod = { + UNKNOWN_EXPAND_METHOD: 0, + SECOND_TAP: 1, + DRAG: 2, + EXPAND_BUTTON_TAP: 3 +}; + + + +/** + * Message EngineEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.EngineEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.EngineEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.EngineEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.EngineEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.EngineEvent.prototype.clone; + + +/** + * Gets the value of the engine_event_type field. + * @return {?protos.research.ink.InkEvent.EngineEvent.EngineEventType} The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.getEngineEventType = function() { + return /** @type {?protos.research.ink.InkEvent.EngineEvent.EngineEventType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the engine_event_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.EngineEvent.EngineEventType} The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.getEngineEventTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.EngineEvent.EngineEventType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the engine_event_type field. + * @param {!protos.research.ink.InkEvent.EngineEvent.EngineEventType} value The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.setEngineEventType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the engine_event_type field has a value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.hasEngineEventType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the engine_event_type field. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.engineEventTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the engine_event_type field. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.clearEngineEventType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the error_code field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.getErrorCode = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the error_code field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.getErrorCodeOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the error_code field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.setErrorCode = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the error_code field has a value. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.hasErrorCode = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the error_code field. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.errorCodeCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the error_code field. + */ +protos.research.ink.InkEvent.EngineEvent.prototype.clearErrorCode = function() { + this.clear$Field(2); +}; + + +/** + * Enumeration EngineEventType. + * @enum {number} + */ +protos.research.ink.InkEvent.EngineEvent.EngineEventType = { + UNKNOWN_ENGINE_EVENT: 0, + LOST_GL_CONTEXT: 1, + RAISED_FATAL_EXCEPTION: 2 +}; + + + +/** + * Message GmsEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +protos.research.ink.InkEvent.GmsEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(protos.research.ink.InkEvent.GmsEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +protos.research.ink.InkEvent.GmsEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!protos.research.ink.InkEvent.GmsEvent} The cloned message. + * @override + */ +protos.research.ink.InkEvent.GmsEvent.prototype.clone; + + +/** + * Gets the value of the gms_event_type field. + * @return {?protos.research.ink.InkEvent.GmsEvent.GmsEventType} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getGmsEventType = function() { + return /** @type {?protos.research.ink.InkEvent.GmsEvent.GmsEventType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the gms_event_type field or the default value if not set. + * @return {!protos.research.ink.InkEvent.GmsEvent.GmsEventType} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getGmsEventTypeOrDefault = function() { + return /** @type {!protos.research.ink.InkEvent.GmsEvent.GmsEventType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the gms_event_type field. + * @param {!protos.research.ink.InkEvent.GmsEvent.GmsEventType} value The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.setGmsEventType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the gms_event_type field has a value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.hasGmsEventType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the gms_event_type field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.gmsEventTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the gms_event_type field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.clearGmsEventType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the time_since_connect_start field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getTimeSinceConnectStart = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the time_since_connect_start field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getTimeSinceConnectStartOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the time_since_connect_start field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.setTimeSinceConnectStart = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the time_since_connect_start field has a value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.hasTimeSinceConnectStart = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the time_since_connect_start field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.timeSinceConnectStartCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the time_since_connect_start field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.clearTimeSinceConnectStart = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the failure_has_resolution field. + * @return {?boolean} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getFailureHasResolution = function() { + return /** @type {?boolean} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the failure_has_resolution field or the default value if not set. + * @return {boolean} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getFailureHasResolutionOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the failure_has_resolution field. + * @param {boolean} value The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.setFailureHasResolution = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the failure_has_resolution field has a value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.hasFailureHasResolution = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the failure_has_resolution field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.failureHasResolutionCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the failure_has_resolution field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.clearFailureHasResolution = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the gms_error_code field. + * @return {?string} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getGmsErrorCode = function() { + return /** @type {?string} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the gms_error_code field or the default value if not set. + * @return {string} The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.getGmsErrorCodeOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the gms_error_code field. + * @param {string} value The value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.setGmsErrorCode = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the gms_error_code field has a value. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.hasGmsErrorCode = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the gms_error_code field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.gmsErrorCodeCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the gms_error_code field. + */ +protos.research.ink.InkEvent.GmsEvent.prototype.clearGmsErrorCode = function() { + this.clear$Field(4); +}; + + +/** + * Enumeration GmsEventType. + * @enum {number} + */ +protos.research.ink.InkEvent.GmsEvent.GmsEventType = { + CONNECT_SUCCESS: 0, + CONNECT_FAILED: 1, + STOPPED_WHEN_PENDING_CONNECT: 2 +}; + + +/** @override */ +protos.research.ink.InkEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'InkEvent', + fullName: 'logs.proto.research.ink.InkEvent' + }, + 1: { + name: 'host', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.Host.UNKNOWN_HOST, + type: protos.research.ink.InkEvent.Host + }, + 2: { + name: 'event_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.EventType.UNKNOWN_TYPE, + type: protos.research.ink.InkEvent.EventType + }, + 3: { + name: 'document_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent + }, + 4: { + name: 'toolbar_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.ToolbarEvent + }, + 5: { + name: 'engine_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.EngineEvent + }, + 6: { + name: 'gms_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.GmsEvent + } + }; + protos.research.ink.InkEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.getDescriptor = + protos.research.ink.InkEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'DocumentEvent', + containingType: protos.research.ink.InkEvent, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent' + }, + 1: { + name: 'event_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.DocumentEvent.DocumentEventType.UNKNOWN_DOCUMENT_EVENT, + type: protos.research.ink.InkEvent.DocumentEvent.DocumentEventType + }, + 2: { + name: 'opened_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent.OpenedEvent + }, + 3: { + name: 'open_cancelled_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent + }, + 4: { + name: 'error_code', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 5: { + name: 'brix_error_code', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 6: { + name: 'collaborator_joined_event', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined + }, + 7: { + name: 'document_state', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent.DocumentState + } + }; + protos.research.ink.InkEvent.DocumentEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'OpenedEvent', + containingType: protos.research.ink.InkEvent.DocumentEvent, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent.OpenedEvent' + }, + 1: { + name: 'millis_until_first_byte_loaded', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 2: { + name: 'millis_until_editable', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 3: { + name: 'missing_document_bounds', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 4: { + name: 'was_opened_by_cosmoid', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 5: { + name: 'active_users', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent.OpenedEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.OpenedEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'OpenCancelledEvent', + containingType: protos.research.ink.InkEvent.DocumentEvent, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent' + }, + 1: { + name: 'time_until_cancelled', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.OpenCancelledEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'CollaboratorJoined', + containingType: protos.research.ink.InkEvent.DocumentEvent, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent.CollaboratorJoined' + }, + 1: { + name: 'is_me', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.DocumentState.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'DocumentState', + containingType: protos.research.ink.InkEvent.DocumentEvent, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent.DocumentState' + }, + 1: { + name: 'stroke_count', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 2: { + name: 'text_field', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField + }, + 3: { + name: 'sticker_count', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.DocumentEvent.DocumentState.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent.DocumentState, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.DocumentState.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'TextField', + containingType: protos.research.ink.InkEvent.DocumentEvent.DocumentState, + fullName: 'logs.proto.research.ink.InkEvent.DocumentEvent.DocumentState.TextField' + }, + 1: { + name: 'character_count', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 2: { + name: 'line_count', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.getDescriptor = + protos.research.ink.InkEvent.DocumentEvent.DocumentState.TextField.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.ToolbarEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.ToolbarEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ToolbarEvent', + containingType: protos.research.ink.InkEvent, + fullName: 'logs.proto.research.ink.InkEvent.ToolbarEvent' + }, + 1: { + name: 'tool_event_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.ToolbarEvent.ToolEventType.UNKNOWN_TOOL_EVENT, + type: protos.research.ink.InkEvent.ToolbarEvent.ToolEventType + }, + 2: { + name: 'tool_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.ToolbarEvent.ToolType.UNKNOWN_TOOL_TYPE, + type: protos.research.ink.InkEvent.ToolbarEvent.ToolType + }, + 3: { + name: 'expand_method', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod.UNKNOWN_EXPAND_METHOD, + type: protos.research.ink.InkEvent.ToolbarEvent.ExpandMethod + }, + 4: { + name: 'color', + fieldType: goog.proto2.Message.FieldType.INT32, + type: Number + } + }; + protos.research.ink.InkEvent.ToolbarEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.ToolbarEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.ToolbarEvent.getDescriptor = + protos.research.ink.InkEvent.ToolbarEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.EngineEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.EngineEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'EngineEvent', + containingType: protos.research.ink.InkEvent, + fullName: 'logs.proto.research.ink.InkEvent.EngineEvent' + }, + 1: { + name: 'engine_event_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.EngineEvent.EngineEventType.UNKNOWN_ENGINE_EVENT, + type: protos.research.ink.InkEvent.EngineEvent.EngineEventType + }, + 2: { + name: 'error_code', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.EngineEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.EngineEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.EngineEvent.getDescriptor = + protos.research.ink.InkEvent.EngineEvent.prototype.getDescriptor; + + +/** @override */ +protos.research.ink.InkEvent.GmsEvent.prototype.getDescriptor = function() { + var descriptor = protos.research.ink.InkEvent.GmsEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'GmsEvent', + containingType: protos.research.ink.InkEvent, + fullName: 'logs.proto.research.ink.InkEvent.GmsEvent' + }, + 1: { + name: 'gms_event_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: protos.research.ink.InkEvent.GmsEvent.GmsEventType.CONNECT_SUCCESS, + type: protos.research.ink.InkEvent.GmsEvent.GmsEventType + }, + 2: { + name: 'time_since_connect_start', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + }, + 3: { + name: 'failure_has_resolution', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 4: { + name: 'gms_error_code', + fieldType: goog.proto2.Message.FieldType.INT64, + type: String + } + }; + protos.research.ink.InkEvent.GmsEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + protos.research.ink.InkEvent.GmsEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +protos.research.ink.InkEvent.GmsEvent.getDescriptor = + protos.research.ink.InkEvent.GmsEvent.prototype.getDescriptor;
diff --git a/third_party/ink/ink_scripts.js b/third_party/ink/ink_scripts.js new file mode 100644 index 0000000..b056e6d6 --- /dev/null +++ b/third_party/ink/ink_scripts.js
@@ -0,0 +1,118 @@ +// Copyright 2017 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. + +// The include directives are put into Javascript-style comments to prevent +// parsing errors in non-flattened mode. The flattener still sees them. +// Note that this makes the flattener to comment out the first line of the +// included file but that's all right since any javascript file should start +// with a copyright comment anyway. + +// <include src="ink/web/js/main.soy.js"> +// <include src="ink/web/js/cursor_updater.js"> +// <include src="ink/web/js/canvas_manager/canvas_manager.js"> +// <include src="ink/web/js/embed/embed_component.js"> +// <include src="ink/web/js/embed/events.js"> +// <include src="ink/web/js/embed/embed.js"> +// <include src="template/soy/soyutils_usegoog.js"> +// <include src="ink_event.pb.js"> +// <include src="sketchology/public/nacl/embed.soy.js"> +// <include src="sketchology/public/nacl/sketchology_engine_wrapper.js"> +// <include src="sketchology/public/js/common/color.js"> +// <include src="sketchology/public/js/common/model.js"> +// <include src="sketchology/public/js/common/undo_state_change_event.js"> +// <include src="sketchology/public/js/common/proto_serializer.js"> +// <include src="sketchology/public/js/common/util.js"> +// <include src="sketchology/public/js/common/element_listener.js"> +// <include src="sketchology/public/js/common/brush_model.js"> +// <include src="sketchology/proto/rect_bounds.pb.js"> +// <include src="sketchology/proto/elements.pb.js"> +// <include src="sketchology/proto/animations.pb.js"> +// <include src="sketchology/proto/sengine.pb.js"> +// <include src="sketchology/proto/document.pb.js"> +// <include src="wireserializer.js"> +// <include src="closure/functions/functions.js"> +// <include src="closure/base.js"> +// <include src="closure/fs/url.js"> +// <include src="closure/uri/utils.js"> +// <include src="closure/uri/uri.js"> +// <include src="closure/style/style.js"> +// <include src="closure/crypt/crypt.js"> +// <include src="closure/crypt/base64.js"> +// <include src="closure/ui/component.js"> +// <include src="closure/ui/idgenerator.js"> +// <include src="closure/labs/useragent/platform.js"> +// <include src="closure/labs/useragent/browser.js"> +// <include src="closure/labs/useragent/util.js"> +// <include src="closure/labs/useragent/engine.js"> +// <include src="closure/string/const.js"> +// <include src="closure/string/string.js"> +// <include src="closure/string/typedstring.js"> +// <include src="closure/structs/set.js"> +// <include src="closure/structs/inversionmap.js"> +// <include src="closure/structs/collection.js"> +// <include src="closure/structs/structs.js"> +// <include src="closure/structs/map.js"> +// <include src="closure/proto2/serializer.js"> +// <include src="closure/proto2/objectserializer.js"> +// <include src="closure/proto2/message.js"> +// <include src="closure/proto2/fielddescriptor.js"> +// <include src="closure/proto2/descriptor.js"> +// <include src="closure/html/safehtml.js"> +// <include src="closure/html/safescript.js"> +// <include src="closure/html/safestyle.js"> +// <include src="closure/html/uncheckedconversions.js"> +// <include src="closure/html/safestylesheet.js"> +// <include src="closure/html/legacyconversions.js"> +// <include src="closure/html/trustedresourceurl.js"> +// <include src="closure/html/safeurl.js"> +// <include src="closure/math/long.js"> +// <include src="closure/math/irect.js"> +// <include src="closure/math/size.js"> +// <include src="closure/math/math.js"> +// <include src="closure/math/rect.js"> +// <include src="closure/math/coordinate.js"> +// <include src="closure/math/box.js"> +// <include src="closure/object/object.js"> +// <include src="closure/reflect/reflect.js"> +// <include src="closure/useragent/useragent.js"> +// <include src="closure/useragent/product.js"> +// <include src="closure/i18n/uchar.js"> +// <include src="closure/i18n/bidi.js"> +// <include src="closure/i18n/graphemebreak.js"> +// <include src="closure/i18n/bidiformatter.js"> +// <include src="closure/dom/tagname.js"> +// <include src="closure/dom/classlist.js"> +// <include src="closure/dom/asserts.js"> +// <include src="closure/dom/nodetype.js"> +// <include src="closure/dom/dom.js"> +// <include src="closure/dom/tags.js"> +// <include src="closure/dom/safe.js"> +// <include src="closure/dom/browserfeature.js"> +// <include src="closure/dom/htmlelement.js"> +// <include src="closure/dom/vendor.js"> +// <include src="closure/soy/soy.js"> +// <include src="closure/soy/data.js"> +// <include src="closure/format/format.js"> +// <include src="closure/asserts/asserts.js"> +// <include src="closure/array/array.js"> +// <include src="closure/iter/iter.js"> +// <include src="closure/events/eventid.js"> +// <include src="closure/events/event.js"> +// <include src="closure/events/listener.js"> +// <include src="closure/events/listenable.js"> +// <include src="closure/events/browserevent.js"> +// <include src="closure/events/events.js"> +// <include src="closure/events/browserfeature.js"> +// <include src="closure/events/eventtarget.js"> +// <include src="closure/events/eventhandler.js"> +// <include src="closure/events/listenermap.js"> +// <include src="closure/events/keycodes.js"> +// <include src="closure/events/wheelevent.js"> +// <include src="closure/events/eventtype.js"> +// <include src="closure/disposable/idisposable.js"> +// <include src="closure/disposable/disposable.js"> +// <include src="closure/debug/debug.js"> +// <include src="closure/debug/errorcontext.js"> +// <include src="closure/debug/entrypointregistry.js"> +// <include src="closure/debug/error.js">
diff --git a/third_party/ink/sketchology/proto/animations.pb.js b/third_party/ink/sketchology/proto/animations.pb.js new file mode 100644 index 0000000..da7702c --- /dev/null +++ b/third_party/ink/sketchology/proto/animations.pb.js
@@ -0,0 +1,984 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * third_party/sketchology/proto/animations.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('sketchology.proto.AnimationCurve'); +goog.provide('sketchology.proto.ColorAnimation'); +goog.provide('sketchology.proto.ScaleAnimation'); +goog.provide('sketchology.proto.ElementAnimation'); +goog.provide('sketchology.proto.CurveType'); + +goog.require('goog.proto2.Message'); + + +/** + * Enumeration CurveType. + * @enum {number} + */ +sketchology.proto.CurveType = { + UNSPECIFIED_CURVE_TYPE: 0, + EASE_IN_OUT: 1, + EASE_IN: 2, + EASE_OUT: 3, + CUSTOM_CUBIC_BEZIER: 4 +}; + + + +/** + * Message AnimationCurve. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.AnimationCurve = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.AnimationCurve, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.AnimationCurve.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.AnimationCurve} The cloned message. + * @override + */ +sketchology.proto.AnimationCurve.prototype.clone; + + +/** + * Gets the value of the type field. + * @return {?sketchology.proto.CurveType} The value. + */ +sketchology.proto.AnimationCurve.prototype.getType = function() { + return /** @type {?sketchology.proto.CurveType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the type field or the default value if not set. + * @return {!sketchology.proto.CurveType} The value. + */ +sketchology.proto.AnimationCurve.prototype.getTypeOrDefault = function() { + return /** @type {!sketchology.proto.CurveType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the type field. + * @param {!sketchology.proto.CurveType} value The value. + */ +sketchology.proto.AnimationCurve.prototype.setType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the type field has a value. + */ +sketchology.proto.AnimationCurve.prototype.hasType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the type field. + */ +sketchology.proto.AnimationCurve.prototype.typeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the type field. + */ +sketchology.proto.AnimationCurve.prototype.clearType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the params field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.AnimationCurve.prototype.getParams = function(index) { + return /** @type {?number} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the params field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.AnimationCurve.prototype.getParamsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the params field. + * @param {number} value The value to add. + */ +sketchology.proto.AnimationCurve.prototype.addParams = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the params field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.AnimationCurve.prototype.paramsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the params field has a value. + */ +sketchology.proto.AnimationCurve.prototype.hasParams = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the params field. + */ +sketchology.proto.AnimationCurve.prototype.paramsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the params field. + */ +sketchology.proto.AnimationCurve.prototype.clearParams = function() { + this.clear$Field(2); +}; + + + +/** + * Message ColorAnimation. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ColorAnimation = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ColorAnimation, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ColorAnimation.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ColorAnimation} The cloned message. + * @override + */ +sketchology.proto.ColorAnimation.prototype.clone; + + +/** + * Gets the value of the duration field. + * @return {?number} The value. + */ +sketchology.proto.ColorAnimation.prototype.getDuration = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the duration field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ColorAnimation.prototype.getDurationOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the duration field. + * @param {number} value The value. + */ +sketchology.proto.ColorAnimation.prototype.setDuration = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the duration field has a value. + */ +sketchology.proto.ColorAnimation.prototype.hasDuration = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the duration field. + */ +sketchology.proto.ColorAnimation.prototype.durationCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the duration field. + */ +sketchology.proto.ColorAnimation.prototype.clearDuration = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the curve field. + * @return {?sketchology.proto.AnimationCurve} The value. + */ +sketchology.proto.ColorAnimation.prototype.getCurve = function() { + return /** @type {?sketchology.proto.AnimationCurve} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the curve field or the default value if not set. + * @return {!sketchology.proto.AnimationCurve} The value. + */ +sketchology.proto.ColorAnimation.prototype.getCurveOrDefault = function() { + return /** @type {!sketchology.proto.AnimationCurve} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the curve field. + * @param {!sketchology.proto.AnimationCurve} value The value. + */ +sketchology.proto.ColorAnimation.prototype.setCurve = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the curve field has a value. + */ +sketchology.proto.ColorAnimation.prototype.hasCurve = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the curve field. + */ +sketchology.proto.ColorAnimation.prototype.curveCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the curve field. + */ +sketchology.proto.ColorAnimation.prototype.clearCurve = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.ColorAnimation.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ColorAnimation.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.ColorAnimation.prototype.setRgba = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.ColorAnimation.prototype.hasRgba = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.ColorAnimation.prototype.rgbaCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.ColorAnimation.prototype.clearRgba = function() { + this.clear$Field(3); +}; + + + +/** + * Message ScaleAnimation. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ScaleAnimation = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ScaleAnimation, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ScaleAnimation.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ScaleAnimation} The cloned message. + * @override + */ +sketchology.proto.ScaleAnimation.prototype.clone; + + +/** + * Gets the value of the duration field. + * @return {?number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getDuration = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the duration field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getDurationOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the duration field. + * @param {number} value The value. + */ +sketchology.proto.ScaleAnimation.prototype.setDuration = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the duration field has a value. + */ +sketchology.proto.ScaleAnimation.prototype.hasDuration = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the duration field. + */ +sketchology.proto.ScaleAnimation.prototype.durationCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the duration field. + */ +sketchology.proto.ScaleAnimation.prototype.clearDuration = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the curve field. + * @return {?sketchology.proto.AnimationCurve} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getCurve = function() { + return /** @type {?sketchology.proto.AnimationCurve} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the curve field or the default value if not set. + * @return {!sketchology.proto.AnimationCurve} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getCurveOrDefault = function() { + return /** @type {!sketchology.proto.AnimationCurve} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the curve field. + * @param {!sketchology.proto.AnimationCurve} value The value. + */ +sketchology.proto.ScaleAnimation.prototype.setCurve = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the curve field has a value. + */ +sketchology.proto.ScaleAnimation.prototype.hasCurve = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the curve field. + */ +sketchology.proto.ScaleAnimation.prototype.curveCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the curve field. + */ +sketchology.proto.ScaleAnimation.prototype.clearCurve = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the scale_x field. + * @return {?number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getScaleX = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the scale_x field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getScaleXOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the scale_x field. + * @param {number} value The value. + */ +sketchology.proto.ScaleAnimation.prototype.setScaleX = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the scale_x field has a value. + */ +sketchology.proto.ScaleAnimation.prototype.hasScaleX = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the scale_x field. + */ +sketchology.proto.ScaleAnimation.prototype.scaleXCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the scale_x field. + */ +sketchology.proto.ScaleAnimation.prototype.clearScaleX = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the scale_y field. + * @return {?number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getScaleY = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the scale_y field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ScaleAnimation.prototype.getScaleYOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the scale_y field. + * @param {number} value The value. + */ +sketchology.proto.ScaleAnimation.prototype.setScaleY = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the scale_y field has a value. + */ +sketchology.proto.ScaleAnimation.prototype.hasScaleY = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the scale_y field. + */ +sketchology.proto.ScaleAnimation.prototype.scaleYCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the scale_y field. + */ +sketchology.proto.ScaleAnimation.prototype.clearScaleY = function() { + this.clear$Field(4); +}; + + + +/** + * Message ElementAnimation. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementAnimation = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementAnimation, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementAnimation.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementAnimation} The cloned message. + * @override + */ +sketchology.proto.ElementAnimation.prototype.clone; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.ElementAnimation.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ElementAnimation.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.ElementAnimation.prototype.setUuid = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ElementAnimation.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ElementAnimation.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ElementAnimation.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the color_animation field. + * @return {?sketchology.proto.ColorAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getColorAnimation = function() { + return /** @type {?sketchology.proto.ColorAnimation} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the color_animation field or the default value if not set. + * @return {!sketchology.proto.ColorAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getColorAnimationOrDefault = function() { + return /** @type {!sketchology.proto.ColorAnimation} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the color_animation field. + * @param {!sketchology.proto.ColorAnimation} value The value. + */ +sketchology.proto.ElementAnimation.prototype.setColorAnimation = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the color_animation field has a value. + */ +sketchology.proto.ElementAnimation.prototype.hasColorAnimation = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the color_animation field. + */ +sketchology.proto.ElementAnimation.prototype.colorAnimationCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the color_animation field. + */ +sketchology.proto.ElementAnimation.prototype.clearColorAnimation = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the scale_animation field. + * @return {?sketchology.proto.ScaleAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getScaleAnimation = function() { + return /** @type {?sketchology.proto.ScaleAnimation} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the scale_animation field or the default value if not set. + * @return {!sketchology.proto.ScaleAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getScaleAnimationOrDefault = function() { + return /** @type {!sketchology.proto.ScaleAnimation} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the scale_animation field. + * @param {!sketchology.proto.ScaleAnimation} value The value. + */ +sketchology.proto.ElementAnimation.prototype.setScaleAnimation = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the scale_animation field has a value. + */ +sketchology.proto.ElementAnimation.prototype.hasScaleAnimation = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the scale_animation field. + */ +sketchology.proto.ElementAnimation.prototype.scaleAnimationCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the scale_animation field. + */ +sketchology.proto.ElementAnimation.prototype.clearScaleAnimation = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the next field. + * @return {?sketchology.proto.ElementAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getNext = function() { + return /** @type {?sketchology.proto.ElementAnimation} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the next field or the default value if not set. + * @return {!sketchology.proto.ElementAnimation} The value. + */ +sketchology.proto.ElementAnimation.prototype.getNextOrDefault = function() { + return /** @type {!sketchology.proto.ElementAnimation} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the next field. + * @param {!sketchology.proto.ElementAnimation} value The value. + */ +sketchology.proto.ElementAnimation.prototype.setNext = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the next field has a value. + */ +sketchology.proto.ElementAnimation.prototype.hasNext = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the next field. + */ +sketchology.proto.ElementAnimation.prototype.nextCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the next field. + */ +sketchology.proto.ElementAnimation.prototype.clearNext = function() { + this.clear$Field(4); +}; + + +/** @override */ +sketchology.proto.AnimationCurve.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.AnimationCurve.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'AnimationCurve', + fullName: 'sketchology.proto.AnimationCurve' + }, + 1: { + name: 'type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.CurveType.EASE_IN_OUT, + type: sketchology.proto.CurveType + }, + 2: { + name: 'params', + repeated: true, + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.AnimationCurve.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.AnimationCurve, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.AnimationCurve.getDescriptor = + sketchology.proto.AnimationCurve.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ColorAnimation.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ColorAnimation.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ColorAnimation', + fullName: 'sketchology.proto.ColorAnimation' + }, + 1: { + name: 'duration', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + defaultValue: 0.5, + type: Number + }, + 2: { + name: 'curve', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AnimationCurve + }, + 3: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.ColorAnimation.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ColorAnimation, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ColorAnimation.getDescriptor = + sketchology.proto.ColorAnimation.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ScaleAnimation.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ScaleAnimation.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ScaleAnimation', + fullName: 'sketchology.proto.ScaleAnimation' + }, + 1: { + name: 'duration', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + defaultValue: 0.5, + type: Number + }, + 2: { + name: 'curve', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AnimationCurve + }, + 3: { + name: 'scale_x', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 4: { + name: 'scale_y', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.ScaleAnimation.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ScaleAnimation, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ScaleAnimation.getDescriptor = + sketchology.proto.ScaleAnimation.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementAnimation.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementAnimation.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementAnimation', + fullName: 'sketchology.proto.ElementAnimation' + }, + 1: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'color_animation', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ColorAnimation + }, + 3: { + name: 'scale_animation', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ScaleAnimation + }, + 4: { + name: 'next', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementAnimation + } + }; + sketchology.proto.ElementAnimation.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementAnimation, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementAnimation.getDescriptor = + sketchology.proto.ElementAnimation.prototype.getDescriptor;
diff --git a/third_party/ink/sketchology/proto/document.pb.js b/third_party/ink/sketchology/proto/document.pb.js new file mode 100644 index 0000000..1826d1c5 --- /dev/null +++ b/third_party/ink/sketchology/proto/document.pb.js
@@ -0,0 +1,2831 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * third_party/sketchology/proto/document.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('sketchology.proto.Color'); +goog.provide('sketchology.proto.BackgroundColor'); +goog.provide('sketchology.proto.PageProperties'); +goog.provide('sketchology.proto.AddAction'); +goog.provide('sketchology.proto.RemoveAction'); +goog.provide('sketchology.proto.ClearAction'); +goog.provide('sketchology.proto.ReplaceAction'); +goog.provide('sketchology.proto.SetTransformAction'); +goog.provide('sketchology.proto.SetPageBoundsAction'); +goog.provide('sketchology.proto.StorageAction'); +goog.provide('sketchology.proto.Snapshot'); +goog.provide('sketchology.proto.MutationPacket'); +goog.provide('sketchology.proto.StorageActionState'); +goog.provide('sketchology.proto.ElementState'); + +goog.require('goog.proto2.Message'); +goog.require('sketchology.proto.AffineTransform'); +goog.require('sketchology.proto.BackgroundImageInfo'); +goog.require('sketchology.proto.Border'); +goog.require('sketchology.proto.ElementBundle'); +goog.require('sketchology.proto.Rect'); + + +/** + * Enumeration StorageActionState. + * @enum {number} + */ +sketchology.proto.StorageActionState = { + APPLIED: 1, + UNDONE: 2 +}; + + +/** + * Enumeration ElementState. + * @enum {number} + */ +sketchology.proto.ElementState = { + ALIVE: 1, + DEAD: 2 +}; + + + +/** + * Message Color. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Color = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Color, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Color.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Color} The cloned message. + * @override + */ +sketchology.proto.Color.prototype.clone; + + +/** + * Gets the value of the argb field. + * @return {?number} The value. + */ +sketchology.proto.Color.prototype.getArgb = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the argb field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Color.prototype.getArgbOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the argb field. + * @param {number} value The value. + */ +sketchology.proto.Color.prototype.setArgb = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the argb field has a value. + */ +sketchology.proto.Color.prototype.hasArgb = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the argb field. + */ +sketchology.proto.Color.prototype.argbCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the argb field. + */ +sketchology.proto.Color.prototype.clearArgb = function() { + this.clear$Field(1); +}; + + + +/** + * Message BackgroundColor. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.BackgroundColor = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.BackgroundColor, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.BackgroundColor.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.BackgroundColor} The cloned message. + * @override + */ +sketchology.proto.BackgroundColor.prototype.clone; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.BackgroundColor.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.BackgroundColor.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.BackgroundColor.prototype.setRgba = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.BackgroundColor.prototype.hasRgba = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.BackgroundColor.prototype.rgbaCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.BackgroundColor.prototype.clearRgba = function() { + this.clear$Field(1); +}; + + + +/** + * Message PageProperties. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.PageProperties = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.PageProperties, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.PageProperties.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.PageProperties} The cloned message. + * @override + */ +sketchology.proto.PageProperties.prototype.clone; + + +/** + * Gets the value of the background_color field. + * @return {?sketchology.proto.Color} The value. + */ +sketchology.proto.PageProperties.prototype.getBackgroundColor = function() { + return /** @type {?sketchology.proto.Color} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the background_color field or the default value if not set. + * @return {!sketchology.proto.Color} The value. + */ +sketchology.proto.PageProperties.prototype.getBackgroundColorOrDefault = function() { + return /** @type {!sketchology.proto.Color} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the background_color field. + * @param {!sketchology.proto.Color} value The value. + */ +sketchology.proto.PageProperties.prototype.setBackgroundColor = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the background_color field has a value. + */ +sketchology.proto.PageProperties.prototype.hasBackgroundColor = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the background_color field. + */ +sketchology.proto.PageProperties.prototype.backgroundColorCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the background_color field. + */ +sketchology.proto.PageProperties.prototype.clearBackgroundColor = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the background_image field. + * @return {?sketchology.proto.BackgroundImageInfo} The value. + */ +sketchology.proto.PageProperties.prototype.getBackgroundImage = function() { + return /** @type {?sketchology.proto.BackgroundImageInfo} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the background_image field or the default value if not set. + * @return {!sketchology.proto.BackgroundImageInfo} The value. + */ +sketchology.proto.PageProperties.prototype.getBackgroundImageOrDefault = function() { + return /** @type {!sketchology.proto.BackgroundImageInfo} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the background_image field. + * @param {!sketchology.proto.BackgroundImageInfo} value The value. + */ +sketchology.proto.PageProperties.prototype.setBackgroundImage = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the background_image field has a value. + */ +sketchology.proto.PageProperties.prototype.hasBackgroundImage = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the background_image field. + */ +sketchology.proto.PageProperties.prototype.backgroundImageCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the background_image field. + */ +sketchology.proto.PageProperties.prototype.clearBackgroundImage = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.PageProperties.prototype.getBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.PageProperties.prototype.getBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.PageProperties.prototype.setBounds = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the bounds field has a value. + */ +sketchology.proto.PageProperties.prototype.hasBounds = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the bounds field. + */ +sketchology.proto.PageProperties.prototype.boundsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the bounds field. + */ +sketchology.proto.PageProperties.prototype.clearBounds = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the border field. + * @return {?sketchology.proto.Border} The value. + */ +sketchology.proto.PageProperties.prototype.getBorder = function() { + return /** @type {?sketchology.proto.Border} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the border field or the default value if not set. + * @return {!sketchology.proto.Border} The value. + */ +sketchology.proto.PageProperties.prototype.getBorderOrDefault = function() { + return /** @type {!sketchology.proto.Border} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the border field. + * @param {!sketchology.proto.Border} value The value. + */ +sketchology.proto.PageProperties.prototype.setBorder = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the border field has a value. + */ +sketchology.proto.PageProperties.prototype.hasBorder = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the border field. + */ +sketchology.proto.PageProperties.prototype.borderCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the border field. + */ +sketchology.proto.PageProperties.prototype.clearBorder = function() { + this.clear$Field(4); +}; + + + +/** + * Message AddAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.AddAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.AddAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.AddAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.AddAction} The cloned message. + * @override + */ +sketchology.proto.AddAction.prototype.clone; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.AddAction.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.AddAction.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.AddAction.prototype.setUuid = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.AddAction.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.AddAction.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.AddAction.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the below_element_with_uuid field. + * @return {?string} The value. + */ +sketchology.proto.AddAction.prototype.getBelowElementWithUuid = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the below_element_with_uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.AddAction.prototype.getBelowElementWithUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the below_element_with_uuid field. + * @param {string} value The value. + */ +sketchology.proto.AddAction.prototype.setBelowElementWithUuid = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the below_element_with_uuid field has a value. + */ +sketchology.proto.AddAction.prototype.hasBelowElementWithUuid = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the below_element_with_uuid field. + */ +sketchology.proto.AddAction.prototype.belowElementWithUuidCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the below_element_with_uuid field. + */ +sketchology.proto.AddAction.prototype.clearBelowElementWithUuid = function() { + this.clear$Field(2); +}; + + + +/** + * Message RemoveAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.RemoveAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.RemoveAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.RemoveAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.RemoveAction} The cloned message. + * @override + */ +sketchology.proto.RemoveAction.prototype.clone; + + +/** + * Gets the value of the uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.RemoveAction.prototype.getUuid = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.RemoveAction.prototype.getUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.RemoveAction.prototype.addUuid = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.RemoveAction.prototype.uuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.RemoveAction.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.RemoveAction.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.RemoveAction.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the was_below_uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.RemoveAction.prototype.getWasBelowUuid = function(index) { + return /** @type {?string} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the was_below_uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.RemoveAction.prototype.getWasBelowUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the was_below_uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.RemoveAction.prototype.addWasBelowUuid = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the was_below_uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.RemoveAction.prototype.wasBelowUuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the was_below_uuid field has a value. + */ +sketchology.proto.RemoveAction.prototype.hasWasBelowUuid = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the was_below_uuid field. + */ +sketchology.proto.RemoveAction.prototype.wasBelowUuidCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the was_below_uuid field. + */ +sketchology.proto.RemoveAction.prototype.clearWasBelowUuid = function() { + this.clear$Field(2); +}; + + + +/** + * Message ClearAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ClearAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ClearAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ClearAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ClearAction} The cloned message. + * @override + */ +sketchology.proto.ClearAction.prototype.clone; + + +/** + * Gets the value of the uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ClearAction.prototype.getUuid = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ClearAction.prototype.getUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.ClearAction.prototype.addUuid = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ClearAction.prototype.uuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ClearAction.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ClearAction.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ClearAction.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + + +/** + * Message ReplaceAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ReplaceAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ReplaceAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ReplaceAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ReplaceAction} The cloned message. + * @override + */ +sketchology.proto.ReplaceAction.prototype.clone; + + +/** + * Gets the value of the uuid_add field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getUuidAdd = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid_add field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getUuidAddOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid_add field. + * @param {string} value The value to add. + */ +sketchology.proto.ReplaceAction.prototype.addUuidAdd = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid_add field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ReplaceAction.prototype.uuidAddArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid_add field has a value. + */ +sketchology.proto.ReplaceAction.prototype.hasUuidAdd = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid_add field. + */ +sketchology.proto.ReplaceAction.prototype.uuidAddCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid_add field. + */ +sketchology.proto.ReplaceAction.prototype.clearUuidAdd = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the below_element_with_uuid field. + * @return {?string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getBelowElementWithUuid = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the below_element_with_uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getBelowElementWithUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the below_element_with_uuid field. + * @param {string} value The value. + */ +sketchology.proto.ReplaceAction.prototype.setBelowElementWithUuid = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the below_element_with_uuid field has a value. + */ +sketchology.proto.ReplaceAction.prototype.hasBelowElementWithUuid = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the below_element_with_uuid field. + */ +sketchology.proto.ReplaceAction.prototype.belowElementWithUuidCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the below_element_with_uuid field. + */ +sketchology.proto.ReplaceAction.prototype.clearBelowElementWithUuid = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the uuid_remove field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getUuidRemove = function(index) { + return /** @type {?string} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the uuid_remove field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getUuidRemoveOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the uuid_remove field. + * @param {string} value The value to add. + */ +sketchology.proto.ReplaceAction.prototype.addUuidRemove = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the uuid_remove field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ReplaceAction.prototype.uuidRemoveArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the uuid_remove field has a value. + */ +sketchology.proto.ReplaceAction.prototype.hasUuidRemove = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the uuid_remove field. + */ +sketchology.proto.ReplaceAction.prototype.uuidRemoveCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the uuid_remove field. + */ +sketchology.proto.ReplaceAction.prototype.clearUuidRemove = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the was_below_uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getWasBelowUuid = function(index) { + return /** @type {?string} */ (this.get$Value(4, index)); +}; + + +/** + * Gets the value of the was_below_uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ReplaceAction.prototype.getWasBelowUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(4, index)); +}; + + +/** + * Adds a value to the was_below_uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.ReplaceAction.prototype.addWasBelowUuid = function(value) { + this.add$Value(4, value); +}; + + +/** + * Returns the array of values in the was_below_uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ReplaceAction.prototype.wasBelowUuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(4)); +}; + + +/** + * @return {boolean} Whether the was_below_uuid field has a value. + */ +sketchology.proto.ReplaceAction.prototype.hasWasBelowUuid = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the was_below_uuid field. + */ +sketchology.proto.ReplaceAction.prototype.wasBelowUuidCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the was_below_uuid field. + */ +sketchology.proto.ReplaceAction.prototype.clearWasBelowUuid = function() { + this.clear$Field(4); +}; + + + +/** + * Message SetTransformAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SetTransformAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SetTransformAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SetTransformAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SetTransformAction} The cloned message. + * @override + */ +sketchology.proto.SetTransformAction.prototype.clone; + + +/** + * Gets the value of the uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.SetTransformAction.prototype.getUuid = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.SetTransformAction.prototype.getUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.SetTransformAction.prototype.addUuid = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.SetTransformAction.prototype.uuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.SetTransformAction.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.SetTransformAction.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.SetTransformAction.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the from_transform field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.SetTransformAction.prototype.getFromTransform = function(index) { + return /** @type {?sketchology.proto.AffineTransform} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the from_transform field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.SetTransformAction.prototype.getFromTransformOrDefault = function(index) { + return /** @type {!sketchology.proto.AffineTransform} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the from_transform field. + * @param {!sketchology.proto.AffineTransform} value The value to add. + */ +sketchology.proto.SetTransformAction.prototype.addFromTransform = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the from_transform field. + * @return {!Array<!sketchology.proto.AffineTransform>} The values in the field. + */ +sketchology.proto.SetTransformAction.prototype.fromTransformArray = function() { + return /** @type {!Array<!sketchology.proto.AffineTransform>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the from_transform field has a value. + */ +sketchology.proto.SetTransformAction.prototype.hasFromTransform = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the from_transform field. + */ +sketchology.proto.SetTransformAction.prototype.fromTransformCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the from_transform field. + */ +sketchology.proto.SetTransformAction.prototype.clearFromTransform = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the to_transform field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.SetTransformAction.prototype.getToTransform = function(index) { + return /** @type {?sketchology.proto.AffineTransform} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the to_transform field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.SetTransformAction.prototype.getToTransformOrDefault = function(index) { + return /** @type {!sketchology.proto.AffineTransform} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the to_transform field. + * @param {!sketchology.proto.AffineTransform} value The value to add. + */ +sketchology.proto.SetTransformAction.prototype.addToTransform = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the to_transform field. + * @return {!Array<!sketchology.proto.AffineTransform>} The values in the field. + */ +sketchology.proto.SetTransformAction.prototype.toTransformArray = function() { + return /** @type {!Array<!sketchology.proto.AffineTransform>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the to_transform field has a value. + */ +sketchology.proto.SetTransformAction.prototype.hasToTransform = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the to_transform field. + */ +sketchology.proto.SetTransformAction.prototype.toTransformCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the to_transform field. + */ +sketchology.proto.SetTransformAction.prototype.clearToTransform = function() { + this.clear$Field(3); +}; + + + +/** + * Message SetPageBoundsAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SetPageBoundsAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SetPageBoundsAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SetPageBoundsAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SetPageBoundsAction} The cloned message. + * @override + */ +sketchology.proto.SetPageBoundsAction.prototype.clone; + + +/** + * Gets the value of the old_bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.getOldBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the old_bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.getOldBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the old_bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.setOldBounds = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the old_bounds field has a value. + */ +sketchology.proto.SetPageBoundsAction.prototype.hasOldBounds = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the old_bounds field. + */ +sketchology.proto.SetPageBoundsAction.prototype.oldBoundsCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the old_bounds field. + */ +sketchology.proto.SetPageBoundsAction.prototype.clearOldBounds = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the new_bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.getNewBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the new_bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.getNewBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the new_bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.SetPageBoundsAction.prototype.setNewBounds = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the new_bounds field has a value. + */ +sketchology.proto.SetPageBoundsAction.prototype.hasNewBounds = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the new_bounds field. + */ +sketchology.proto.SetPageBoundsAction.prototype.newBoundsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the new_bounds field. + */ +sketchology.proto.SetPageBoundsAction.prototype.clearNewBounds = function() { + this.clear$Field(2); +}; + + + +/** + * Message StorageAction. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.StorageAction = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.StorageAction, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.StorageAction.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.StorageAction} The cloned message. + * @override + */ +sketchology.proto.StorageAction.prototype.clone; + + +/** + * Gets the value of the add_action field. + * @return {?sketchology.proto.AddAction} The value. + */ +sketchology.proto.StorageAction.prototype.getAddAction = function() { + return /** @type {?sketchology.proto.AddAction} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the add_action field or the default value if not set. + * @return {!sketchology.proto.AddAction} The value. + */ +sketchology.proto.StorageAction.prototype.getAddActionOrDefault = function() { + return /** @type {!sketchology.proto.AddAction} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the add_action field. + * @param {!sketchology.proto.AddAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setAddAction = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the add_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasAddAction = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the add_action field. + */ +sketchology.proto.StorageAction.prototype.addActionCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the add_action field. + */ +sketchology.proto.StorageAction.prototype.clearAddAction = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the remove_action field. + * @return {?sketchology.proto.RemoveAction} The value. + */ +sketchology.proto.StorageAction.prototype.getRemoveAction = function() { + return /** @type {?sketchology.proto.RemoveAction} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the remove_action field or the default value if not set. + * @return {!sketchology.proto.RemoveAction} The value. + */ +sketchology.proto.StorageAction.prototype.getRemoveActionOrDefault = function() { + return /** @type {!sketchology.proto.RemoveAction} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the remove_action field. + * @param {!sketchology.proto.RemoveAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setRemoveAction = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the remove_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasRemoveAction = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the remove_action field. + */ +sketchology.proto.StorageAction.prototype.removeActionCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the remove_action field. + */ +sketchology.proto.StorageAction.prototype.clearRemoveAction = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the clear_action field. + * @return {?sketchology.proto.ClearAction} The value. + */ +sketchology.proto.StorageAction.prototype.getClearAction = function() { + return /** @type {?sketchology.proto.ClearAction} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the clear_action field or the default value if not set. + * @return {!sketchology.proto.ClearAction} The value. + */ +sketchology.proto.StorageAction.prototype.getClearActionOrDefault = function() { + return /** @type {!sketchology.proto.ClearAction} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the clear_action field. + * @param {!sketchology.proto.ClearAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setClearAction = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the clear_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasClearAction = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the clear_action field. + */ +sketchology.proto.StorageAction.prototype.clearActionCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the clear_action field. + */ +sketchology.proto.StorageAction.prototype.clearClearAction = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the replace_action field. + * @return {?sketchology.proto.ReplaceAction} The value. + */ +sketchology.proto.StorageAction.prototype.getReplaceAction = function() { + return /** @type {?sketchology.proto.ReplaceAction} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the replace_action field or the default value if not set. + * @return {!sketchology.proto.ReplaceAction} The value. + */ +sketchology.proto.StorageAction.prototype.getReplaceActionOrDefault = function() { + return /** @type {!sketchology.proto.ReplaceAction} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the replace_action field. + * @param {!sketchology.proto.ReplaceAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setReplaceAction = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the replace_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasReplaceAction = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the replace_action field. + */ +sketchology.proto.StorageAction.prototype.replaceActionCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the replace_action field. + */ +sketchology.proto.StorageAction.prototype.clearReplaceAction = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the set_transform_action field. + * @return {?sketchology.proto.SetTransformAction} The value. + */ +sketchology.proto.StorageAction.prototype.getSetTransformAction = function() { + return /** @type {?sketchology.proto.SetTransformAction} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the set_transform_action field or the default value if not set. + * @return {!sketchology.proto.SetTransformAction} The value. + */ +sketchology.proto.StorageAction.prototype.getSetTransformActionOrDefault = function() { + return /** @type {!sketchology.proto.SetTransformAction} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the set_transform_action field. + * @param {!sketchology.proto.SetTransformAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setSetTransformAction = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the set_transform_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasSetTransformAction = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the set_transform_action field. + */ +sketchology.proto.StorageAction.prototype.setTransformActionCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the set_transform_action field. + */ +sketchology.proto.StorageAction.prototype.clearSetTransformAction = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the set_page_bounds_action field. + * @return {?sketchology.proto.SetPageBoundsAction} The value. + */ +sketchology.proto.StorageAction.prototype.getSetPageBoundsAction = function() { + return /** @type {?sketchology.proto.SetPageBoundsAction} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the set_page_bounds_action field or the default value if not set. + * @return {!sketchology.proto.SetPageBoundsAction} The value. + */ +sketchology.proto.StorageAction.prototype.getSetPageBoundsActionOrDefault = function() { + return /** @type {!sketchology.proto.SetPageBoundsAction} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the set_page_bounds_action field. + * @param {!sketchology.proto.SetPageBoundsAction} value The value. + */ +sketchology.proto.StorageAction.prototype.setSetPageBoundsAction = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the set_page_bounds_action field has a value. + */ +sketchology.proto.StorageAction.prototype.hasSetPageBoundsAction = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the set_page_bounds_action field. + */ +sketchology.proto.StorageAction.prototype.setPageBoundsActionCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the set_page_bounds_action field. + */ +sketchology.proto.StorageAction.prototype.clearSetPageBoundsAction = function() { + this.clear$Field(6); +}; + + + +/** + * Message Snapshot. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Snapshot = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Snapshot, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Snapshot.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Snapshot} The cloned message. + * @override + */ +sketchology.proto.Snapshot.prototype.clone; + + +/** + * Gets the value of the page_properties field. + * @return {?sketchology.proto.PageProperties} The value. + */ +sketchology.proto.Snapshot.prototype.getPageProperties = function() { + return /** @type {?sketchology.proto.PageProperties} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the page_properties field or the default value if not set. + * @return {!sketchology.proto.PageProperties} The value. + */ +sketchology.proto.Snapshot.prototype.getPagePropertiesOrDefault = function() { + return /** @type {!sketchology.proto.PageProperties} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the page_properties field. + * @param {!sketchology.proto.PageProperties} value The value. + */ +sketchology.proto.Snapshot.prototype.setPageProperties = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the page_properties field has a value. + */ +sketchology.proto.Snapshot.prototype.hasPageProperties = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the page_properties field. + */ +sketchology.proto.Snapshot.prototype.pagePropertiesCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the page_properties field. + */ +sketchology.proto.Snapshot.prototype.clearPageProperties = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the element field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.Snapshot.prototype.getElement = function(index) { + return /** @type {?sketchology.proto.ElementBundle} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the element field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.Snapshot.prototype.getElementOrDefault = function(index) { + return /** @type {!sketchology.proto.ElementBundle} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the element field. + * @param {!sketchology.proto.ElementBundle} value The value to add. + */ +sketchology.proto.Snapshot.prototype.addElement = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the element field. + * @return {!Array<!sketchology.proto.ElementBundle>} The values in the field. + */ +sketchology.proto.Snapshot.prototype.elementArray = function() { + return /** @type {!Array<!sketchology.proto.ElementBundle>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the element field has a value. + */ +sketchology.proto.Snapshot.prototype.hasElement = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the element field. + */ +sketchology.proto.Snapshot.prototype.elementCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the element field. + */ +sketchology.proto.Snapshot.prototype.clearElement = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the dead_element field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.Snapshot.prototype.getDeadElement = function(index) { + return /** @type {?sketchology.proto.ElementBundle} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the dead_element field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.Snapshot.prototype.getDeadElementOrDefault = function(index) { + return /** @type {!sketchology.proto.ElementBundle} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the dead_element field. + * @param {!sketchology.proto.ElementBundle} value The value to add. + */ +sketchology.proto.Snapshot.prototype.addDeadElement = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the dead_element field. + * @return {!Array<!sketchology.proto.ElementBundle>} The values in the field. + */ +sketchology.proto.Snapshot.prototype.deadElementArray = function() { + return /** @type {!Array<!sketchology.proto.ElementBundle>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the dead_element field has a value. + */ +sketchology.proto.Snapshot.prototype.hasDeadElement = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the dead_element field. + */ +sketchology.proto.Snapshot.prototype.deadElementCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the dead_element field. + */ +sketchology.proto.Snapshot.prototype.clearDeadElement = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the undo_action field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.StorageAction} The value. + */ +sketchology.proto.Snapshot.prototype.getUndoAction = function(index) { + return /** @type {?sketchology.proto.StorageAction} */ (this.get$Value(4, index)); +}; + + +/** + * Gets the value of the undo_action field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.StorageAction} The value. + */ +sketchology.proto.Snapshot.prototype.getUndoActionOrDefault = function(index) { + return /** @type {!sketchology.proto.StorageAction} */ (this.get$ValueOrDefault(4, index)); +}; + + +/** + * Adds a value to the undo_action field. + * @param {!sketchology.proto.StorageAction} value The value to add. + */ +sketchology.proto.Snapshot.prototype.addUndoAction = function(value) { + this.add$Value(4, value); +}; + + +/** + * Returns the array of values in the undo_action field. + * @return {!Array<!sketchology.proto.StorageAction>} The values in the field. + */ +sketchology.proto.Snapshot.prototype.undoActionArray = function() { + return /** @type {!Array<!sketchology.proto.StorageAction>} */ (this.array$Values(4)); +}; + + +/** + * @return {boolean} Whether the undo_action field has a value. + */ +sketchology.proto.Snapshot.prototype.hasUndoAction = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the undo_action field. + */ +sketchology.proto.Snapshot.prototype.undoActionCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the undo_action field. + */ +sketchology.proto.Snapshot.prototype.clearUndoAction = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the redo_action field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.StorageAction} The value. + */ +sketchology.proto.Snapshot.prototype.getRedoAction = function(index) { + return /** @type {?sketchology.proto.StorageAction} */ (this.get$Value(5, index)); +}; + + +/** + * Gets the value of the redo_action field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.StorageAction} The value. + */ +sketchology.proto.Snapshot.prototype.getRedoActionOrDefault = function(index) { + return /** @type {!sketchology.proto.StorageAction} */ (this.get$ValueOrDefault(5, index)); +}; + + +/** + * Adds a value to the redo_action field. + * @param {!sketchology.proto.StorageAction} value The value to add. + */ +sketchology.proto.Snapshot.prototype.addRedoAction = function(value) { + this.add$Value(5, value); +}; + + +/** + * Returns the array of values in the redo_action field. + * @return {!Array<!sketchology.proto.StorageAction>} The values in the field. + */ +sketchology.proto.Snapshot.prototype.redoActionArray = function() { + return /** @type {!Array<!sketchology.proto.StorageAction>} */ (this.array$Values(5)); +}; + + +/** + * @return {boolean} Whether the redo_action field has a value. + */ +sketchology.proto.Snapshot.prototype.hasRedoAction = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the redo_action field. + */ +sketchology.proto.Snapshot.prototype.redoActionCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the redo_action field. + */ +sketchology.proto.Snapshot.prototype.clearRedoAction = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the element_state_index field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.ElementState} The value. + */ +sketchology.proto.Snapshot.prototype.getElementStateIndex = function(index) { + return /** @type {?sketchology.proto.ElementState} */ (this.get$Value(6, index)); +}; + + +/** + * Gets the value of the element_state_index field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.ElementState} The value. + */ +sketchology.proto.Snapshot.prototype.getElementStateIndexOrDefault = function(index) { + return /** @type {!sketchology.proto.ElementState} */ (this.get$ValueOrDefault(6, index)); +}; + + +/** + * Adds a value to the element_state_index field. + * @param {!sketchology.proto.ElementState} value The value to add. + */ +sketchology.proto.Snapshot.prototype.addElementStateIndex = function(value) { + this.add$Value(6, value); +}; + + +/** + * Returns the array of values in the element_state_index field. + * @return {!Array<!sketchology.proto.ElementState>} The values in the field. + */ +sketchology.proto.Snapshot.prototype.elementStateIndexArray = function() { + return /** @type {!Array<!sketchology.proto.ElementState>} */ (this.array$Values(6)); +}; + + +/** + * @return {boolean} Whether the element_state_index field has a value. + */ +sketchology.proto.Snapshot.prototype.hasElementStateIndex = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the element_state_index field. + */ +sketchology.proto.Snapshot.prototype.elementStateIndexCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the element_state_index field. + */ +sketchology.proto.Snapshot.prototype.clearElementStateIndex = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the fingerprint field. + * @return {?string} The value. + */ +sketchology.proto.Snapshot.prototype.getFingerprint = function() { + return /** @type {?string} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the fingerprint field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.Snapshot.prototype.getFingerprintOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the fingerprint field. + * @param {string} value The value. + */ +sketchology.proto.Snapshot.prototype.setFingerprint = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the fingerprint field has a value. + */ +sketchology.proto.Snapshot.prototype.hasFingerprint = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the fingerprint field. + */ +sketchology.proto.Snapshot.prototype.fingerprintCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the fingerprint field. + */ +sketchology.proto.Snapshot.prototype.clearFingerprint = function() { + this.clear$Field(7); +}; + + + +/** + * Message MutationPacket. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.MutationPacket = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.MutationPacket, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.MutationPacket.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.MutationPacket} The cloned message. + * @override + */ +sketchology.proto.MutationPacket.prototype.clone; + + +/** + * Gets the value of the mutation field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.StorageAction} The value. + */ +sketchology.proto.MutationPacket.prototype.getMutation = function(index) { + return /** @type {?sketchology.proto.StorageAction} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the mutation field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.StorageAction} The value. + */ +sketchology.proto.MutationPacket.prototype.getMutationOrDefault = function(index) { + return /** @type {!sketchology.proto.StorageAction} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the mutation field. + * @param {!sketchology.proto.StorageAction} value The value to add. + */ +sketchology.proto.MutationPacket.prototype.addMutation = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the mutation field. + * @return {!Array<!sketchology.proto.StorageAction>} The values in the field. + */ +sketchology.proto.MutationPacket.prototype.mutationArray = function() { + return /** @type {!Array<!sketchology.proto.StorageAction>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the mutation field has a value. + */ +sketchology.proto.MutationPacket.prototype.hasMutation = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the mutation field. + */ +sketchology.proto.MutationPacket.prototype.mutationCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the mutation field. + */ +sketchology.proto.MutationPacket.prototype.clearMutation = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the element field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.MutationPacket.prototype.getElement = function(index) { + return /** @type {?sketchology.proto.ElementBundle} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the element field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.MutationPacket.prototype.getElementOrDefault = function(index) { + return /** @type {!sketchology.proto.ElementBundle} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the element field. + * @param {!sketchology.proto.ElementBundle} value The value to add. + */ +sketchology.proto.MutationPacket.prototype.addElement = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the element field. + * @return {!Array<!sketchology.proto.ElementBundle>} The values in the field. + */ +sketchology.proto.MutationPacket.prototype.elementArray = function() { + return /** @type {!Array<!sketchology.proto.ElementBundle>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the element field has a value. + */ +sketchology.proto.MutationPacket.prototype.hasElement = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the element field. + */ +sketchology.proto.MutationPacket.prototype.elementCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the element field. + */ +sketchology.proto.MutationPacket.prototype.clearElement = function() { + this.clear$Field(2); +}; + + +/** @override */ +sketchology.proto.Color.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Color.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Color', + fullName: 'sketchology.proto.Color' + }, + 1: { + name: 'argb', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.Color.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Color, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Color.getDescriptor = + sketchology.proto.Color.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.BackgroundColor.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.BackgroundColor.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'BackgroundColor', + fullName: 'sketchology.proto.BackgroundColor' + }, + 1: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.BackgroundColor.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.BackgroundColor, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.BackgroundColor.getDescriptor = + sketchology.proto.BackgroundColor.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.PageProperties.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.PageProperties.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'PageProperties', + fullName: 'sketchology.proto.PageProperties' + }, + 1: { + name: 'background_color', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Color + }, + 2: { + name: 'background_image', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.BackgroundImageInfo + }, + 3: { + name: 'bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 4: { + name: 'border', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Border + } + }; + sketchology.proto.PageProperties.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.PageProperties, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.PageProperties.getDescriptor = + sketchology.proto.PageProperties.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.AddAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.AddAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'AddAction', + fullName: 'sketchology.proto.AddAction' + }, + 1: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'below_element_with_uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.AddAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.AddAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.AddAction.getDescriptor = + sketchology.proto.AddAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.RemoveAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.RemoveAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'RemoveAction', + fullName: 'sketchology.proto.RemoveAction' + }, + 1: { + name: 'uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'was_below_uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.RemoveAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.RemoveAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.RemoveAction.getDescriptor = + sketchology.proto.RemoveAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ClearAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ClearAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ClearAction', + fullName: 'sketchology.proto.ClearAction' + }, + 1: { + name: 'uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.ClearAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ClearAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ClearAction.getDescriptor = + sketchology.proto.ClearAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ReplaceAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ReplaceAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ReplaceAction', + fullName: 'sketchology.proto.ReplaceAction' + }, + 1: { + name: 'uuid_add', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'below_element_with_uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 3: { + name: 'uuid_remove', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 4: { + name: 'was_below_uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.ReplaceAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ReplaceAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ReplaceAction.getDescriptor = + sketchology.proto.ReplaceAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SetTransformAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SetTransformAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SetTransformAction', + fullName: 'sketchology.proto.SetTransformAction' + }, + 1: { + name: 'uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'from_transform', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AffineTransform + }, + 3: { + name: 'to_transform', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AffineTransform + } + }; + sketchology.proto.SetTransformAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SetTransformAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SetTransformAction.getDescriptor = + sketchology.proto.SetTransformAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SetPageBoundsAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SetPageBoundsAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SetPageBoundsAction', + fullName: 'sketchology.proto.SetPageBoundsAction' + }, + 1: { + name: 'old_bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 2: { + name: 'new_bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + } + }; + sketchology.proto.SetPageBoundsAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SetPageBoundsAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SetPageBoundsAction.getDescriptor = + sketchology.proto.SetPageBoundsAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.StorageAction.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.StorageAction.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'StorageAction', + fullName: 'sketchology.proto.StorageAction' + }, + 1: { + name: 'add_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AddAction + }, + 2: { + name: 'remove_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.RemoveAction + }, + 3: { + name: 'clear_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ClearAction + }, + 4: { + name: 'replace_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ReplaceAction + }, + 5: { + name: 'set_transform_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SetTransformAction + }, + 6: { + name: 'set_page_bounds_action', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SetPageBoundsAction + } + }; + sketchology.proto.StorageAction.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.StorageAction, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.StorageAction.getDescriptor = + sketchology.proto.StorageAction.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Snapshot.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Snapshot.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Snapshot', + fullName: 'sketchology.proto.Snapshot' + }, + 1: { + name: 'page_properties', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.PageProperties + }, + 2: { + name: 'element', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementBundle + }, + 3: { + name: 'dead_element', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementBundle + }, + 4: { + name: 'undo_action', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.StorageAction + }, + 5: { + name: 'redo_action', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.StorageAction + }, + 6: { + name: 'element_state_index', + repeated: true, + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.ElementState.ALIVE, + type: sketchology.proto.ElementState + }, + 7: { + name: 'fingerprint', + fieldType: goog.proto2.Message.FieldType.UINT64, + type: String + } + }; + sketchology.proto.Snapshot.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Snapshot, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Snapshot.getDescriptor = + sketchology.proto.Snapshot.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.MutationPacket.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.MutationPacket.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'MutationPacket', + fullName: 'sketchology.proto.MutationPacket' + }, + 1: { + name: 'mutation', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.StorageAction + }, + 2: { + name: 'element', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementBundle + } + }; + sketchology.proto.MutationPacket.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.MutationPacket, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.MutationPacket.getDescriptor = + sketchology.proto.MutationPacket.prototype.getDescriptor;
diff --git a/third_party/ink/sketchology/proto/elements.pb.js b/third_party/ink/sketchology/proto/elements.pb.js new file mode 100644 index 0000000..4c0de6a6 --- /dev/null +++ b/third_party/ink/sketchology/proto/elements.pb.js
@@ -0,0 +1,3976 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * third_party/sketchology/proto/elements.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('sketchology.proto.CallbackFlags'); +goog.provide('sketchology.proto.SourceDetails'); +goog.provide('sketchology.proto.SourceDetails.Origin'); +goog.provide('sketchology.proto.BackgroundImageInfo'); +goog.provide('sketchology.proto.Border'); +goog.provide('sketchology.proto.LOD'); +goog.provide('sketchology.proto.Stroke'); +goog.provide('sketchology.proto.UncompressedStroke'); +goog.provide('sketchology.proto.AffineTransform'); +goog.provide('sketchology.proto.Element'); +goog.provide('sketchology.proto.ElementAttributes'); +goog.provide('sketchology.proto.UncompressedElement'); +goog.provide('sketchology.proto.ElementMutation'); +goog.provide('sketchology.proto.ElementIdList'); +goog.provide('sketchology.proto.Point'); +goog.provide('sketchology.proto.ElementBundle'); +goog.provide('sketchology.proto.Path'); +goog.provide('sketchology.proto.Path.SegmentType'); +goog.provide('sketchology.proto.Path.EndCapType'); +goog.provide('sketchology.proto.ShaderType'); + +goog.require('goog.proto2.Message'); +goog.require('sketchology.proto.Rect'); + + +/** + * Enumeration ShaderType. + * @enum {number} + */ +sketchology.proto.ShaderType = { + NONE: 0, + VERTEX_COLORED: 1, + SOLID_COLORED: 2, + ERASE: 3, + VERTEX_TEXTURED: 4 +}; + + + +/** + * Message CallbackFlags. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.CallbackFlags = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.CallbackFlags, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.CallbackFlags.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.CallbackFlags} The cloned message. + * @override + */ +sketchology.proto.CallbackFlags.prototype.clone; + + +/** + * Gets the value of the mesh_data_ctm field. + * @return {?boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getMeshDataCtm = function() { + return /** @type {?boolean} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the mesh_data_ctm field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getMeshDataCtmOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the mesh_data_ctm field. + * @param {boolean} value The value. + */ +sketchology.proto.CallbackFlags.prototype.setMeshDataCtm = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the mesh_data_ctm field has a value. + */ +sketchology.proto.CallbackFlags.prototype.hasMeshDataCtm = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the mesh_data_ctm field. + */ +sketchology.proto.CallbackFlags.prototype.meshDataCtmCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the mesh_data_ctm field. + */ +sketchology.proto.CallbackFlags.prototype.clearMeshDataCtm = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the uncompressed_outline field. + * @return {?boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getUncompressedOutline = function() { + return /** @type {?boolean} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the uncompressed_outline field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getUncompressedOutlineOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the uncompressed_outline field. + * @param {boolean} value The value. + */ +sketchology.proto.CallbackFlags.prototype.setUncompressedOutline = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the uncompressed_outline field has a value. + */ +sketchology.proto.CallbackFlags.prototype.hasUncompressedOutline = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the uncompressed_outline field. + */ +sketchology.proto.CallbackFlags.prototype.uncompressedOutlineCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the uncompressed_outline field. + */ +sketchology.proto.CallbackFlags.prototype.clearUncompressedOutline = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the compressed_input_points field. + * @return {?boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getCompressedInputPoints = function() { + return /** @type {?boolean} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the compressed_input_points field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.CallbackFlags.prototype.getCompressedInputPointsOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the compressed_input_points field. + * @param {boolean} value The value. + */ +sketchology.proto.CallbackFlags.prototype.setCompressedInputPoints = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the compressed_input_points field has a value. + */ +sketchology.proto.CallbackFlags.prototype.hasCompressedInputPoints = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the compressed_input_points field. + */ +sketchology.proto.CallbackFlags.prototype.compressedInputPointsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the compressed_input_points field. + */ +sketchology.proto.CallbackFlags.prototype.clearCompressedInputPoints = function() { + this.clear$Field(3); +}; + + + +/** + * Message SourceDetails. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SourceDetails = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SourceDetails, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SourceDetails.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SourceDetails} The cloned message. + * @override + */ +sketchology.proto.SourceDetails.prototype.clone; + + +/** + * Gets the value of the origin field. + * @return {?sketchology.proto.SourceDetails.Origin} The value. + */ +sketchology.proto.SourceDetails.prototype.getOrigin = function() { + return /** @type {?sketchology.proto.SourceDetails.Origin} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the origin field or the default value if not set. + * @return {!sketchology.proto.SourceDetails.Origin} The value. + */ +sketchology.proto.SourceDetails.prototype.getOriginOrDefault = function() { + return /** @type {!sketchology.proto.SourceDetails.Origin} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the origin field. + * @param {!sketchology.proto.SourceDetails.Origin} value The value. + */ +sketchology.proto.SourceDetails.prototype.setOrigin = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the origin field has a value. + */ +sketchology.proto.SourceDetails.prototype.hasOrigin = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the origin field. + */ +sketchology.proto.SourceDetails.prototype.originCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the origin field. + */ +sketchology.proto.SourceDetails.prototype.clearOrigin = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the host_source_details field. + * @return {?number} The value. + */ +sketchology.proto.SourceDetails.prototype.getHostSourceDetails = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the host_source_details field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SourceDetails.prototype.getHostSourceDetailsOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the host_source_details field. + * @param {number} value The value. + */ +sketchology.proto.SourceDetails.prototype.setHostSourceDetails = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the host_source_details field has a value. + */ +sketchology.proto.SourceDetails.prototype.hasHostSourceDetails = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the host_source_details field. + */ +sketchology.proto.SourceDetails.prototype.hostSourceDetailsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the host_source_details field. + */ +sketchology.proto.SourceDetails.prototype.clearHostSourceDetails = function() { + this.clear$Field(2); +}; + + +/** + * Enumeration Origin. + * @enum {number} + */ +sketchology.proto.SourceDetails.Origin = { + UNKNOWN: 0, + ENGINE: 1, + HOST: 2 +}; + + + +/** + * Message BackgroundImageInfo. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.BackgroundImageInfo = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.BackgroundImageInfo, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.BackgroundImageInfo.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.BackgroundImageInfo} The cloned message. + * @override + */ +sketchology.proto.BackgroundImageInfo.prototype.clone; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.setUri = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.BackgroundImageInfo.prototype.hasUri = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.BackgroundImageInfo.prototype.uriCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.BackgroundImageInfo.prototype.clearUri = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.getBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.getBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.BackgroundImageInfo.prototype.setBounds = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the bounds field has a value. + */ +sketchology.proto.BackgroundImageInfo.prototype.hasBounds = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the bounds field. + */ +sketchology.proto.BackgroundImageInfo.prototype.boundsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the bounds field. + */ +sketchology.proto.BackgroundImageInfo.prototype.clearBounds = function() { + this.clear$Field(3); +}; + + + +/** + * Message Border. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Border = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Border, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Border.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Border} The cloned message. + * @override + */ +sketchology.proto.Border.prototype.clone; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.Border.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.Border.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.Border.prototype.setUri = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.Border.prototype.hasUri = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.Border.prototype.uriCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.Border.prototype.clearUri = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the scale field. + * @return {?number} The value. + */ +sketchology.proto.Border.prototype.getScale = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the scale field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Border.prototype.getScaleOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the scale field. + * @param {number} value The value. + */ +sketchology.proto.Border.prototype.setScale = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the scale field has a value. + */ +sketchology.proto.Border.prototype.hasScale = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the scale field. + */ +sketchology.proto.Border.prototype.scaleCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the scale field. + */ +sketchology.proto.Border.prototype.clearScale = function() { + this.clear$Field(2); +}; + + + +/** + * Message LOD. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.LOD = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.LOD, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.LOD.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.LOD} The cloned message. + * @override + */ +sketchology.proto.LOD.prototype.clone; + + +/** + * Gets the value of the max_coverage field. + * @return {?number} The value. + */ +sketchology.proto.LOD.prototype.getMaxCoverage = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the max_coverage field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LOD.prototype.getMaxCoverageOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the max_coverage field. + * @param {number} value The value. + */ +sketchology.proto.LOD.prototype.setMaxCoverage = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the max_coverage field has a value. + */ +sketchology.proto.LOD.prototype.hasMaxCoverage = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the max_coverage field. + */ +sketchology.proto.LOD.prototype.maxCoverageCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the max_coverage field. + */ +sketchology.proto.LOD.prototype.clearMaxCoverage = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the ctm_blob field. + * @return {?string} The value. + */ +sketchology.proto.LOD.prototype.getCtmBlob = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the ctm_blob field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.LOD.prototype.getCtmBlobOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the ctm_blob field. + * @param {string} value The value. + */ +sketchology.proto.LOD.prototype.setCtmBlob = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the ctm_blob field has a value. + */ +sketchology.proto.LOD.prototype.hasCtmBlob = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the ctm_blob field. + */ +sketchology.proto.LOD.prototype.ctmBlobCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the ctm_blob field. + */ +sketchology.proto.LOD.prototype.clearCtmBlob = function() { + this.clear$Field(2); +}; + + + +/** + * Message Stroke. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Stroke = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Stroke, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Stroke.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Stroke} The cloned message. + * @override + */ +sketchology.proto.Stroke.prototype.clone; + + +/** + * Gets the value of the shader_type field. + * @return {?sketchology.proto.ShaderType} The value. + */ +sketchology.proto.Stroke.prototype.getShaderType = function() { + return /** @type {?sketchology.proto.ShaderType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the shader_type field or the default value if not set. + * @return {!sketchology.proto.ShaderType} The value. + */ +sketchology.proto.Stroke.prototype.getShaderTypeOrDefault = function() { + return /** @type {!sketchology.proto.ShaderType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the shader_type field. + * @param {!sketchology.proto.ShaderType} value The value. + */ +sketchology.proto.Stroke.prototype.setShaderType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the shader_type field has a value. + */ +sketchology.proto.Stroke.prototype.hasShaderType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the shader_type field. + */ +sketchology.proto.Stroke.prototype.shaderTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the shader_type field. + */ +sketchology.proto.Stroke.prototype.clearShaderType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the lod field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.LOD} The value. + */ +sketchology.proto.Stroke.prototype.getLod = function(index) { + return /** @type {?sketchology.proto.LOD} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the lod field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.LOD} The value. + */ +sketchology.proto.Stroke.prototype.getLodOrDefault = function(index) { + return /** @type {!sketchology.proto.LOD} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the lod field. + * @param {!sketchology.proto.LOD} value The value to add. + */ +sketchology.proto.Stroke.prototype.addLod = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the lod field. + * @return {!Array<!sketchology.proto.LOD>} The values in the field. + */ +sketchology.proto.Stroke.prototype.lodArray = function() { + return /** @type {!Array<!sketchology.proto.LOD>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the lod field has a value. + */ +sketchology.proto.Stroke.prototype.hasLod = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the lod field. + */ +sketchology.proto.Stroke.prototype.lodCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the lod field. + */ +sketchology.proto.Stroke.prototype.clearLod = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the abgr field. + * @return {?number} The value. + */ +sketchology.proto.Stroke.prototype.getAbgr = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the abgr field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Stroke.prototype.getAbgrOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the abgr field. + * @param {number} value The value. + */ +sketchology.proto.Stroke.prototype.setAbgr = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the abgr field has a value. + */ +sketchology.proto.Stroke.prototype.hasAbgr = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the abgr field. + */ +sketchology.proto.Stroke.prototype.abgrCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the abgr field. + */ +sketchology.proto.Stroke.prototype.clearAbgr = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the point_x field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.Stroke.prototype.getPointX = function(index) { + return /** @type {?number} */ (this.get$Value(5, index)); +}; + + +/** + * Gets the value of the point_x field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.Stroke.prototype.getPointXOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(5, index)); +}; + + +/** + * Adds a value to the point_x field. + * @param {number} value The value to add. + */ +sketchology.proto.Stroke.prototype.addPointX = function(value) { + this.add$Value(5, value); +}; + + +/** + * Returns the array of values in the point_x field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.Stroke.prototype.pointXArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(5)); +}; + + +/** + * @return {boolean} Whether the point_x field has a value. + */ +sketchology.proto.Stroke.prototype.hasPointX = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the point_x field. + */ +sketchology.proto.Stroke.prototype.pointXCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the point_x field. + */ +sketchology.proto.Stroke.prototype.clearPointX = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the point_y field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.Stroke.prototype.getPointY = function(index) { + return /** @type {?number} */ (this.get$Value(6, index)); +}; + + +/** + * Gets the value of the point_y field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.Stroke.prototype.getPointYOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(6, index)); +}; + + +/** + * Adds a value to the point_y field. + * @param {number} value The value to add. + */ +sketchology.proto.Stroke.prototype.addPointY = function(value) { + this.add$Value(6, value); +}; + + +/** + * Returns the array of values in the point_y field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.Stroke.prototype.pointYArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(6)); +}; + + +/** + * @return {boolean} Whether the point_y field has a value. + */ +sketchology.proto.Stroke.prototype.hasPointY = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the point_y field. + */ +sketchology.proto.Stroke.prototype.pointYCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the point_y field. + */ +sketchology.proto.Stroke.prototype.clearPointY = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the point_t_ms field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.Stroke.prototype.getPointTMs = function(index) { + return /** @type {?number} */ (this.get$Value(7, index)); +}; + + +/** + * Gets the value of the point_t_ms field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.Stroke.prototype.getPointTMsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(7, index)); +}; + + +/** + * Adds a value to the point_t_ms field. + * @param {number} value The value to add. + */ +sketchology.proto.Stroke.prototype.addPointTMs = function(value) { + this.add$Value(7, value); +}; + + +/** + * Returns the array of values in the point_t_ms field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.Stroke.prototype.pointTMsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(7)); +}; + + +/** + * @return {boolean} Whether the point_t_ms field has a value. + */ +sketchology.proto.Stroke.prototype.hasPointTMs = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the point_t_ms field. + */ +sketchology.proto.Stroke.prototype.pointTMsCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the point_t_ms field. + */ +sketchology.proto.Stroke.prototype.clearPointTMs = function() { + this.clear$Field(7); +}; + + +/** + * Gets the value of the deprecated_transform field. + * @return {?sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.Stroke.prototype.getDeprecatedTransform = function() { + return /** @type {?sketchology.proto.AffineTransform} */ (this.get$Value(8)); +}; + + +/** + * Gets the value of the deprecated_transform field or the default value if not set. + * @return {!sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.Stroke.prototype.getDeprecatedTransformOrDefault = function() { + return /** @type {!sketchology.proto.AffineTransform} */ (this.get$ValueOrDefault(8)); +}; + + +/** + * Sets the value of the deprecated_transform field. + * @param {!sketchology.proto.AffineTransform} value The value. + */ +sketchology.proto.Stroke.prototype.setDeprecatedTransform = function(value) { + this.set$Value(8, value); +}; + + +/** + * @return {boolean} Whether the deprecated_transform field has a value. + */ +sketchology.proto.Stroke.prototype.hasDeprecatedTransform = function() { + return this.has$Value(8); +}; + + +/** + * @return {number} The number of values in the deprecated_transform field. + */ +sketchology.proto.Stroke.prototype.deprecatedTransformCount = function() { + return this.count$Values(8); +}; + + +/** + * Clears the values in the deprecated_transform field. + */ +sketchology.proto.Stroke.prototype.clearDeprecatedTransform = function() { + this.clear$Field(8); +}; + + +/** + * Gets the value of the start_time_ms field. + * @return {?string} The value. + */ +sketchology.proto.Stroke.prototype.getStartTimeMs = function() { + return /** @type {?string} */ (this.get$Value(9)); +}; + + +/** + * Gets the value of the start_time_ms field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.Stroke.prototype.getStartTimeMsOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(9)); +}; + + +/** + * Sets the value of the start_time_ms field. + * @param {string} value The value. + */ +sketchology.proto.Stroke.prototype.setStartTimeMs = function(value) { + this.set$Value(9, value); +}; + + +/** + * @return {boolean} Whether the start_time_ms field has a value. + */ +sketchology.proto.Stroke.prototype.hasStartTimeMs = function() { + return this.has$Value(9); +}; + + +/** + * @return {number} The number of values in the start_time_ms field. + */ +sketchology.proto.Stroke.prototype.startTimeMsCount = function() { + return this.count$Values(9); +}; + + +/** + * Clears the values in the start_time_ms field. + */ +sketchology.proto.Stroke.prototype.clearStartTimeMs = function() { + this.clear$Field(9); +}; + + + +/** + * Message UncompressedStroke. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.UncompressedStroke = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.UncompressedStroke, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.UncompressedStroke.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.UncompressedStroke} The cloned message. + * @override + */ +sketchology.proto.UncompressedStroke.prototype.clone; + + +/** + * Gets the value of the outline field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Point} The value. + */ +sketchology.proto.UncompressedStroke.prototype.getOutline = function(index) { + return /** @type {?sketchology.proto.Point} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the outline field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Point} The value. + */ +sketchology.proto.UncompressedStroke.prototype.getOutlineOrDefault = function(index) { + return /** @type {!sketchology.proto.Point} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the outline field. + * @param {!sketchology.proto.Point} value The value to add. + */ +sketchology.proto.UncompressedStroke.prototype.addOutline = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the outline field. + * @return {!Array<!sketchology.proto.Point>} The values in the field. + */ +sketchology.proto.UncompressedStroke.prototype.outlineArray = function() { + return /** @type {!Array<!sketchology.proto.Point>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the outline field has a value. + */ +sketchology.proto.UncompressedStroke.prototype.hasOutline = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the outline field. + */ +sketchology.proto.UncompressedStroke.prototype.outlineCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the outline field. + */ +sketchology.proto.UncompressedStroke.prototype.clearOutline = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.UncompressedStroke.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.UncompressedStroke.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.UncompressedStroke.prototype.setRgba = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.UncompressedStroke.prototype.hasRgba = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.UncompressedStroke.prototype.rgbaCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.UncompressedStroke.prototype.clearRgba = function() { + this.clear$Field(2); +}; + + + +/** + * Message AffineTransform. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.AffineTransform = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.AffineTransform, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.AffineTransform.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.AffineTransform} The cloned message. + * @override + */ +sketchology.proto.AffineTransform.prototype.clone; + + +/** + * Gets the value of the tx field. + * @return {?number} The value. + */ +sketchology.proto.AffineTransform.prototype.getTx = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the tx field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.AffineTransform.prototype.getTxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the tx field. + * @param {number} value The value. + */ +sketchology.proto.AffineTransform.prototype.setTx = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the tx field has a value. + */ +sketchology.proto.AffineTransform.prototype.hasTx = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the tx field. + */ +sketchology.proto.AffineTransform.prototype.txCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the tx field. + */ +sketchology.proto.AffineTransform.prototype.clearTx = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the ty field. + * @return {?number} The value. + */ +sketchology.proto.AffineTransform.prototype.getTy = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the ty field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.AffineTransform.prototype.getTyOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the ty field. + * @param {number} value The value. + */ +sketchology.proto.AffineTransform.prototype.setTy = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the ty field has a value. + */ +sketchology.proto.AffineTransform.prototype.hasTy = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the ty field. + */ +sketchology.proto.AffineTransform.prototype.tyCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the ty field. + */ +sketchology.proto.AffineTransform.prototype.clearTy = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the scale_x field. + * @return {?number} The value. + */ +sketchology.proto.AffineTransform.prototype.getScaleX = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the scale_x field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.AffineTransform.prototype.getScaleXOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the scale_x field. + * @param {number} value The value. + */ +sketchology.proto.AffineTransform.prototype.setScaleX = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the scale_x field has a value. + */ +sketchology.proto.AffineTransform.prototype.hasScaleX = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the scale_x field. + */ +sketchology.proto.AffineTransform.prototype.scaleXCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the scale_x field. + */ +sketchology.proto.AffineTransform.prototype.clearScaleX = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the scale_y field. + * @return {?number} The value. + */ +sketchology.proto.AffineTransform.prototype.getScaleY = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the scale_y field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.AffineTransform.prototype.getScaleYOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the scale_y field. + * @param {number} value The value. + */ +sketchology.proto.AffineTransform.prototype.setScaleY = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the scale_y field has a value. + */ +sketchology.proto.AffineTransform.prototype.hasScaleY = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the scale_y field. + */ +sketchology.proto.AffineTransform.prototype.scaleYCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the scale_y field. + */ +sketchology.proto.AffineTransform.prototype.clearScaleY = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the rotation_radians field. + * @return {?number} The value. + */ +sketchology.proto.AffineTransform.prototype.getRotationRadians = function() { + return /** @type {?number} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the rotation_radians field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.AffineTransform.prototype.getRotationRadiansOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the rotation_radians field. + * @param {number} value The value. + */ +sketchology.proto.AffineTransform.prototype.setRotationRadians = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the rotation_radians field has a value. + */ +sketchology.proto.AffineTransform.prototype.hasRotationRadians = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the rotation_radians field. + */ +sketchology.proto.AffineTransform.prototype.rotationRadiansCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the rotation_radians field. + */ +sketchology.proto.AffineTransform.prototype.clearRotationRadians = function() { + this.clear$Field(5); +}; + + + +/** + * Message Element. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Element = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Element, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Element.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Element} The cloned message. + * @override + */ +sketchology.proto.Element.prototype.clone; + + +/** + * Gets the value of the deprecated_uuid field. + * @return {?string} The value. + */ +sketchology.proto.Element.prototype.getDeprecatedUuid = function() { + return /** @type {?string} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the deprecated_uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.Element.prototype.getDeprecatedUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the deprecated_uuid field. + * @param {string} value The value. + */ +sketchology.proto.Element.prototype.setDeprecatedUuid = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the deprecated_uuid field has a value. + */ +sketchology.proto.Element.prototype.hasDeprecatedUuid = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the deprecated_uuid field. + */ +sketchology.proto.Element.prototype.deprecatedUuidCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the deprecated_uuid field. + */ +sketchology.proto.Element.prototype.clearDeprecatedUuid = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the minimum_serializer_version field. + * @return {?number} The value. + */ +sketchology.proto.Element.prototype.getMinimumSerializerVersion = function() { + return /** @type {?number} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the minimum_serializer_version field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Element.prototype.getMinimumSerializerVersionOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the minimum_serializer_version field. + * @param {number} value The value. + */ +sketchology.proto.Element.prototype.setMinimumSerializerVersion = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the minimum_serializer_version field has a value. + */ +sketchology.proto.Element.prototype.hasMinimumSerializerVersion = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the minimum_serializer_version field. + */ +sketchology.proto.Element.prototype.minimumSerializerVersionCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the minimum_serializer_version field. + */ +sketchology.proto.Element.prototype.clearMinimumSerializerVersion = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the stroke field. + * @return {?sketchology.proto.Stroke} The value. + */ +sketchology.proto.Element.prototype.getStroke = function() { + return /** @type {?sketchology.proto.Stroke} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the stroke field or the default value if not set. + * @return {!sketchology.proto.Stroke} The value. + */ +sketchology.proto.Element.prototype.getStrokeOrDefault = function() { + return /** @type {!sketchology.proto.Stroke} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the stroke field. + * @param {!sketchology.proto.Stroke} value The value. + */ +sketchology.proto.Element.prototype.setStroke = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the stroke field has a value. + */ +sketchology.proto.Element.prototype.hasStroke = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the stroke field. + */ +sketchology.proto.Element.prototype.strokeCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the stroke field. + */ +sketchology.proto.Element.prototype.clearStroke = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the path field. + * @return {?sketchology.proto.Path} The value. + */ +sketchology.proto.Element.prototype.getPath = function() { + return /** @type {?sketchology.proto.Path} */ (this.get$Value(9)); +}; + + +/** + * Gets the value of the path field or the default value if not set. + * @return {!sketchology.proto.Path} The value. + */ +sketchology.proto.Element.prototype.getPathOrDefault = function() { + return /** @type {!sketchology.proto.Path} */ (this.get$ValueOrDefault(9)); +}; + + +/** + * Sets the value of the path field. + * @param {!sketchology.proto.Path} value The value. + */ +sketchology.proto.Element.prototype.setPath = function(value) { + this.set$Value(9, value); +}; + + +/** + * @return {boolean} Whether the path field has a value. + */ +sketchology.proto.Element.prototype.hasPath = function() { + return this.has$Value(9); +}; + + +/** + * @return {number} The number of values in the path field. + */ +sketchology.proto.Element.prototype.pathCount = function() { + return this.count$Values(9); +}; + + +/** + * Clears the values in the path field. + */ +sketchology.proto.Element.prototype.clearPath = function() { + this.clear$Field(9); +}; + + +/** + * Gets the value of the attributes field. + * @return {?sketchology.proto.ElementAttributes} The value. + */ +sketchology.proto.Element.prototype.getAttributes = function() { + return /** @type {?sketchology.proto.ElementAttributes} */ (this.get$Value(10)); +}; + + +/** + * Gets the value of the attributes field or the default value if not set. + * @return {!sketchology.proto.ElementAttributes} The value. + */ +sketchology.proto.Element.prototype.getAttributesOrDefault = function() { + return /** @type {!sketchology.proto.ElementAttributes} */ (this.get$ValueOrDefault(10)); +}; + + +/** + * Sets the value of the attributes field. + * @param {!sketchology.proto.ElementAttributes} value The value. + */ +sketchology.proto.Element.prototype.setAttributes = function(value) { + this.set$Value(10, value); +}; + + +/** + * @return {boolean} Whether the attributes field has a value. + */ +sketchology.proto.Element.prototype.hasAttributes = function() { + return this.has$Value(10); +}; + + +/** + * @return {number} The number of values in the attributes field. + */ +sketchology.proto.Element.prototype.attributesCount = function() { + return this.count$Values(10); +}; + + +/** + * Clears the values in the attributes field. + */ +sketchology.proto.Element.prototype.clearAttributes = function() { + this.clear$Field(10); +}; + + + +/** + * Message ElementAttributes. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementAttributes = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementAttributes, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementAttributes.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementAttributes} The cloned message. + * @override + */ +sketchology.proto.ElementAttributes.prototype.clone; + + +/** + * Gets the value of the selectable field. + * @return {?boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getSelectable = function() { + return /** @type {?boolean} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the selectable field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getSelectableOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the selectable field. + * @param {boolean} value The value. + */ +sketchology.proto.ElementAttributes.prototype.setSelectable = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the selectable field has a value. + */ +sketchology.proto.ElementAttributes.prototype.hasSelectable = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the selectable field. + */ +sketchology.proto.ElementAttributes.prototype.selectableCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the selectable field. + */ +sketchology.proto.ElementAttributes.prototype.clearSelectable = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the magic_erasable field. + * @return {?boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getMagicErasable = function() { + return /** @type {?boolean} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the magic_erasable field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getMagicErasableOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the magic_erasable field. + * @param {boolean} value The value. + */ +sketchology.proto.ElementAttributes.prototype.setMagicErasable = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the magic_erasable field has a value. + */ +sketchology.proto.ElementAttributes.prototype.hasMagicErasable = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the magic_erasable field. + */ +sketchology.proto.ElementAttributes.prototype.magicErasableCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the magic_erasable field. + */ +sketchology.proto.ElementAttributes.prototype.clearMagicErasable = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the is_sticker field. + * @return {?boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getIsSticker = function() { + return /** @type {?boolean} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the is_sticker field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getIsStickerOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the is_sticker field. + * @param {boolean} value The value. + */ +sketchology.proto.ElementAttributes.prototype.setIsSticker = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the is_sticker field has a value. + */ +sketchology.proto.ElementAttributes.prototype.hasIsSticker = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the is_sticker field. + */ +sketchology.proto.ElementAttributes.prototype.isStickerCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the is_sticker field. + */ +sketchology.proto.ElementAttributes.prototype.clearIsSticker = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the is_text field. + * @return {?boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getIsText = function() { + return /** @type {?boolean} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the is_text field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.ElementAttributes.prototype.getIsTextOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the is_text field. + * @param {boolean} value The value. + */ +sketchology.proto.ElementAttributes.prototype.setIsText = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the is_text field has a value. + */ +sketchology.proto.ElementAttributes.prototype.hasIsText = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the is_text field. + */ +sketchology.proto.ElementAttributes.prototype.isTextCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the is_text field. + */ +sketchology.proto.ElementAttributes.prototype.clearIsText = function() { + this.clear$Field(4); +}; + + + +/** + * Message UncompressedElement. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.UncompressedElement = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.UncompressedElement, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.UncompressedElement.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.UncompressedElement} The cloned message. + * @override + */ +sketchology.proto.UncompressedElement.prototype.clone; + + +/** + * Gets the value of the uncompressed_stroke field. + * @return {?sketchology.proto.UncompressedStroke} The value. + */ +sketchology.proto.UncompressedElement.prototype.getUncompressedStroke = function() { + return /** @type {?sketchology.proto.UncompressedStroke} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uncompressed_stroke field or the default value if not set. + * @return {!sketchology.proto.UncompressedStroke} The value. + */ +sketchology.proto.UncompressedElement.prototype.getUncompressedStrokeOrDefault = function() { + return /** @type {!sketchology.proto.UncompressedStroke} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uncompressed_stroke field. + * @param {!sketchology.proto.UncompressedStroke} value The value. + */ +sketchology.proto.UncompressedElement.prototype.setUncompressedStroke = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uncompressed_stroke field has a value. + */ +sketchology.proto.UncompressedElement.prototype.hasUncompressedStroke = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uncompressed_stroke field. + */ +sketchology.proto.UncompressedElement.prototype.uncompressedStrokeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uncompressed_stroke field. + */ +sketchology.proto.UncompressedElement.prototype.clearUncompressedStroke = function() { + this.clear$Field(1); +}; + + + +/** + * Message ElementMutation. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementMutation = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementMutation, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementMutation.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementMutation} The cloned message. + * @override + */ +sketchology.proto.ElementMutation.prototype.clone; + + +/** + * Gets the value of the uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ElementMutation.prototype.getUuid = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ElementMutation.prototype.getUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.ElementMutation.prototype.addUuid = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ElementMutation.prototype.uuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ElementMutation.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ElementMutation.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ElementMutation.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the transform field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.ElementMutation.prototype.getTransform = function(index) { + return /** @type {?sketchology.proto.AffineTransform} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the transform field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.ElementMutation.prototype.getTransformOrDefault = function(index) { + return /** @type {!sketchology.proto.AffineTransform} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the transform field. + * @param {!sketchology.proto.AffineTransform} value The value to add. + */ +sketchology.proto.ElementMutation.prototype.addTransform = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the transform field. + * @return {!Array<!sketchology.proto.AffineTransform>} The values in the field. + */ +sketchology.proto.ElementMutation.prototype.transformArray = function() { + return /** @type {!Array<!sketchology.proto.AffineTransform>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the transform field has a value. + */ +sketchology.proto.ElementMutation.prototype.hasTransform = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the transform field. + */ +sketchology.proto.ElementMutation.prototype.transformCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the transform field. + */ +sketchology.proto.ElementMutation.prototype.clearTransform = function() { + this.clear$Field(2); +}; + + + +/** + * Message ElementIdList. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementIdList = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementIdList, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementIdList.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementIdList} The cloned message. + * @override + */ +sketchology.proto.ElementIdList.prototype.clone; + + +/** + * Gets the value of the uuid field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ElementIdList.prototype.getUuid = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuid field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ElementIdList.prototype.getUuidOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuid field. + * @param {string} value The value to add. + */ +sketchology.proto.ElementIdList.prototype.addUuid = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuid field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ElementIdList.prototype.uuidArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ElementIdList.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ElementIdList.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ElementIdList.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + + +/** + * Message Point. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Point = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Point, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Point.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Point} The cloned message. + * @override + */ +sketchology.proto.Point.prototype.clone; + + +/** + * Gets the value of the x field. + * @return {?number} The value. + */ +sketchology.proto.Point.prototype.getX = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the x field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Point.prototype.getXOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the x field. + * @param {number} value The value. + */ +sketchology.proto.Point.prototype.setX = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the x field has a value. + */ +sketchology.proto.Point.prototype.hasX = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the x field. + */ +sketchology.proto.Point.prototype.xCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the x field. + */ +sketchology.proto.Point.prototype.clearX = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the y field. + * @return {?number} The value. + */ +sketchology.proto.Point.prototype.getY = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the y field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Point.prototype.getYOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the y field. + * @param {number} value The value. + */ +sketchology.proto.Point.prototype.setY = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the y field has a value. + */ +sketchology.proto.Point.prototype.hasY = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the y field. + */ +sketchology.proto.Point.prototype.yCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the y field. + */ +sketchology.proto.Point.prototype.clearY = function() { + this.clear$Field(2); +}; + + + +/** + * Message ElementBundle. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementBundle = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementBundle, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementBundle.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementBundle} The cloned message. + * @override + */ +sketchology.proto.ElementBundle.prototype.clone; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.ElementBundle.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ElementBundle.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.ElementBundle.prototype.setUuid = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ElementBundle.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ElementBundle.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ElementBundle.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the element field. + * @return {?sketchology.proto.Element} The value. + */ +sketchology.proto.ElementBundle.prototype.getElement = function() { + return /** @type {?sketchology.proto.Element} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the element field or the default value if not set. + * @return {!sketchology.proto.Element} The value. + */ +sketchology.proto.ElementBundle.prototype.getElementOrDefault = function() { + return /** @type {!sketchology.proto.Element} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the element field. + * @param {!sketchology.proto.Element} value The value. + */ +sketchology.proto.ElementBundle.prototype.setElement = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the element field has a value. + */ +sketchology.proto.ElementBundle.prototype.hasElement = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the element field. + */ +sketchology.proto.ElementBundle.prototype.elementCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the element field. + */ +sketchology.proto.ElementBundle.prototype.clearElement = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the transform field. + * @return {?sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.ElementBundle.prototype.getTransform = function() { + return /** @type {?sketchology.proto.AffineTransform} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the transform field or the default value if not set. + * @return {!sketchology.proto.AffineTransform} The value. + */ +sketchology.proto.ElementBundle.prototype.getTransformOrDefault = function() { + return /** @type {!sketchology.proto.AffineTransform} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the transform field. + * @param {!sketchology.proto.AffineTransform} value The value. + */ +sketchology.proto.ElementBundle.prototype.setTransform = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the transform field has a value. + */ +sketchology.proto.ElementBundle.prototype.hasTransform = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the transform field. + */ +sketchology.proto.ElementBundle.prototype.transformCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the transform field. + */ +sketchology.proto.ElementBundle.prototype.clearTransform = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the uncompressed_element field. + * @return {?sketchology.proto.UncompressedElement} The value. + */ +sketchology.proto.ElementBundle.prototype.getUncompressedElement = function() { + return /** @type {?sketchology.proto.UncompressedElement} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the uncompressed_element field or the default value if not set. + * @return {!sketchology.proto.UncompressedElement} The value. + */ +sketchology.proto.ElementBundle.prototype.getUncompressedElementOrDefault = function() { + return /** @type {!sketchology.proto.UncompressedElement} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the uncompressed_element field. + * @param {!sketchology.proto.UncompressedElement} value The value. + */ +sketchology.proto.ElementBundle.prototype.setUncompressedElement = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the uncompressed_element field has a value. + */ +sketchology.proto.ElementBundle.prototype.hasUncompressedElement = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the uncompressed_element field. + */ +sketchology.proto.ElementBundle.prototype.uncompressedElementCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the uncompressed_element field. + */ +sketchology.proto.ElementBundle.prototype.clearUncompressedElement = function() { + this.clear$Field(4); +}; + + + +/** + * Message Path. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Path = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Path, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Path.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Path} The cloned message. + * @override + */ +sketchology.proto.Path.prototype.clone; + + +/** + * Gets the value of the segment_types field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Path.SegmentType} The value. + */ +sketchology.proto.Path.prototype.getSegmentTypes = function(index) { + return /** @type {?sketchology.proto.Path.SegmentType} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the segment_types field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Path.SegmentType} The value. + */ +sketchology.proto.Path.prototype.getSegmentTypesOrDefault = function(index) { + return /** @type {!sketchology.proto.Path.SegmentType} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the segment_types field. + * @param {!sketchology.proto.Path.SegmentType} value The value to add. + */ +sketchology.proto.Path.prototype.addSegmentTypes = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the segment_types field. + * @return {!Array<!sketchology.proto.Path.SegmentType>} The values in the field. + */ +sketchology.proto.Path.prototype.segmentTypesArray = function() { + return /** @type {!Array<!sketchology.proto.Path.SegmentType>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the segment_types field has a value. + */ +sketchology.proto.Path.prototype.hasSegmentTypes = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the segment_types field. + */ +sketchology.proto.Path.prototype.segmentTypesCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the segment_types field. + */ +sketchology.proto.Path.prototype.clearSegmentTypes = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the segment_counts field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.Path.prototype.getSegmentCounts = function(index) { + return /** @type {?number} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the segment_counts field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.Path.prototype.getSegmentCountsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the segment_counts field. + * @param {number} value The value to add. + */ +sketchology.proto.Path.prototype.addSegmentCounts = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the segment_counts field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.Path.prototype.segmentCountsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the segment_counts field has a value. + */ +sketchology.proto.Path.prototype.hasSegmentCounts = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the segment_counts field. + */ +sketchology.proto.Path.prototype.segmentCountsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the segment_counts field. + */ +sketchology.proto.Path.prototype.clearSegmentCounts = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the segment_args field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.Path.prototype.getSegmentArgs = function(index) { + return /** @type {?number} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the segment_args field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.Path.prototype.getSegmentArgsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the segment_args field. + * @param {number} value The value to add. + */ +sketchology.proto.Path.prototype.addSegmentArgs = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the segment_args field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.Path.prototype.segmentArgsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the segment_args field has a value. + */ +sketchology.proto.Path.prototype.hasSegmentArgs = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the segment_args field. + */ +sketchology.proto.Path.prototype.segmentArgsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the segment_args field. + */ +sketchology.proto.Path.prototype.clearSegmentArgs = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the radius field. + * @return {?number} The value. + */ +sketchology.proto.Path.prototype.getRadius = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the radius field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Path.prototype.getRadiusOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the radius field. + * @param {number} value The value. + */ +sketchology.proto.Path.prototype.setRadius = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the radius field has a value. + */ +sketchology.proto.Path.prototype.hasRadius = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the radius field. + */ +sketchology.proto.Path.prototype.radiusCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the radius field. + */ +sketchology.proto.Path.prototype.clearRadius = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.Path.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Path.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.Path.prototype.setRgba = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.Path.prototype.hasRgba = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.Path.prototype.rgbaCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.Path.prototype.clearRgba = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the end_cap field. + * @return {?sketchology.proto.Path.EndCapType} The value. + */ +sketchology.proto.Path.prototype.getEndCap = function() { + return /** @type {?sketchology.proto.Path.EndCapType} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the end_cap field or the default value if not set. + * @return {!sketchology.proto.Path.EndCapType} The value. + */ +sketchology.proto.Path.prototype.getEndCapOrDefault = function() { + return /** @type {!sketchology.proto.Path.EndCapType} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the end_cap field. + * @param {!sketchology.proto.Path.EndCapType} value The value. + */ +sketchology.proto.Path.prototype.setEndCap = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the end_cap field has a value. + */ +sketchology.proto.Path.prototype.hasEndCap = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the end_cap field. + */ +sketchology.proto.Path.prototype.endCapCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the end_cap field. + */ +sketchology.proto.Path.prototype.clearEndCap = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the fill_rgba field. + * @return {?number} The value. + */ +sketchology.proto.Path.prototype.getFillRgba = function() { + return /** @type {?number} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the fill_rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Path.prototype.getFillRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the fill_rgba field. + * @param {number} value The value. + */ +sketchology.proto.Path.prototype.setFillRgba = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the fill_rgba field has a value. + */ +sketchology.proto.Path.prototype.hasFillRgba = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the fill_rgba field. + */ +sketchology.proto.Path.prototype.fillRgbaCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the fill_rgba field. + */ +sketchology.proto.Path.prototype.clearFillRgba = function() { + this.clear$Field(7); +}; + + +/** + * Enumeration SegmentType. + * @enum {number} + */ +sketchology.proto.Path.SegmentType = { + UNKNOWN: 0, + MOVE_TO: 1, + LINE_TO: 2, + CURVE_TO: 3, + QUAD_TO: 4, + CLOSE: 5 +}; + + +/** + * Enumeration EndCapType. + * @enum {number} + */ +sketchology.proto.Path.EndCapType = { + BUTT: 1, + ROUND: 2, + SQUARE: 3 +}; + + +/** @override */ +sketchology.proto.CallbackFlags.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.CallbackFlags.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'CallbackFlags', + fullName: 'sketchology.proto.CallbackFlags' + }, + 1: { + name: 'mesh_data_ctm', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 2: { + name: 'uncompressed_outline', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 3: { + name: 'compressed_input_points', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + sketchology.proto.CallbackFlags.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.CallbackFlags, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.CallbackFlags.getDescriptor = + sketchology.proto.CallbackFlags.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SourceDetails.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SourceDetails.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SourceDetails', + fullName: 'sketchology.proto.SourceDetails' + }, + 1: { + name: 'origin', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.SourceDetails.Origin.UNKNOWN, + type: sketchology.proto.SourceDetails.Origin + }, + 2: { + name: 'host_source_details', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.SourceDetails.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SourceDetails, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SourceDetails.getDescriptor = + sketchology.proto.SourceDetails.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.BackgroundImageInfo.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.BackgroundImageInfo.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'BackgroundImageInfo', + fullName: 'sketchology.proto.BackgroundImageInfo' + }, + 1: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 3: { + name: 'bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + } + }; + sketchology.proto.BackgroundImageInfo.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.BackgroundImageInfo, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.BackgroundImageInfo.getDescriptor = + sketchology.proto.BackgroundImageInfo.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Border.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Border.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Border', + fullName: 'sketchology.proto.Border' + }, + 1: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'scale', + fieldType: goog.proto2.Message.FieldType.FLOAT, + defaultValue: 1, + type: Number + } + }; + sketchology.proto.Border.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Border, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Border.getDescriptor = + sketchology.proto.Border.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.LOD.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.LOD.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'LOD', + fullName: 'sketchology.proto.LOD' + }, + 1: { + name: 'max_coverage', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'ctm_blob', + fieldType: goog.proto2.Message.FieldType.BYTES, + type: String + } + }; + sketchology.proto.LOD.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.LOD, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.LOD.getDescriptor = + sketchology.proto.LOD.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Stroke.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Stroke.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Stroke', + fullName: 'sketchology.proto.Stroke' + }, + 1: { + name: 'shader_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.ShaderType.NONE, + type: sketchology.proto.ShaderType + }, + 3: { + name: 'lod', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.LOD + }, + 4: { + name: 'abgr', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 5: { + name: 'point_x', + repeated: true, + packed: true, + fieldType: goog.proto2.Message.FieldType.SINT32, + type: Number + }, + 6: { + name: 'point_y', + repeated: true, + packed: true, + fieldType: goog.proto2.Message.FieldType.SINT32, + type: Number + }, + 7: { + name: 'point_t_ms', + repeated: true, + packed: true, + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 8: { + name: 'deprecated_transform', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AffineTransform + }, + 9: { + name: 'start_time_ms', + fieldType: goog.proto2.Message.FieldType.UINT64, + type: String + } + }; + sketchology.proto.Stroke.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Stroke, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Stroke.getDescriptor = + sketchology.proto.Stroke.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.UncompressedStroke.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.UncompressedStroke.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'UncompressedStroke', + fullName: 'sketchology.proto.UncompressedStroke' + }, + 1: { + name: 'outline', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Point + }, + 2: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.UncompressedStroke.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.UncompressedStroke, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.UncompressedStroke.getDescriptor = + sketchology.proto.UncompressedStroke.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.AffineTransform.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.AffineTransform.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'AffineTransform', + fullName: 'sketchology.proto.AffineTransform' + }, + 1: { + name: 'tx', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'ty', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 3: { + name: 'scale_x', + fieldType: goog.proto2.Message.FieldType.FLOAT, + defaultValue: 1, + type: Number + }, + 4: { + name: 'scale_y', + fieldType: goog.proto2.Message.FieldType.FLOAT, + defaultValue: 1, + type: Number + }, + 5: { + name: 'rotation_radians', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.AffineTransform.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.AffineTransform, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.AffineTransform.getDescriptor = + sketchology.proto.AffineTransform.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Element.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Element.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Element', + fullName: 'sketchology.proto.Element' + }, + 4: { + name: 'deprecated_uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 5: { + name: 'minimum_serializer_version', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 6: { + name: 'stroke', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Stroke + }, + 9: { + name: 'path', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Path + }, + 10: { + name: 'attributes', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementAttributes + } + }; + sketchology.proto.Element.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Element, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Element.getDescriptor = + sketchology.proto.Element.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementAttributes.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementAttributes.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementAttributes', + fullName: 'sketchology.proto.ElementAttributes' + }, + 1: { + name: 'selectable', + fieldType: goog.proto2.Message.FieldType.BOOL, + defaultValue: true, + type: Boolean + }, + 2: { + name: 'magic_erasable', + fieldType: goog.proto2.Message.FieldType.BOOL, + defaultValue: true, + type: Boolean + }, + 3: { + name: 'is_sticker', + fieldType: goog.proto2.Message.FieldType.BOOL, + defaultValue: false, + type: Boolean + }, + 4: { + name: 'is_text', + fieldType: goog.proto2.Message.FieldType.BOOL, + defaultValue: false, + type: Boolean + } + }; + sketchology.proto.ElementAttributes.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementAttributes, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementAttributes.getDescriptor = + sketchology.proto.ElementAttributes.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.UncompressedElement.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.UncompressedElement.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'UncompressedElement', + fullName: 'sketchology.proto.UncompressedElement' + }, + 1: { + name: 'uncompressed_stroke', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.UncompressedStroke + } + }; + sketchology.proto.UncompressedElement.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.UncompressedElement, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.UncompressedElement.getDescriptor = + sketchology.proto.UncompressedElement.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementMutation.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementMutation.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementMutation', + fullName: 'sketchology.proto.ElementMutation' + }, + 1: { + name: 'uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'transform', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AffineTransform + } + }; + sketchology.proto.ElementMutation.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementMutation, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementMutation.getDescriptor = + sketchology.proto.ElementMutation.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementIdList.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementIdList.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementIdList', + fullName: 'sketchology.proto.ElementIdList' + }, + 1: { + name: 'uuid', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.ElementIdList.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementIdList, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementIdList.getDescriptor = + sketchology.proto.ElementIdList.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Point.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Point.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Point', + fullName: 'sketchology.proto.Point' + }, + 1: { + name: 'x', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'y', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.Point.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Point, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Point.getDescriptor = + sketchology.proto.Point.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementBundle.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementBundle.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementBundle', + fullName: 'sketchology.proto.ElementBundle' + }, + 1: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'element', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Element + }, + 3: { + name: 'transform', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AffineTransform + }, + 4: { + name: 'uncompressed_element', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.UncompressedElement + } + }; + sketchology.proto.ElementBundle.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementBundle, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementBundle.getDescriptor = + sketchology.proto.ElementBundle.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Path.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Path.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Path', + fullName: 'sketchology.proto.Path' + }, + 1: { + name: 'segment_types', + repeated: true, + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.Path.SegmentType.UNKNOWN, + type: sketchology.proto.Path.SegmentType + }, + 2: { + name: 'segment_counts', + repeated: true, + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 3: { + name: 'segment_args', + repeated: true, + fieldType: goog.proto2.Message.FieldType.DOUBLE, + type: Number + }, + 4: { + name: 'radius', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + defaultValue: 1, + type: Number + }, + 5: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 6: { + name: 'end_cap', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.Path.EndCapType.ROUND, + type: sketchology.proto.Path.EndCapType + }, + 7: { + name: 'fill_rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.Path.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Path, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Path.getDescriptor = + sketchology.proto.Path.prototype.getDescriptor;
diff --git a/third_party/ink/sketchology/proto/rect_bounds.pb.js b/third_party/ink/sketchology/proto/rect_bounds.pb.js new file mode 100644 index 0000000..ef41a1d1 --- /dev/null +++ b/third_party/ink/sketchology/proto/rect_bounds.pb.js
@@ -0,0 +1,525 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * third_party/sketchology/proto/rect_bounds.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('sketchology.proto.Rect'); +goog.provide('sketchology.proto.RectBounds'); + +goog.require('goog.proto2.Message'); + + + +/** + * Message Rect. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Rect = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Rect, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Rect.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Rect} The cloned message. + * @override + */ +sketchology.proto.Rect.prototype.clone; + + +/** + * Gets the value of the xlow field. + * @return {?number} The value. + */ +sketchology.proto.Rect.prototype.getXlow = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the xlow field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Rect.prototype.getXlowOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the xlow field. + * @param {number} value The value. + */ +sketchology.proto.Rect.prototype.setXlow = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the xlow field has a value. + */ +sketchology.proto.Rect.prototype.hasXlow = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the xlow field. + */ +sketchology.proto.Rect.prototype.xlowCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the xlow field. + */ +sketchology.proto.Rect.prototype.clearXlow = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the xhigh field. + * @return {?number} The value. + */ +sketchology.proto.Rect.prototype.getXhigh = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the xhigh field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Rect.prototype.getXhighOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the xhigh field. + * @param {number} value The value. + */ +sketchology.proto.Rect.prototype.setXhigh = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the xhigh field has a value. + */ +sketchology.proto.Rect.prototype.hasXhigh = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the xhigh field. + */ +sketchology.proto.Rect.prototype.xhighCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the xhigh field. + */ +sketchology.proto.Rect.prototype.clearXhigh = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the ylow field. + * @return {?number} The value. + */ +sketchology.proto.Rect.prototype.getYlow = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the ylow field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Rect.prototype.getYlowOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the ylow field. + * @param {number} value The value. + */ +sketchology.proto.Rect.prototype.setYlow = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the ylow field has a value. + */ +sketchology.proto.Rect.prototype.hasYlow = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the ylow field. + */ +sketchology.proto.Rect.prototype.ylowCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the ylow field. + */ +sketchology.proto.Rect.prototype.clearYlow = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the yhigh field. + * @return {?number} The value. + */ +sketchology.proto.Rect.prototype.getYhigh = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the yhigh field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Rect.prototype.getYhighOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the yhigh field. + * @param {number} value The value. + */ +sketchology.proto.Rect.prototype.setYhigh = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the yhigh field has a value. + */ +sketchology.proto.Rect.prototype.hasYhigh = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the yhigh field. + */ +sketchology.proto.Rect.prototype.yhighCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the yhigh field. + */ +sketchology.proto.Rect.prototype.clearYhigh = function() { + this.clear$Field(4); +}; + + + +/** + * Message RectBounds. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.RectBounds = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.RectBounds, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.RectBounds.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.RectBounds} The cloned message. + * @override + */ +sketchology.proto.RectBounds.prototype.clone; + + +/** + * Gets the value of the mbr field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.RectBounds.prototype.getMbr = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the mbr field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.RectBounds.prototype.getMbrOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the mbr field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.RectBounds.prototype.setMbr = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the mbr field has a value. + */ +sketchology.proto.RectBounds.prototype.hasMbr = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the mbr field. + */ +sketchology.proto.RectBounds.prototype.mbrCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the mbr field. + */ +sketchology.proto.RectBounds.prototype.clearMbr = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the fit_rects field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.RectBounds.prototype.getFitRects = function(index) { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the fit_rects field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.RectBounds.prototype.getFitRectsOrDefault = function(index) { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the fit_rects field. + * @param {!sketchology.proto.Rect} value The value to add. + */ +sketchology.proto.RectBounds.prototype.addFitRects = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the fit_rects field. + * @return {!Array<!sketchology.proto.Rect>} The values in the field. + */ +sketchology.proto.RectBounds.prototype.fitRectsArray = function() { + return /** @type {!Array<!sketchology.proto.Rect>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the fit_rects field has a value. + */ +sketchology.proto.RectBounds.prototype.hasFitRects = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the fit_rects field. + */ +sketchology.proto.RectBounds.prototype.fitRectsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the fit_rects field. + */ +sketchology.proto.RectBounds.prototype.clearFitRects = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the max_dim field. + * @return {?number} The value. + */ +sketchology.proto.RectBounds.prototype.getMaxDim = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the max_dim field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.RectBounds.prototype.getMaxDimOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the max_dim field. + * @param {number} value The value. + */ +sketchology.proto.RectBounds.prototype.setMaxDim = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the max_dim field has a value. + */ +sketchology.proto.RectBounds.prototype.hasMaxDim = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the max_dim field. + */ +sketchology.proto.RectBounds.prototype.maxDimCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the max_dim field. + */ +sketchology.proto.RectBounds.prototype.clearMaxDim = function() { + this.clear$Field(3); +}; + + +/** @override */ +sketchology.proto.Rect.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Rect.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Rect', + fullName: 'sketchology.proto.Rect' + }, + 1: { + name: 'xlow', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'xhigh', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 3: { + name: 'ylow', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 4: { + name: 'yhigh', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.Rect.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Rect, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Rect.getDescriptor = + sketchology.proto.Rect.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.RectBounds.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.RectBounds.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'RectBounds', + fullName: 'sketchology.proto.RectBounds' + }, + 1: { + name: 'mbr', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 2: { + name: 'fit_rects', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 3: { + name: 'max_dim', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.RectBounds.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.RectBounds, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.RectBounds.getDescriptor = + sketchology.proto.RectBounds.prototype.getDescriptor;
diff --git a/third_party/ink/sketchology/proto/sengine.pb.js b/third_party/ink/sketchology/proto/sengine.pb.js new file mode 100644 index 0000000..56bd87d --- /dev/null +++ b/third_party/ink/sketchology/proto/sengine.pb.js
@@ -0,0 +1,8196 @@ +// Copyright 2017 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. +// Protocol Buffer 2 Copyright 2008 Google Inc. +// All other code copyright its respective owners. + +/** + * @fileoverview Generated Protocol Buffer code for file + * third_party/sketchology/proto/sengine.proto. + * Generated by //net/proto2/compiler/public:protocol_compiler. + * @suppress {messageConventions} + */ + +goog.provide('sketchology.proto.Command'); +goog.provide('sketchology.proto.CommandList'); +goog.provide('sketchology.proto.NoArgCommand'); +goog.provide('sketchology.proto.ReplaceElementsCommand'); +goog.provide('sketchology.proto.EvictImageData'); +goog.provide('sketchology.proto.Viewport'); +goog.provide('sketchology.proto.ImageExport'); +goog.provide('sketchology.proto.LinearPathAnimation'); +goog.provide('sketchology.proto.LineSize'); +goog.provide('sketchology.proto.LineSize.SizeType'); +goog.provide('sketchology.proto.PusherToolParams'); +goog.provide('sketchology.proto.ToolParams'); +goog.provide('sketchology.proto.ToolParams.ToolType'); +goog.provide('sketchology.proto.FlagAssignment'); +goog.provide('sketchology.proto.AddElement'); +goog.provide('sketchology.proto.OutOfBoundsColor'); +goog.provide('sketchology.proto.SInputStream'); +goog.provide('sketchology.proto.SInput'); +goog.provide('sketchology.proto.SInput.InputType'); +goog.provide('sketchology.proto.SimulatedInput'); +goog.provide('sketchology.proto.SequencePoint'); +goog.provide('sketchology.proto.SetCallbackFlags'); +goog.provide('sketchology.proto.EngineState'); +goog.provide('sketchology.proto.CameraBoundsConfig'); +goog.provide('sketchology.proto.ImageInfo'); +goog.provide('sketchology.proto.ImageInfo.AssetType'); +goog.provide('sketchology.proto.ImageRect'); +goog.provide('sketchology.proto.GridInfo'); +goog.provide('sketchology.proto.CreateDocument'); +goog.provide('sketchology.proto.AddPath'); +goog.provide('sketchology.proto.PusherPositionUpdate'); +goog.provide('sketchology.proto.ElementQueryData'); +goog.provide('sketchology.proto.ElementQueryItem'); +goog.provide('sketchology.proto.SelectionState'); +goog.provide('sketchology.proto.ToolEvent'); +goog.provide('sketchology.proto.RenderingStrategy'); +goog.provide('sketchology.proto.BrushType'); +goog.provide('sketchology.proto.Flag'); +goog.provide('sketchology.proto.DocumentType'); +goog.provide('sketchology.proto.StorageType'); + +goog.require('goog.proto2.Message'); +goog.require('sketchology.proto.BackgroundColor'); +goog.require('sketchology.proto.BackgroundImageInfo'); +goog.require('sketchology.proto.Border'); +goog.require('sketchology.proto.CallbackFlags'); +goog.require('sketchology.proto.ElementAnimation'); +goog.require('sketchology.proto.ElementAttributes'); +goog.require('sketchology.proto.ElementBundle'); +goog.require('sketchology.proto.ElementMutation'); +goog.require('sketchology.proto.Path'); +goog.require('sketchology.proto.Point'); +goog.require('sketchology.proto.Rect'); +goog.require('sketchology.proto.Snapshot'); +goog.require('sketchology.proto.SourceDetails'); + + +/** + * Enumeration RenderingStrategy. + * @enum {number} + */ +sketchology.proto.RenderingStrategy = { + UNKNOWN_RENDERER: 0, + BUFFERED_RENDERER: 1, + DIRECT_RENDERER: 2 +}; + + +/** + * Enumeration BrushType. + * @enum {number} + */ +sketchology.proto.BrushType = { + UNKNOWN_BRUSH: 0, + CALLIGRAPHY: 1, + INKPEN: 2, + MARKER: 3, + BALLPOINT: 4, + PENCIL: 5, + ERASER: 6, + AIRBRUSH: 7, + HIGHLIGHTER: 8, + GRADIENT: 9, + CHISEL: 10, + BALLPOINT_IN_PEN_MODE_ELSE_MARKER: 11 +}; + + +/** + * Enumeration Flag. + * @enum {number} + */ +sketchology.proto.Flag = { + UNKNOWN: 0, + READ_ONLY_MODE: 1, + ENABLE_PAN_ZOOM: 2, + ENABLE_ROTATION: 3, + ENABLE_AUTO_PEN_MODE: 4, + ENABLE_PEN_MODE: 5, + LOW_MEMORY_MODE: 6, + OPAQUE_PREDICTED_SEGMENT: 7 +}; + + +/** + * Enumeration DocumentType. + * @enum {number} + */ +sketchology.proto.DocumentType = { + UNKNOWN_DOCUMENT_TYPE: 0, + SINGLE_USER_DOCUMENT: 1, + PASSTHROUGH_DOCUMENT: 2 +}; + + +/** + * Enumeration StorageType. + * @enum {number} + */ +sketchology.proto.StorageType = { + UNKNOWN_STORAGE_TYPE: 0, + IN_MEMORY_STORAGE: 1, + SQLITE_STORAGE: 2 +}; + + + +/** + * Message Command. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Command = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Command, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Command.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Command} The cloned message. + * @override + */ +sketchology.proto.Command.prototype.clone; + + +/** + * Gets the value of the set_viewport field. + * @return {?sketchology.proto.Viewport} The value. + */ +sketchology.proto.Command.prototype.getSetViewport = function() { + return /** @type {?sketchology.proto.Viewport} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the set_viewport field or the default value if not set. + * @return {!sketchology.proto.Viewport} The value. + */ +sketchology.proto.Command.prototype.getSetViewportOrDefault = function() { + return /** @type {!sketchology.proto.Viewport} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the set_viewport field. + * @param {!sketchology.proto.Viewport} value The value. + */ +sketchology.proto.Command.prototype.setSetViewport = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the set_viewport field has a value. + */ +sketchology.proto.Command.prototype.hasSetViewport = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the set_viewport field. + */ +sketchology.proto.Command.prototype.setViewportCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the set_viewport field. + */ +sketchology.proto.Command.prototype.clearSetViewport = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the tool_params field. + * @return {?sketchology.proto.ToolParams} The value. + */ +sketchology.proto.Command.prototype.getToolParams = function() { + return /** @type {?sketchology.proto.ToolParams} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the tool_params field or the default value if not set. + * @return {!sketchology.proto.ToolParams} The value. + */ +sketchology.proto.Command.prototype.getToolParamsOrDefault = function() { + return /** @type {!sketchology.proto.ToolParams} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the tool_params field. + * @param {!sketchology.proto.ToolParams} value The value. + */ +sketchology.proto.Command.prototype.setToolParams = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the tool_params field has a value. + */ +sketchology.proto.Command.prototype.hasToolParams = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the tool_params field. + */ +sketchology.proto.Command.prototype.toolParamsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the tool_params field. + */ +sketchology.proto.Command.prototype.clearToolParams = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the add_path field. + * @return {?sketchology.proto.AddPath} The value. + */ +sketchology.proto.Command.prototype.getAddPath = function() { + return /** @type {?sketchology.proto.AddPath} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the add_path field or the default value if not set. + * @return {!sketchology.proto.AddPath} The value. + */ +sketchology.proto.Command.prototype.getAddPathOrDefault = function() { + return /** @type {!sketchology.proto.AddPath} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the add_path field. + * @param {!sketchology.proto.AddPath} value The value. + */ +sketchology.proto.Command.prototype.setAddPath = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the add_path field has a value. + */ +sketchology.proto.Command.prototype.hasAddPath = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the add_path field. + */ +sketchology.proto.Command.prototype.addPathCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the add_path field. + */ +sketchology.proto.Command.prototype.clearAddPath = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the camera_position field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.Command.prototype.getCameraPosition = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the camera_position field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.Command.prototype.getCameraPositionOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the camera_position field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.Command.prototype.setCameraPosition = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the camera_position field has a value. + */ +sketchology.proto.Command.prototype.hasCameraPosition = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the camera_position field. + */ +sketchology.proto.Command.prototype.cameraPositionCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the camera_position field. + */ +sketchology.proto.Command.prototype.clearCameraPosition = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the page_bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.Command.prototype.getPageBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the page_bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.Command.prototype.getPageBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the page_bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.Command.prototype.setPageBounds = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the page_bounds field has a value. + */ +sketchology.proto.Command.prototype.hasPageBounds = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the page_bounds field. + */ +sketchology.proto.Command.prototype.pageBoundsCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the page_bounds field. + */ +sketchology.proto.Command.prototype.clearPageBounds = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the image_export field. + * @return {?sketchology.proto.ImageExport} The value. + */ +sketchology.proto.Command.prototype.getImageExport = function() { + return /** @type {?sketchology.proto.ImageExport} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the image_export field or the default value if not set. + * @return {!sketchology.proto.ImageExport} The value. + */ +sketchology.proto.Command.prototype.getImageExportOrDefault = function() { + return /** @type {!sketchology.proto.ImageExport} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the image_export field. + * @param {!sketchology.proto.ImageExport} value The value. + */ +sketchology.proto.Command.prototype.setImageExport = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the image_export field has a value. + */ +sketchology.proto.Command.prototype.hasImageExport = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the image_export field. + */ +sketchology.proto.Command.prototype.imageExportCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the image_export field. + */ +sketchology.proto.Command.prototype.clearImageExport = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the flag_assignment field. + * @return {?sketchology.proto.FlagAssignment} The value. + */ +sketchology.proto.Command.prototype.getFlagAssignment = function() { + return /** @type {?sketchology.proto.FlagAssignment} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the flag_assignment field or the default value if not set. + * @return {!sketchology.proto.FlagAssignment} The value. + */ +sketchology.proto.Command.prototype.getFlagAssignmentOrDefault = function() { + return /** @type {!sketchology.proto.FlagAssignment} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the flag_assignment field. + * @param {!sketchology.proto.FlagAssignment} value The value. + */ +sketchology.proto.Command.prototype.setFlagAssignment = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the flag_assignment field has a value. + */ +sketchology.proto.Command.prototype.hasFlagAssignment = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the flag_assignment field. + */ +sketchology.proto.Command.prototype.flagAssignmentCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the flag_assignment field. + */ +sketchology.proto.Command.prototype.clearFlagAssignment = function() { + this.clear$Field(7); +}; + + +/** + * Gets the value of the set_element_transforms field. + * @return {?sketchology.proto.ElementMutation} The value. + */ +sketchology.proto.Command.prototype.getSetElementTransforms = function() { + return /** @type {?sketchology.proto.ElementMutation} */ (this.get$Value(8)); +}; + + +/** + * Gets the value of the set_element_transforms field or the default value if not set. + * @return {!sketchology.proto.ElementMutation} The value. + */ +sketchology.proto.Command.prototype.getSetElementTransformsOrDefault = function() { + return /** @type {!sketchology.proto.ElementMutation} */ (this.get$ValueOrDefault(8)); +}; + + +/** + * Sets the value of the set_element_transforms field. + * @param {!sketchology.proto.ElementMutation} value The value. + */ +sketchology.proto.Command.prototype.setSetElementTransforms = function(value) { + this.set$Value(8, value); +}; + + +/** + * @return {boolean} Whether the set_element_transforms field has a value. + */ +sketchology.proto.Command.prototype.hasSetElementTransforms = function() { + return this.has$Value(8); +}; + + +/** + * @return {number} The number of values in the set_element_transforms field. + */ +sketchology.proto.Command.prototype.setElementTransformsCount = function() { + return this.count$Values(8); +}; + + +/** + * Clears the values in the set_element_transforms field. + */ +sketchology.proto.Command.prototype.clearSetElementTransforms = function() { + this.clear$Field(8); +}; + + +/** + * Gets the value of the add_element field. + * @return {?sketchology.proto.AddElement} The value. + */ +sketchology.proto.Command.prototype.getAddElement = function() { + return /** @type {?sketchology.proto.AddElement} */ (this.get$Value(9)); +}; + + +/** + * Gets the value of the add_element field or the default value if not set. + * @return {!sketchology.proto.AddElement} The value. + */ +sketchology.proto.Command.prototype.getAddElementOrDefault = function() { + return /** @type {!sketchology.proto.AddElement} */ (this.get$ValueOrDefault(9)); +}; + + +/** + * Sets the value of the add_element field. + * @param {!sketchology.proto.AddElement} value The value. + */ +sketchology.proto.Command.prototype.setAddElement = function(value) { + this.set$Value(9, value); +}; + + +/** + * @return {boolean} Whether the add_element field has a value. + */ +sketchology.proto.Command.prototype.hasAddElement = function() { + return this.has$Value(9); +}; + + +/** + * @return {number} The number of values in the add_element field. + */ +sketchology.proto.Command.prototype.addElementCount = function() { + return this.count$Values(9); +}; + + +/** + * Clears the values in the add_element field. + */ +sketchology.proto.Command.prototype.clearAddElement = function() { + this.clear$Field(9); +}; + + +/** + * Gets the value of the background_image field. + * @return {?sketchology.proto.BackgroundImageInfo} The value. + */ +sketchology.proto.Command.prototype.getBackgroundImage = function() { + return /** @type {?sketchology.proto.BackgroundImageInfo} */ (this.get$Value(10)); +}; + + +/** + * Gets the value of the background_image field or the default value if not set. + * @return {!sketchology.proto.BackgroundImageInfo} The value. + */ +sketchology.proto.Command.prototype.getBackgroundImageOrDefault = function() { + return /** @type {!sketchology.proto.BackgroundImageInfo} */ (this.get$ValueOrDefault(10)); +}; + + +/** + * Sets the value of the background_image field. + * @param {!sketchology.proto.BackgroundImageInfo} value The value. + */ +sketchology.proto.Command.prototype.setBackgroundImage = function(value) { + this.set$Value(10, value); +}; + + +/** + * @return {boolean} Whether the background_image field has a value. + */ +sketchology.proto.Command.prototype.hasBackgroundImage = function() { + return this.has$Value(10); +}; + + +/** + * @return {number} The number of values in the background_image field. + */ +sketchology.proto.Command.prototype.backgroundImageCount = function() { + return this.count$Values(10); +}; + + +/** + * Clears the values in the background_image field. + */ +sketchology.proto.Command.prototype.clearBackgroundImage = function() { + this.clear$Field(10); +}; + + +/** + * Gets the value of the background_color field. + * @return {?sketchology.proto.BackgroundColor} The value. + */ +sketchology.proto.Command.prototype.getBackgroundColor = function() { + return /** @type {?sketchology.proto.BackgroundColor} */ (this.get$Value(11)); +}; + + +/** + * Gets the value of the background_color field or the default value if not set. + * @return {!sketchology.proto.BackgroundColor} The value. + */ +sketchology.proto.Command.prototype.getBackgroundColorOrDefault = function() { + return /** @type {!sketchology.proto.BackgroundColor} */ (this.get$ValueOrDefault(11)); +}; + + +/** + * Sets the value of the background_color field. + * @param {!sketchology.proto.BackgroundColor} value The value. + */ +sketchology.proto.Command.prototype.setBackgroundColor = function(value) { + this.set$Value(11, value); +}; + + +/** + * @return {boolean} Whether the background_color field has a value. + */ +sketchology.proto.Command.prototype.hasBackgroundColor = function() { + return this.has$Value(11); +}; + + +/** + * @return {number} The number of values in the background_color field. + */ +sketchology.proto.Command.prototype.backgroundColorCount = function() { + return this.count$Values(11); +}; + + +/** + * Clears the values in the background_color field. + */ +sketchology.proto.Command.prototype.clearBackgroundColor = function() { + this.clear$Field(11); +}; + + +/** + * Gets the value of the set_out_of_bounds_color field. + * @return {?sketchology.proto.OutOfBoundsColor} The value. + */ +sketchology.proto.Command.prototype.getSetOutOfBoundsColor = function() { + return /** @type {?sketchology.proto.OutOfBoundsColor} */ (this.get$Value(12)); +}; + + +/** + * Gets the value of the set_out_of_bounds_color field or the default value if not set. + * @return {!sketchology.proto.OutOfBoundsColor} The value. + */ +sketchology.proto.Command.prototype.getSetOutOfBoundsColorOrDefault = function() { + return /** @type {!sketchology.proto.OutOfBoundsColor} */ (this.get$ValueOrDefault(12)); +}; + + +/** + * Sets the value of the set_out_of_bounds_color field. + * @param {!sketchology.proto.OutOfBoundsColor} value The value. + */ +sketchology.proto.Command.prototype.setSetOutOfBoundsColor = function(value) { + this.set$Value(12, value); +}; + + +/** + * @return {boolean} Whether the set_out_of_bounds_color field has a value. + */ +sketchology.proto.Command.prototype.hasSetOutOfBoundsColor = function() { + return this.has$Value(12); +}; + + +/** + * @return {number} The number of values in the set_out_of_bounds_color field. + */ +sketchology.proto.Command.prototype.setOutOfBoundsColorCount = function() { + return this.count$Values(12); +}; + + +/** + * Clears the values in the set_out_of_bounds_color field. + */ +sketchology.proto.Command.prototype.clearSetOutOfBoundsColor = function() { + this.clear$Field(12); +}; + + +/** + * Gets the value of the set_page_border field. + * @return {?sketchology.proto.Border} The value. + */ +sketchology.proto.Command.prototype.getSetPageBorder = function() { + return /** @type {?sketchology.proto.Border} */ (this.get$Value(13)); +}; + + +/** + * Gets the value of the set_page_border field or the default value if not set. + * @return {!sketchology.proto.Border} The value. + */ +sketchology.proto.Command.prototype.getSetPageBorderOrDefault = function() { + return /** @type {!sketchology.proto.Border} */ (this.get$ValueOrDefault(13)); +}; + + +/** + * Sets the value of the set_page_border field. + * @param {!sketchology.proto.Border} value The value. + */ +sketchology.proto.Command.prototype.setSetPageBorder = function(value) { + this.set$Value(13, value); +}; + + +/** + * @return {boolean} Whether the set_page_border field has a value. + */ +sketchology.proto.Command.prototype.hasSetPageBorder = function() { + return this.has$Value(13); +}; + + +/** + * @return {number} The number of values in the set_page_border field. + */ +sketchology.proto.Command.prototype.setPageBorderCount = function() { + return this.count$Values(13); +}; + + +/** + * Clears the values in the set_page_border field. + */ +sketchology.proto.Command.prototype.clearSetPageBorder = function() { + this.clear$Field(13); +}; + + +/** + * Gets the value of the send_input_stream field. + * @return {?sketchology.proto.SInputStream} The value. + */ +sketchology.proto.Command.prototype.getSendInputStream = function() { + return /** @type {?sketchology.proto.SInputStream} */ (this.get$Value(14)); +}; + + +/** + * Gets the value of the send_input_stream field or the default value if not set. + * @return {!sketchology.proto.SInputStream} The value. + */ +sketchology.proto.Command.prototype.getSendInputStreamOrDefault = function() { + return /** @type {!sketchology.proto.SInputStream} */ (this.get$ValueOrDefault(14)); +}; + + +/** + * Sets the value of the send_input_stream field. + * @param {!sketchology.proto.SInputStream} value The value. + */ +sketchology.proto.Command.prototype.setSendInputStream = function(value) { + this.set$Value(14, value); +}; + + +/** + * @return {boolean} Whether the send_input_stream field has a value. + */ +sketchology.proto.Command.prototype.hasSendInputStream = function() { + return this.has$Value(14); +}; + + +/** + * @return {number} The number of values in the send_input_stream field. + */ +sketchology.proto.Command.prototype.sendInputStreamCount = function() { + return this.count$Values(14); +}; + + +/** + * Clears the values in the send_input_stream field. + */ +sketchology.proto.Command.prototype.clearSendInputStream = function() { + this.clear$Field(14); +}; + + +/** + * Gets the value of the sequence_point field. + * @return {?sketchology.proto.SequencePoint} The value. + */ +sketchology.proto.Command.prototype.getSequencePoint = function() { + return /** @type {?sketchology.proto.SequencePoint} */ (this.get$Value(15)); +}; + + +/** + * Gets the value of the sequence_point field or the default value if not set. + * @return {!sketchology.proto.SequencePoint} The value. + */ +sketchology.proto.Command.prototype.getSequencePointOrDefault = function() { + return /** @type {!sketchology.proto.SequencePoint} */ (this.get$ValueOrDefault(15)); +}; + + +/** + * Sets the value of the sequence_point field. + * @param {!sketchology.proto.SequencePoint} value The value. + */ +sketchology.proto.Command.prototype.setSequencePoint = function(value) { + this.set$Value(15, value); +}; + + +/** + * @return {boolean} Whether the sequence_point field has a value. + */ +sketchology.proto.Command.prototype.hasSequencePoint = function() { + return this.has$Value(15); +}; + + +/** + * @return {number} The number of values in the sequence_point field. + */ +sketchology.proto.Command.prototype.sequencePointCount = function() { + return this.count$Values(15); +}; + + +/** + * Clears the values in the sequence_point field. + */ +sketchology.proto.Command.prototype.clearSequencePoint = function() { + this.clear$Field(15); +}; + + +/** + * Gets the value of the set_callback_flags field. + * @return {?sketchology.proto.SetCallbackFlags} The value. + */ +sketchology.proto.Command.prototype.getSetCallbackFlags = function() { + return /** @type {?sketchology.proto.SetCallbackFlags} */ (this.get$Value(16)); +}; + + +/** + * Gets the value of the set_callback_flags field or the default value if not set. + * @return {!sketchology.proto.SetCallbackFlags} The value. + */ +sketchology.proto.Command.prototype.getSetCallbackFlagsOrDefault = function() { + return /** @type {!sketchology.proto.SetCallbackFlags} */ (this.get$ValueOrDefault(16)); +}; + + +/** + * Sets the value of the set_callback_flags field. + * @param {!sketchology.proto.SetCallbackFlags} value The value. + */ +sketchology.proto.Command.prototype.setSetCallbackFlags = function(value) { + this.set$Value(16, value); +}; + + +/** + * @return {boolean} Whether the set_callback_flags field has a value. + */ +sketchology.proto.Command.prototype.hasSetCallbackFlags = function() { + return this.has$Value(16); +}; + + +/** + * @return {number} The number of values in the set_callback_flags field. + */ +sketchology.proto.Command.prototype.setCallbackFlagsCount = function() { + return this.count$Values(16); +}; + + +/** + * Clears the values in the set_callback_flags field. + */ +sketchology.proto.Command.prototype.clearSetCallbackFlags = function() { + this.clear$Field(16); +}; + + +/** + * Gets the value of the set_camera_bounds_config field. + * @return {?sketchology.proto.CameraBoundsConfig} The value. + */ +sketchology.proto.Command.prototype.getSetCameraBoundsConfig = function() { + return /** @type {?sketchology.proto.CameraBoundsConfig} */ (this.get$Value(17)); +}; + + +/** + * Gets the value of the set_camera_bounds_config field or the default value if not set. + * @return {!sketchology.proto.CameraBoundsConfig} The value. + */ +sketchology.proto.Command.prototype.getSetCameraBoundsConfigOrDefault = function() { + return /** @type {!sketchology.proto.CameraBoundsConfig} */ (this.get$ValueOrDefault(17)); +}; + + +/** + * Sets the value of the set_camera_bounds_config field. + * @param {!sketchology.proto.CameraBoundsConfig} value The value. + */ +sketchology.proto.Command.prototype.setSetCameraBoundsConfig = function(value) { + this.set$Value(17, value); +}; + + +/** + * @return {boolean} Whether the set_camera_bounds_config field has a value. + */ +sketchology.proto.Command.prototype.hasSetCameraBoundsConfig = function() { + return this.has$Value(17); +}; + + +/** + * @return {number} The number of values in the set_camera_bounds_config field. + */ +sketchology.proto.Command.prototype.setCameraBoundsConfigCount = function() { + return this.count$Values(17); +}; + + +/** + * Clears the values in the set_camera_bounds_config field. + */ +sketchology.proto.Command.prototype.clearSetCameraBoundsConfig = function() { + this.clear$Field(17); +}; + + +/** + * Gets the value of the deselect_all field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getDeselectAll = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(18)); +}; + + +/** + * Gets the value of the deselect_all field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getDeselectAllOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(18)); +}; + + +/** + * Sets the value of the deselect_all field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setDeselectAll = function(value) { + this.set$Value(18, value); +}; + + +/** + * @return {boolean} Whether the deselect_all field has a value. + */ +sketchology.proto.Command.prototype.hasDeselectAll = function() { + return this.has$Value(18); +}; + + +/** + * @return {number} The number of values in the deselect_all field. + */ +sketchology.proto.Command.prototype.deselectAllCount = function() { + return this.count$Values(18); +}; + + +/** + * Clears the values in the deselect_all field. + */ +sketchology.proto.Command.prototype.clearDeselectAll = function() { + this.clear$Field(18); +}; + + +/** + * Gets the value of the add_image_rect field. + * @return {?sketchology.proto.ImageRect} The value. + */ +sketchology.proto.Command.prototype.getAddImageRect = function() { + return /** @type {?sketchology.proto.ImageRect} */ (this.get$Value(19)); +}; + + +/** + * Gets the value of the add_image_rect field or the default value if not set. + * @return {!sketchology.proto.ImageRect} The value. + */ +sketchology.proto.Command.prototype.getAddImageRectOrDefault = function() { + return /** @type {!sketchology.proto.ImageRect} */ (this.get$ValueOrDefault(19)); +}; + + +/** + * Sets the value of the add_image_rect field. + * @param {!sketchology.proto.ImageRect} value The value. + */ +sketchology.proto.Command.prototype.setAddImageRect = function(value) { + this.set$Value(19, value); +}; + + +/** + * @return {boolean} Whether the add_image_rect field has a value. + */ +sketchology.proto.Command.prototype.hasAddImageRect = function() { + return this.has$Value(19); +}; + + +/** + * @return {number} The number of values in the add_image_rect field. + */ +sketchology.proto.Command.prototype.addImageRectCount = function() { + return this.count$Values(19); +}; + + +/** + * Clears the values in the add_image_rect field. + */ +sketchology.proto.Command.prototype.clearAddImageRect = function() { + this.clear$Field(19); +}; + + +/** + * Gets the value of the clear field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getClear = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(21)); +}; + + +/** + * Gets the value of the clear field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getClearOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(21)); +}; + + +/** + * Sets the value of the clear field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setClear = function(value) { + this.set$Value(21, value); +}; + + +/** + * @return {boolean} Whether the clear field has a value. + */ +sketchology.proto.Command.prototype.hasClear = function() { + return this.has$Value(21); +}; + + +/** + * @return {number} The number of values in the clear field. + */ +sketchology.proto.Command.prototype.clearCount = function() { + return this.count$Values(21); +}; + + +/** + * Clears the values in the clear field. + */ +sketchology.proto.Command.prototype.clearClear = function() { + this.clear$Field(21); +}; + + +/** + * Gets the value of the remove_all_elements field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getRemoveAllElements = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(22)); +}; + + +/** + * Gets the value of the remove_all_elements field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getRemoveAllElementsOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(22)); +}; + + +/** + * Sets the value of the remove_all_elements field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setRemoveAllElements = function(value) { + this.set$Value(22, value); +}; + + +/** + * @return {boolean} Whether the remove_all_elements field has a value. + */ +sketchology.proto.Command.prototype.hasRemoveAllElements = function() { + return this.has$Value(22); +}; + + +/** + * @return {number} The number of values in the remove_all_elements field. + */ +sketchology.proto.Command.prototype.removeAllElementsCount = function() { + return this.count$Values(22); +}; + + +/** + * Clears the values in the remove_all_elements field. + */ +sketchology.proto.Command.prototype.clearRemoveAllElements = function() { + this.clear$Field(22); +}; + + +/** + * Gets the value of the undo field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getUndo = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(23)); +}; + + +/** + * Gets the value of the undo field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getUndoOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(23)); +}; + + +/** + * Sets the value of the undo field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setUndo = function(value) { + this.set$Value(23, value); +}; + + +/** + * @return {boolean} Whether the undo field has a value. + */ +sketchology.proto.Command.prototype.hasUndo = function() { + return this.has$Value(23); +}; + + +/** + * @return {number} The number of values in the undo field. + */ +sketchology.proto.Command.prototype.undoCount = function() { + return this.count$Values(23); +}; + + +/** + * Clears the values in the undo field. + */ +sketchology.proto.Command.prototype.clearUndo = function() { + this.clear$Field(23); +}; + + +/** + * Gets the value of the redo field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getRedo = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(24)); +}; + + +/** + * Gets the value of the redo field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getRedoOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(24)); +}; + + +/** + * Sets the value of the redo field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setRedo = function(value) { + this.set$Value(24, value); +}; + + +/** + * @return {boolean} Whether the redo field has a value. + */ +sketchology.proto.Command.prototype.hasRedo = function() { + return this.has$Value(24); +}; + + +/** + * @return {number} The number of values in the redo field. + */ +sketchology.proto.Command.prototype.redoCount = function() { + return this.count$Values(24); +}; + + +/** + * Clears the values in the redo field. + */ +sketchology.proto.Command.prototype.clearRedo = function() { + this.clear$Field(24); +}; + + +/** + * Gets the value of the evict_image_data field. + * @return {?sketchology.proto.EvictImageData} The value. + */ +sketchology.proto.Command.prototype.getEvictImageData = function() { + return /** @type {?sketchology.proto.EvictImageData} */ (this.get$Value(25)); +}; + + +/** + * Gets the value of the evict_image_data field or the default value if not set. + * @return {!sketchology.proto.EvictImageData} The value. + */ +sketchology.proto.Command.prototype.getEvictImageDataOrDefault = function() { + return /** @type {!sketchology.proto.EvictImageData} */ (this.get$ValueOrDefault(25)); +}; + + +/** + * Sets the value of the evict_image_data field. + * @param {!sketchology.proto.EvictImageData} value The value. + */ +sketchology.proto.Command.prototype.setEvictImageData = function(value) { + this.set$Value(25, value); +}; + + +/** + * @return {boolean} Whether the evict_image_data field has a value. + */ +sketchology.proto.Command.prototype.hasEvictImageData = function() { + return this.has$Value(25); +}; + + +/** + * @return {number} The number of values in the evict_image_data field. + */ +sketchology.proto.Command.prototype.evictImageDataCount = function() { + return this.count$Values(25); +}; + + +/** + * Clears the values in the evict_image_data field. + */ +sketchology.proto.Command.prototype.clearEvictImageData = function() { + this.clear$Field(25); +}; + + +/** + * Gets the value of the replace_elements field. + * @return {?sketchology.proto.ReplaceElementsCommand} The value. + */ +sketchology.proto.Command.prototype.getReplaceElements = function() { + return /** @type {?sketchology.proto.ReplaceElementsCommand} */ (this.get$Value(26)); +}; + + +/** + * Gets the value of the replace_elements field or the default value if not set. + * @return {!sketchology.proto.ReplaceElementsCommand} The value. + */ +sketchology.proto.Command.prototype.getReplaceElementsOrDefault = function() { + return /** @type {!sketchology.proto.ReplaceElementsCommand} */ (this.get$ValueOrDefault(26)); +}; + + +/** + * Sets the value of the replace_elements field. + * @param {!sketchology.proto.ReplaceElementsCommand} value The value. + */ +sketchology.proto.Command.prototype.setReplaceElements = function(value) { + this.set$Value(26, value); +}; + + +/** + * @return {boolean} Whether the replace_elements field has a value. + */ +sketchology.proto.Command.prototype.hasReplaceElements = function() { + return this.has$Value(26); +}; + + +/** + * @return {number} The number of values in the replace_elements field. + */ +sketchology.proto.Command.prototype.replaceElementsCount = function() { + return this.count$Values(26); +}; + + +/** + * Clears the values in the replace_elements field. + */ +sketchology.proto.Command.prototype.clearReplaceElements = function() { + this.clear$Field(26); +}; + + +/** + * Gets the value of the commit_crop field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getCommitCrop = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(27)); +}; + + +/** + * Gets the value of the commit_crop field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getCommitCropOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(27)); +}; + + +/** + * Sets the value of the commit_crop field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setCommitCrop = function(value) { + this.set$Value(27, value); +}; + + +/** + * @return {boolean} Whether the commit_crop field has a value. + */ +sketchology.proto.Command.prototype.hasCommitCrop = function() { + return this.has$Value(27); +}; + + +/** + * @return {number} The number of values in the commit_crop field. + */ +sketchology.proto.Command.prototype.commitCropCount = function() { + return this.count$Values(27); +}; + + +/** + * Clears the values in the commit_crop field. + */ +sketchology.proto.Command.prototype.clearCommitCrop = function() { + this.clear$Field(27); +}; + + +/** + * Gets the value of the element_animation field. + * @return {?sketchology.proto.ElementAnimation} The value. + */ +sketchology.proto.Command.prototype.getElementAnimation = function() { + return /** @type {?sketchology.proto.ElementAnimation} */ (this.get$Value(28)); +}; + + +/** + * Gets the value of the element_animation field or the default value if not set. + * @return {!sketchology.proto.ElementAnimation} The value. + */ +sketchology.proto.Command.prototype.getElementAnimationOrDefault = function() { + return /** @type {!sketchology.proto.ElementAnimation} */ (this.get$ValueOrDefault(28)); +}; + + +/** + * Sets the value of the element_animation field. + * @param {!sketchology.proto.ElementAnimation} value The value. + */ +sketchology.proto.Command.prototype.setElementAnimation = function(value) { + this.set$Value(28, value); +}; + + +/** + * @return {boolean} Whether the element_animation field has a value. + */ +sketchology.proto.Command.prototype.hasElementAnimation = function() { + return this.has$Value(28); +}; + + +/** + * @return {number} The number of values in the element_animation field. + */ +sketchology.proto.Command.prototype.elementAnimationCount = function() { + return this.count$Values(28); +}; + + +/** + * Clears the values in the element_animation field. + */ +sketchology.proto.Command.prototype.clearElementAnimation = function() { + this.clear$Field(28); +}; + + +/** + * Gets the value of the set_grid field. + * @return {?sketchology.proto.GridInfo} The value. + */ +sketchology.proto.Command.prototype.getSetGrid = function() { + return /** @type {?sketchology.proto.GridInfo} */ (this.get$Value(29)); +}; + + +/** + * Gets the value of the set_grid field or the default value if not set. + * @return {!sketchology.proto.GridInfo} The value. + */ +sketchology.proto.Command.prototype.getSetGridOrDefault = function() { + return /** @type {!sketchology.proto.GridInfo} */ (this.get$ValueOrDefault(29)); +}; + + +/** + * Sets the value of the set_grid field. + * @param {!sketchology.proto.GridInfo} value The value. + */ +sketchology.proto.Command.prototype.setSetGrid = function(value) { + this.set$Value(29, value); +}; + + +/** + * @return {boolean} Whether the set_grid field has a value. + */ +sketchology.proto.Command.prototype.hasSetGrid = function() { + return this.has$Value(29); +}; + + +/** + * @return {number} The number of values in the set_grid field. + */ +sketchology.proto.Command.prototype.setGridCount = function() { + return this.count$Values(29); +}; + + +/** + * Clears the values in the set_grid field. + */ +sketchology.proto.Command.prototype.clearSetGrid = function() { + this.clear$Field(29); +}; + + +/** + * Gets the value of the clear_grid field. + * @return {?sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getClearGrid = function() { + return /** @type {?sketchology.proto.NoArgCommand} */ (this.get$Value(30)); +}; + + +/** + * Gets the value of the clear_grid field or the default value if not set. + * @return {!sketchology.proto.NoArgCommand} The value. + */ +sketchology.proto.Command.prototype.getClearGridOrDefault = function() { + return /** @type {!sketchology.proto.NoArgCommand} */ (this.get$ValueOrDefault(30)); +}; + + +/** + * Sets the value of the clear_grid field. + * @param {!sketchology.proto.NoArgCommand} value The value. + */ +sketchology.proto.Command.prototype.setClearGrid = function(value) { + this.set$Value(30, value); +}; + + +/** + * @return {boolean} Whether the clear_grid field has a value. + */ +sketchology.proto.Command.prototype.hasClearGrid = function() { + return this.has$Value(30); +}; + + +/** + * @return {number} The number of values in the clear_grid field. + */ +sketchology.proto.Command.prototype.clearGridCount = function() { + return this.count$Values(30); +}; + + +/** + * Clears the values in the clear_grid field. + */ +sketchology.proto.Command.prototype.clearClearGrid = function() { + this.clear$Field(30); +}; + + + +/** + * Message CommandList. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.CommandList = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.CommandList, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.CommandList.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.CommandList} The cloned message. + * @override + */ +sketchology.proto.CommandList.prototype.clone; + + +/** + * Gets the value of the commands field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Command} The value. + */ +sketchology.proto.CommandList.prototype.getCommands = function(index) { + return /** @type {?sketchology.proto.Command} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the commands field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Command} The value. + */ +sketchology.proto.CommandList.prototype.getCommandsOrDefault = function(index) { + return /** @type {!sketchology.proto.Command} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the commands field. + * @param {!sketchology.proto.Command} value The value to add. + */ +sketchology.proto.CommandList.prototype.addCommands = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the commands field. + * @return {!Array<!sketchology.proto.Command>} The values in the field. + */ +sketchology.proto.CommandList.prototype.commandsArray = function() { + return /** @type {!Array<!sketchology.proto.Command>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the commands field has a value. + */ +sketchology.proto.CommandList.prototype.hasCommands = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the commands field. + */ +sketchology.proto.CommandList.prototype.commandsCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the commands field. + */ +sketchology.proto.CommandList.prototype.clearCommands = function() { + this.clear$Field(1); +}; + + + +/** + * Message NoArgCommand. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.NoArgCommand = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.NoArgCommand, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.NoArgCommand.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.NoArgCommand} The cloned message. + * @override + */ +sketchology.proto.NoArgCommand.prototype.clone; + + + +/** + * Message ReplaceElementsCommand. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ReplaceElementsCommand = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ReplaceElementsCommand, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ReplaceElementsCommand.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ReplaceElementsCommand} The cloned message. + * @override + */ +sketchology.proto.ReplaceElementsCommand.prototype.clone; + + +/** + * Gets the value of the uuids_to_remove field at the index given. + * @param {number} index The index to lookup. + * @return {?string} The value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.getUuidsToRemove = function(index) { + return /** @type {?string} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the uuids_to_remove field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {string} The value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.getUuidsToRemoveOrDefault = function(index) { + return /** @type {string} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the uuids_to_remove field. + * @param {string} value The value to add. + */ +sketchology.proto.ReplaceElementsCommand.prototype.addUuidsToRemove = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the uuids_to_remove field. + * @return {!Array<string>} The values in the field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.uuidsToRemoveArray = function() { + return /** @type {!Array<string>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the uuids_to_remove field has a value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.hasUuidsToRemove = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuids_to_remove field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.uuidsToRemoveCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuids_to_remove field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.clearUuidsToRemove = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the paths_to_add field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Path} The value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.getPathsToAdd = function(index) { + return /** @type {?sketchology.proto.Path} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the paths_to_add field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Path} The value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.getPathsToAddOrDefault = function(index) { + return /** @type {!sketchology.proto.Path} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the paths_to_add field. + * @param {!sketchology.proto.Path} value The value to add. + */ +sketchology.proto.ReplaceElementsCommand.prototype.addPathsToAdd = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the paths_to_add field. + * @return {!Array<!sketchology.proto.Path>} The values in the field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.pathsToAddArray = function() { + return /** @type {!Array<!sketchology.proto.Path>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the paths_to_add field has a value. + */ +sketchology.proto.ReplaceElementsCommand.prototype.hasPathsToAdd = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the paths_to_add field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.pathsToAddCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the paths_to_add field. + */ +sketchology.proto.ReplaceElementsCommand.prototype.clearPathsToAdd = function() { + this.clear$Field(2); +}; + + + +/** + * Message EvictImageData. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.EvictImageData = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.EvictImageData, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.EvictImageData.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.EvictImageData} The cloned message. + * @override + */ +sketchology.proto.EvictImageData.prototype.clone; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.EvictImageData.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.EvictImageData.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.EvictImageData.prototype.setUri = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.EvictImageData.prototype.hasUri = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.EvictImageData.prototype.uriCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.EvictImageData.prototype.clearUri = function() { + this.clear$Field(1); +}; + + + +/** + * Message Viewport. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.Viewport = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.Viewport, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.Viewport.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.Viewport} The cloned message. + * @override + */ +sketchology.proto.Viewport.prototype.clone; + + +/** + * Gets the value of the fbo_handle field. + * @return {?number} The value. + */ +sketchology.proto.Viewport.prototype.getFboHandle = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the fbo_handle field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Viewport.prototype.getFboHandleOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the fbo_handle field. + * @param {number} value The value. + */ +sketchology.proto.Viewport.prototype.setFboHandle = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the fbo_handle field has a value. + */ +sketchology.proto.Viewport.prototype.hasFboHandle = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the fbo_handle field. + */ +sketchology.proto.Viewport.prototype.fboHandleCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the fbo_handle field. + */ +sketchology.proto.Viewport.prototype.clearFboHandle = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the width field. + * @return {?number} The value. + */ +sketchology.proto.Viewport.prototype.getWidth = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the width field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Viewport.prototype.getWidthOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the width field. + * @param {number} value The value. + */ +sketchology.proto.Viewport.prototype.setWidth = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the width field has a value. + */ +sketchology.proto.Viewport.prototype.hasWidth = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the width field. + */ +sketchology.proto.Viewport.prototype.widthCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the width field. + */ +sketchology.proto.Viewport.prototype.clearWidth = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the height field. + * @return {?number} The value. + */ +sketchology.proto.Viewport.prototype.getHeight = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the height field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Viewport.prototype.getHeightOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the height field. + * @param {number} value The value. + */ +sketchology.proto.Viewport.prototype.setHeight = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the height field has a value. + */ +sketchology.proto.Viewport.prototype.hasHeight = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the height field. + */ +sketchology.proto.Viewport.prototype.heightCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the height field. + */ +sketchology.proto.Viewport.prototype.clearHeight = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the ppi field. + * @return {?number} The value. + */ +sketchology.proto.Viewport.prototype.getPpi = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the ppi field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.Viewport.prototype.getPpiOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the ppi field. + * @param {number} value The value. + */ +sketchology.proto.Viewport.prototype.setPpi = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the ppi field has a value. + */ +sketchology.proto.Viewport.prototype.hasPpi = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the ppi field. + */ +sketchology.proto.Viewport.prototype.ppiCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the ppi field. + */ +sketchology.proto.Viewport.prototype.clearPpi = function() { + this.clear$Field(4); +}; + + + +/** + * Message ImageExport. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ImageExport = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ImageExport, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ImageExport.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ImageExport} The cloned message. + * @override + */ +sketchology.proto.ImageExport.prototype.clone; + + +/** + * Gets the value of the max_dimension_px field. + * @return {?number} The value. + */ +sketchology.proto.ImageExport.prototype.getMaxDimensionPx = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the max_dimension_px field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ImageExport.prototype.getMaxDimensionPxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the max_dimension_px field. + * @param {number} value The value. + */ +sketchology.proto.ImageExport.prototype.setMaxDimensionPx = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the max_dimension_px field has a value. + */ +sketchology.proto.ImageExport.prototype.hasMaxDimensionPx = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the max_dimension_px field. + */ +sketchology.proto.ImageExport.prototype.maxDimensionPxCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the max_dimension_px field. + */ +sketchology.proto.ImageExport.prototype.clearMaxDimensionPx = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the should_draw_background field. + * @return {?boolean} The value. + */ +sketchology.proto.ImageExport.prototype.getShouldDrawBackground = function() { + return /** @type {?boolean} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the should_draw_background field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.ImageExport.prototype.getShouldDrawBackgroundOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the should_draw_background field. + * @param {boolean} value The value. + */ +sketchology.proto.ImageExport.prototype.setShouldDrawBackground = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the should_draw_background field has a value. + */ +sketchology.proto.ImageExport.prototype.hasShouldDrawBackground = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the should_draw_background field. + */ +sketchology.proto.ImageExport.prototype.shouldDrawBackgroundCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the should_draw_background field. + */ +sketchology.proto.ImageExport.prototype.clearShouldDrawBackground = function() { + this.clear$Field(2); +}; + + + +/** + * Message LinearPathAnimation. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.LinearPathAnimation = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.LinearPathAnimation, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.LinearPathAnimation.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.LinearPathAnimation} The cloned message. + * @override + */ +sketchology.proto.LinearPathAnimation.prototype.clone; + + +/** + * Gets the value of the rgba_from field. + * @return {?number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getRgbaFrom = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the rgba_from field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getRgbaFromOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the rgba_from field. + * @param {number} value The value. + */ +sketchology.proto.LinearPathAnimation.prototype.setRgbaFrom = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the rgba_from field has a value. + */ +sketchology.proto.LinearPathAnimation.prototype.hasRgbaFrom = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the rgba_from field. + */ +sketchology.proto.LinearPathAnimation.prototype.rgbaFromCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the rgba_from field. + */ +sketchology.proto.LinearPathAnimation.prototype.clearRgbaFrom = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the rgba_seconds field. + * @return {?number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getRgbaSeconds = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the rgba_seconds field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getRgbaSecondsOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the rgba_seconds field. + * @param {number} value The value. + */ +sketchology.proto.LinearPathAnimation.prototype.setRgbaSeconds = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the rgba_seconds field has a value. + */ +sketchology.proto.LinearPathAnimation.prototype.hasRgbaSeconds = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the rgba_seconds field. + */ +sketchology.proto.LinearPathAnimation.prototype.rgbaSecondsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the rgba_seconds field. + */ +sketchology.proto.LinearPathAnimation.prototype.clearRgbaSeconds = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the dilation_from field. + * @return {?number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getDilationFrom = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the dilation_from field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getDilationFromOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the dilation_from field. + * @param {number} value The value. + */ +sketchology.proto.LinearPathAnimation.prototype.setDilationFrom = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the dilation_from field has a value. + */ +sketchology.proto.LinearPathAnimation.prototype.hasDilationFrom = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the dilation_from field. + */ +sketchology.proto.LinearPathAnimation.prototype.dilationFromCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the dilation_from field. + */ +sketchology.proto.LinearPathAnimation.prototype.clearDilationFrom = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the dilation_seconds field. + * @return {?number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getDilationSeconds = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the dilation_seconds field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LinearPathAnimation.prototype.getDilationSecondsOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the dilation_seconds field. + * @param {number} value The value. + */ +sketchology.proto.LinearPathAnimation.prototype.setDilationSeconds = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the dilation_seconds field has a value. + */ +sketchology.proto.LinearPathAnimation.prototype.hasDilationSeconds = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the dilation_seconds field. + */ +sketchology.proto.LinearPathAnimation.prototype.dilationSecondsCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the dilation_seconds field. + */ +sketchology.proto.LinearPathAnimation.prototype.clearDilationSeconds = function() { + this.clear$Field(4); +}; + + + +/** + * Message LineSize. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.LineSize = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.LineSize, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.LineSize.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.LineSize} The cloned message. + * @override + */ +sketchology.proto.LineSize.prototype.clone; + + +/** + * Gets the value of the stroke_width field. + * @return {?number} The value. + */ +sketchology.proto.LineSize.prototype.getStrokeWidth = function() { + return /** @type {?number} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the stroke_width field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.LineSize.prototype.getStrokeWidthOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the stroke_width field. + * @param {number} value The value. + */ +sketchology.proto.LineSize.prototype.setStrokeWidth = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the stroke_width field has a value. + */ +sketchology.proto.LineSize.prototype.hasStrokeWidth = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the stroke_width field. + */ +sketchology.proto.LineSize.prototype.strokeWidthCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the stroke_width field. + */ +sketchology.proto.LineSize.prototype.clearStrokeWidth = function() { + this.clear$Field(7); +}; + + +/** + * Gets the value of the units field. + * @return {?sketchology.proto.LineSize.SizeType} The value. + */ +sketchology.proto.LineSize.prototype.getUnits = function() { + return /** @type {?sketchology.proto.LineSize.SizeType} */ (this.get$Value(8)); +}; + + +/** + * Gets the value of the units field or the default value if not set. + * @return {!sketchology.proto.LineSize.SizeType} The value. + */ +sketchology.proto.LineSize.prototype.getUnitsOrDefault = function() { + return /** @type {!sketchology.proto.LineSize.SizeType} */ (this.get$ValueOrDefault(8)); +}; + + +/** + * Sets the value of the units field. + * @param {!sketchology.proto.LineSize.SizeType} value The value. + */ +sketchology.proto.LineSize.prototype.setUnits = function(value) { + this.set$Value(8, value); +}; + + +/** + * @return {boolean} Whether the units field has a value. + */ +sketchology.proto.LineSize.prototype.hasUnits = function() { + return this.has$Value(8); +}; + + +/** + * @return {number} The number of values in the units field. + */ +sketchology.proto.LineSize.prototype.unitsCount = function() { + return this.count$Values(8); +}; + + +/** + * Clears the values in the units field. + */ +sketchology.proto.LineSize.prototype.clearUnits = function() { + this.clear$Field(8); +}; + + +/** + * Enumeration SizeType. + * @enum {number} + */ +sketchology.proto.LineSize.SizeType = { + UNKNOWN_SIZE: 0, + WORLD_UNITS: 1, + POINTS: 2, + ZOOM_INDEPENDENT_DP: 3, + PERCENT_WORLD: 4, + PERCENT_ZOOM_INDEPENDENT: 5 +}; + + + +/** + * Message PusherToolParams. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.PusherToolParams = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.PusherToolParams, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.PusherToolParams.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.PusherToolParams} The cloned message. + * @override + */ +sketchology.proto.PusherToolParams.prototype.clone; + + +/** + * Gets the value of the manipulate_stickers field. + * @return {?boolean} The value. + */ +sketchology.proto.PusherToolParams.prototype.getManipulateStickers = function() { + return /** @type {?boolean} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the manipulate_stickers field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.PusherToolParams.prototype.getManipulateStickersOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the manipulate_stickers field. + * @param {boolean} value The value. + */ +sketchology.proto.PusherToolParams.prototype.setManipulateStickers = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the manipulate_stickers field has a value. + */ +sketchology.proto.PusherToolParams.prototype.hasManipulateStickers = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the manipulate_stickers field. + */ +sketchology.proto.PusherToolParams.prototype.manipulateStickersCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the manipulate_stickers field. + */ +sketchology.proto.PusherToolParams.prototype.clearManipulateStickers = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the manipulate_text field. + * @return {?boolean} The value. + */ +sketchology.proto.PusherToolParams.prototype.getManipulateText = function() { + return /** @type {?boolean} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the manipulate_text field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.PusherToolParams.prototype.getManipulateTextOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the manipulate_text field. + * @param {boolean} value The value. + */ +sketchology.proto.PusherToolParams.prototype.setManipulateText = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the manipulate_text field has a value. + */ +sketchology.proto.PusherToolParams.prototype.hasManipulateText = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the manipulate_text field. + */ +sketchology.proto.PusherToolParams.prototype.manipulateTextCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the manipulate_text field. + */ +sketchology.proto.PusherToolParams.prototype.clearManipulateText = function() { + this.clear$Field(2); +}; + + + +/** + * Message ToolParams. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ToolParams = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ToolParams, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ToolParams.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ToolParams} The cloned message. + * @override + */ +sketchology.proto.ToolParams.prototype.clone; + + +/** + * Gets the value of the tool field. + * @return {?sketchology.proto.ToolParams.ToolType} The value. + */ +sketchology.proto.ToolParams.prototype.getTool = function() { + return /** @type {?sketchology.proto.ToolParams.ToolType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the tool field or the default value if not set. + * @return {!sketchology.proto.ToolParams.ToolType} The value. + */ +sketchology.proto.ToolParams.prototype.getToolOrDefault = function() { + return /** @type {!sketchology.proto.ToolParams.ToolType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the tool field. + * @param {!sketchology.proto.ToolParams.ToolType} value The value. + */ +sketchology.proto.ToolParams.prototype.setTool = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the tool field has a value. + */ +sketchology.proto.ToolParams.prototype.hasTool = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the tool field. + */ +sketchology.proto.ToolParams.prototype.toolCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the tool field. + */ +sketchology.proto.ToolParams.prototype.clearTool = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.ToolParams.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ToolParams.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.ToolParams.prototype.setRgba = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.ToolParams.prototype.hasRgba = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.ToolParams.prototype.rgbaCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.ToolParams.prototype.clearRgba = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the line_size field. + * @return {?sketchology.proto.LineSize} The value. + */ +sketchology.proto.ToolParams.prototype.getLineSize = function() { + return /** @type {?sketchology.proto.LineSize} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the line_size field or the default value if not set. + * @return {!sketchology.proto.LineSize} The value. + */ +sketchology.proto.ToolParams.prototype.getLineSizeOrDefault = function() { + return /** @type {!sketchology.proto.LineSize} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the line_size field. + * @param {!sketchology.proto.LineSize} value The value. + */ +sketchology.proto.ToolParams.prototype.setLineSize = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the line_size field has a value. + */ +sketchology.proto.ToolParams.prototype.hasLineSize = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the line_size field. + */ +sketchology.proto.ToolParams.prototype.lineSizeCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the line_size field. + */ +sketchology.proto.ToolParams.prototype.clearLineSize = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the pusher_tool_params field. + * @return {?sketchology.proto.PusherToolParams} The value. + */ +sketchology.proto.ToolParams.prototype.getPusherToolParams = function() { + return /** @type {?sketchology.proto.PusherToolParams} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the pusher_tool_params field or the default value if not set. + * @return {!sketchology.proto.PusherToolParams} The value. + */ +sketchology.proto.ToolParams.prototype.getPusherToolParamsOrDefault = function() { + return /** @type {!sketchology.proto.PusherToolParams} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the pusher_tool_params field. + * @param {!sketchology.proto.PusherToolParams} value The value. + */ +sketchology.proto.ToolParams.prototype.setPusherToolParams = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the pusher_tool_params field has a value. + */ +sketchology.proto.ToolParams.prototype.hasPusherToolParams = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the pusher_tool_params field. + */ +sketchology.proto.ToolParams.prototype.pusherToolParamsCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the pusher_tool_params field. + */ +sketchology.proto.ToolParams.prototype.clearPusherToolParams = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the brush_type field. + * @return {?sketchology.proto.BrushType} The value. + */ +sketchology.proto.ToolParams.prototype.getBrushType = function() { + return /** @type {?sketchology.proto.BrushType} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the brush_type field or the default value if not set. + * @return {!sketchology.proto.BrushType} The value. + */ +sketchology.proto.ToolParams.prototype.getBrushTypeOrDefault = function() { + return /** @type {!sketchology.proto.BrushType} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the brush_type field. + * @param {!sketchology.proto.BrushType} value The value. + */ +sketchology.proto.ToolParams.prototype.setBrushType = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the brush_type field has a value. + */ +sketchology.proto.ToolParams.prototype.hasBrushType = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the brush_type field. + */ +sketchology.proto.ToolParams.prototype.brushTypeCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the brush_type field. + */ +sketchology.proto.ToolParams.prototype.clearBrushType = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the linear_path_animation field. + * @return {?sketchology.proto.LinearPathAnimation} The value. + */ +sketchology.proto.ToolParams.prototype.getLinearPathAnimation = function() { + return /** @type {?sketchology.proto.LinearPathAnimation} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the linear_path_animation field or the default value if not set. + * @return {!sketchology.proto.LinearPathAnimation} The value. + */ +sketchology.proto.ToolParams.prototype.getLinearPathAnimationOrDefault = function() { + return /** @type {!sketchology.proto.LinearPathAnimation} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the linear_path_animation field. + * @param {!sketchology.proto.LinearPathAnimation} value The value. + */ +sketchology.proto.ToolParams.prototype.setLinearPathAnimation = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the linear_path_animation field has a value. + */ +sketchology.proto.ToolParams.prototype.hasLinearPathAnimation = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the linear_path_animation field. + */ +sketchology.proto.ToolParams.prototype.linearPathAnimationCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the linear_path_animation field. + */ +sketchology.proto.ToolParams.prototype.clearLinearPathAnimation = function() { + this.clear$Field(6); +}; + + +/** + * Enumeration ToolType. + * @enum {number} + */ +sketchology.proto.ToolParams.ToolType = { + UNKNOWN: 0, + LINE: 1, + EDIT: 2, + MAGIC_ERASE: 3, + QUERY: 4, + NOTOOL: 5, + FILTER_CHOOSER: 6, + PUSHER: 7, + CROP: 8 +}; + + + +/** + * Message FlagAssignment. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.FlagAssignment = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.FlagAssignment, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.FlagAssignment.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.FlagAssignment} The cloned message. + * @override + */ +sketchology.proto.FlagAssignment.prototype.clone; + + +/** + * Gets the value of the flag field. + * @return {?sketchology.proto.Flag} The value. + */ +sketchology.proto.FlagAssignment.prototype.getFlag = function() { + return /** @type {?sketchology.proto.Flag} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the flag field or the default value if not set. + * @return {!sketchology.proto.Flag} The value. + */ +sketchology.proto.FlagAssignment.prototype.getFlagOrDefault = function() { + return /** @type {!sketchology.proto.Flag} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the flag field. + * @param {!sketchology.proto.Flag} value The value. + */ +sketchology.proto.FlagAssignment.prototype.setFlag = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the flag field has a value. + */ +sketchology.proto.FlagAssignment.prototype.hasFlag = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the flag field. + */ +sketchology.proto.FlagAssignment.prototype.flagCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the flag field. + */ +sketchology.proto.FlagAssignment.prototype.clearFlag = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the bool_value field. + * @return {?boolean} The value. + */ +sketchology.proto.FlagAssignment.prototype.getBoolValue = function() { + return /** @type {?boolean} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the bool_value field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.FlagAssignment.prototype.getBoolValueOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the bool_value field. + * @param {boolean} value The value. + */ +sketchology.proto.FlagAssignment.prototype.setBoolValue = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the bool_value field has a value. + */ +sketchology.proto.FlagAssignment.prototype.hasBoolValue = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the bool_value field. + */ +sketchology.proto.FlagAssignment.prototype.boolValueCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the bool_value field. + */ +sketchology.proto.FlagAssignment.prototype.clearBoolValue = function() { + this.clear$Field(2); +}; + + + +/** + * Message AddElement. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.AddElement = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.AddElement, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.AddElement.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.AddElement} The cloned message. + * @override + */ +sketchology.proto.AddElement.prototype.clone; + + +/** + * Gets the value of the bundle field. + * @return {?sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.AddElement.prototype.getBundle = function() { + return /** @type {?sketchology.proto.ElementBundle} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the bundle field or the default value if not set. + * @return {!sketchology.proto.ElementBundle} The value. + */ +sketchology.proto.AddElement.prototype.getBundleOrDefault = function() { + return /** @type {!sketchology.proto.ElementBundle} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the bundle field. + * @param {!sketchology.proto.ElementBundle} value The value. + */ +sketchology.proto.AddElement.prototype.setBundle = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the bundle field has a value. + */ +sketchology.proto.AddElement.prototype.hasBundle = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the bundle field. + */ +sketchology.proto.AddElement.prototype.bundleCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the bundle field. + */ +sketchology.proto.AddElement.prototype.clearBundle = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the below_element_with_uuid field. + * @return {?string} The value. + */ +sketchology.proto.AddElement.prototype.getBelowElementWithUuid = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the below_element_with_uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.AddElement.prototype.getBelowElementWithUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the below_element_with_uuid field. + * @param {string} value The value. + */ +sketchology.proto.AddElement.prototype.setBelowElementWithUuid = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the below_element_with_uuid field has a value. + */ +sketchology.proto.AddElement.prototype.hasBelowElementWithUuid = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the below_element_with_uuid field. + */ +sketchology.proto.AddElement.prototype.belowElementWithUuidCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the below_element_with_uuid field. + */ +sketchology.proto.AddElement.prototype.clearBelowElementWithUuid = function() { + this.clear$Field(2); +}; + + + +/** + * Message OutOfBoundsColor. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.OutOfBoundsColor = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.OutOfBoundsColor, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.OutOfBoundsColor.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.OutOfBoundsColor} The cloned message. + * @override + */ +sketchology.proto.OutOfBoundsColor.prototype.clone; + + +/** + * Gets the value of the rgba field. + * @return {?number} The value. + */ +sketchology.proto.OutOfBoundsColor.prototype.getRgba = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the rgba field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.OutOfBoundsColor.prototype.getRgbaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the rgba field. + * @param {number} value The value. + */ +sketchology.proto.OutOfBoundsColor.prototype.setRgba = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the rgba field has a value. + */ +sketchology.proto.OutOfBoundsColor.prototype.hasRgba = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the rgba field. + */ +sketchology.proto.OutOfBoundsColor.prototype.rgbaCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the rgba field. + */ +sketchology.proto.OutOfBoundsColor.prototype.clearRgba = function() { + this.clear$Field(1); +}; + + + +/** + * Message SInputStream. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SInputStream = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SInputStream, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SInputStream.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SInputStream} The cloned message. + * @override + */ +sketchology.proto.SInputStream.prototype.clone; + + +/** + * Gets the value of the screen_width field. + * @return {?number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenWidth = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the screen_width field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenWidthOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the screen_width field. + * @param {number} value The value. + */ +sketchology.proto.SInputStream.prototype.setScreenWidth = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the screen_width field has a value. + */ +sketchology.proto.SInputStream.prototype.hasScreenWidth = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the screen_width field. + */ +sketchology.proto.SInputStream.prototype.screenWidthCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the screen_width field. + */ +sketchology.proto.SInputStream.prototype.clearScreenWidth = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the screen_height field. + * @return {?number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenHeight = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the screen_height field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenHeightOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the screen_height field. + * @param {number} value The value. + */ +sketchology.proto.SInputStream.prototype.setScreenHeight = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the screen_height field has a value. + */ +sketchology.proto.SInputStream.prototype.hasScreenHeight = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the screen_height field. + */ +sketchology.proto.SInputStream.prototype.screenHeightCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the screen_height field. + */ +sketchology.proto.SInputStream.prototype.clearScreenHeight = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the screen_ppi field. + * @return {?number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenPpi = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the screen_ppi field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInputStream.prototype.getScreenPpiOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the screen_ppi field. + * @param {number} value The value. + */ +sketchology.proto.SInputStream.prototype.setScreenPpi = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the screen_ppi field has a value. + */ +sketchology.proto.SInputStream.prototype.hasScreenPpi = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the screen_ppi field. + */ +sketchology.proto.SInputStream.prototype.screenPpiCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the screen_ppi field. + */ +sketchology.proto.SInputStream.prototype.clearScreenPpi = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the input field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.SInput} The value. + */ +sketchology.proto.SInputStream.prototype.getInput = function(index) { + return /** @type {?sketchology.proto.SInput} */ (this.get$Value(4, index)); +}; + + +/** + * Gets the value of the input field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.SInput} The value. + */ +sketchology.proto.SInputStream.prototype.getInputOrDefault = function(index) { + return /** @type {!sketchology.proto.SInput} */ (this.get$ValueOrDefault(4, index)); +}; + + +/** + * Adds a value to the input field. + * @param {!sketchology.proto.SInput} value The value to add. + */ +sketchology.proto.SInputStream.prototype.addInput = function(value) { + this.add$Value(4, value); +}; + + +/** + * Returns the array of values in the input field. + * @return {!Array<!sketchology.proto.SInput>} The values in the field. + */ +sketchology.proto.SInputStream.prototype.inputArray = function() { + return /** @type {!Array<!sketchology.proto.SInput>} */ (this.array$Values(4)); +}; + + +/** + * @return {boolean} Whether the input field has a value. + */ +sketchology.proto.SInputStream.prototype.hasInput = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the input field. + */ +sketchology.proto.SInputStream.prototype.inputCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the input field. + */ +sketchology.proto.SInputStream.prototype.clearInput = function() { + this.clear$Field(4); +}; + + + +/** + * Message SInput. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SInput = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SInput, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SInput.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SInput} The cloned message. + * @override + */ +sketchology.proto.SInput.prototype.clone; + + +/** + * Gets the value of the type field. + * @return {?sketchology.proto.SInput.InputType} The value. + */ +sketchology.proto.SInput.prototype.getType = function() { + return /** @type {?sketchology.proto.SInput.InputType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the type field or the default value if not set. + * @return {!sketchology.proto.SInput.InputType} The value. + */ +sketchology.proto.SInput.prototype.getTypeOrDefault = function() { + return /** @type {!sketchology.proto.SInput.InputType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the type field. + * @param {!sketchology.proto.SInput.InputType} value The value. + */ +sketchology.proto.SInput.prototype.setType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the type field has a value. + */ +sketchology.proto.SInput.prototype.hasType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the type field. + */ +sketchology.proto.SInput.prototype.typeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the type field. + */ +sketchology.proto.SInput.prototype.clearType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the id field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getId = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the id field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getIdOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the id field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setId = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the id field has a value. + */ +sketchology.proto.SInput.prototype.hasId = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the id field. + */ +sketchology.proto.SInput.prototype.idCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the id field. + */ +sketchology.proto.SInput.prototype.clearId = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the flags field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getFlags = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the flags field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getFlagsOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the flags field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setFlags = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the flags field has a value. + */ +sketchology.proto.SInput.prototype.hasFlags = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the flags field. + */ +sketchology.proto.SInput.prototype.flagsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the flags field. + */ +sketchology.proto.SInput.prototype.clearFlags = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the time_s field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getTimeS = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the time_s field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getTimeSOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the time_s field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setTimeS = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the time_s field has a value. + */ +sketchology.proto.SInput.prototype.hasTimeS = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the time_s field. + */ +sketchology.proto.SInput.prototype.timeSCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the time_s field. + */ +sketchology.proto.SInput.prototype.clearTimeS = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the screen_pos_x field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getScreenPosX = function() { + return /** @type {?number} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the screen_pos_x field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getScreenPosXOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the screen_pos_x field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setScreenPosX = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the screen_pos_x field has a value. + */ +sketchology.proto.SInput.prototype.hasScreenPosX = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the screen_pos_x field. + */ +sketchology.proto.SInput.prototype.screenPosXCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the screen_pos_x field. + */ +sketchology.proto.SInput.prototype.clearScreenPosX = function() { + this.clear$Field(5); +}; + + +/** + * Gets the value of the screen_pos_y field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getScreenPosY = function() { + return /** @type {?number} */ (this.get$Value(6)); +}; + + +/** + * Gets the value of the screen_pos_y field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getScreenPosYOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(6)); +}; + + +/** + * Sets the value of the screen_pos_y field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setScreenPosY = function(value) { + this.set$Value(6, value); +}; + + +/** + * @return {boolean} Whether the screen_pos_y field has a value. + */ +sketchology.proto.SInput.prototype.hasScreenPosY = function() { + return this.has$Value(6); +}; + + +/** + * @return {number} The number of values in the screen_pos_y field. + */ +sketchology.proto.SInput.prototype.screenPosYCount = function() { + return this.count$Values(6); +}; + + +/** + * Clears the values in the screen_pos_y field. + */ +sketchology.proto.SInput.prototype.clearScreenPosY = function() { + this.clear$Field(6); +}; + + +/** + * Gets the value of the pressure field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getPressure = function() { + return /** @type {?number} */ (this.get$Value(7)); +}; + + +/** + * Gets the value of the pressure field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getPressureOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(7)); +}; + + +/** + * Sets the value of the pressure field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setPressure = function(value) { + this.set$Value(7, value); +}; + + +/** + * @return {boolean} Whether the pressure field has a value. + */ +sketchology.proto.SInput.prototype.hasPressure = function() { + return this.has$Value(7); +}; + + +/** + * @return {number} The number of values in the pressure field. + */ +sketchology.proto.SInput.prototype.pressureCount = function() { + return this.count$Values(7); +}; + + +/** + * Clears the values in the pressure field. + */ +sketchology.proto.SInput.prototype.clearPressure = function() { + this.clear$Field(7); +}; + + +/** + * Gets the value of the wheel_delta field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getWheelDelta = function() { + return /** @type {?number} */ (this.get$Value(8)); +}; + + +/** + * Gets the value of the wheel_delta field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getWheelDeltaOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(8)); +}; + + +/** + * Sets the value of the wheel_delta field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setWheelDelta = function(value) { + this.set$Value(8, value); +}; + + +/** + * @return {boolean} Whether the wheel_delta field has a value. + */ +sketchology.proto.SInput.prototype.hasWheelDelta = function() { + return this.has$Value(8); +}; + + +/** + * @return {number} The number of values in the wheel_delta field. + */ +sketchology.proto.SInput.prototype.wheelDeltaCount = function() { + return this.count$Values(8); +}; + + +/** + * Clears the values in the wheel_delta field. + */ +sketchology.proto.SInput.prototype.clearWheelDelta = function() { + this.clear$Field(8); +}; + + +/** + * Gets the value of the tilt field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getTilt = function() { + return /** @type {?number} */ (this.get$Value(9)); +}; + + +/** + * Gets the value of the tilt field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getTiltOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(9)); +}; + + +/** + * Sets the value of the tilt field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setTilt = function(value) { + this.set$Value(9, value); +}; + + +/** + * @return {boolean} Whether the tilt field has a value. + */ +sketchology.proto.SInput.prototype.hasTilt = function() { + return this.has$Value(9); +}; + + +/** + * @return {number} The number of values in the tilt field. + */ +sketchology.proto.SInput.prototype.tiltCount = function() { + return this.count$Values(9); +}; + + +/** + * Clears the values in the tilt field. + */ +sketchology.proto.SInput.prototype.clearTilt = function() { + this.clear$Field(9); +}; + + +/** + * Gets the value of the orientation field. + * @return {?number} The value. + */ +sketchology.proto.SInput.prototype.getOrientation = function() { + return /** @type {?number} */ (this.get$Value(10)); +}; + + +/** + * Gets the value of the orientation field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SInput.prototype.getOrientationOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(10)); +}; + + +/** + * Sets the value of the orientation field. + * @param {number} value The value. + */ +sketchology.proto.SInput.prototype.setOrientation = function(value) { + this.set$Value(10, value); +}; + + +/** + * @return {boolean} Whether the orientation field has a value. + */ +sketchology.proto.SInput.prototype.hasOrientation = function() { + return this.has$Value(10); +}; + + +/** + * @return {number} The number of values in the orientation field. + */ +sketchology.proto.SInput.prototype.orientationCount = function() { + return this.count$Values(10); +}; + + +/** + * Clears the values in the orientation field. + */ +sketchology.proto.SInput.prototype.clearOrientation = function() { + this.clear$Field(10); +}; + + +/** + * Enumeration InputType. + * @enum {number} + */ +sketchology.proto.SInput.InputType = { + UNKNOWN: 0, + MOUSE: 1, + TOUCH: 2, + PEN: 3, + ERASER: 4 +}; + + + +/** + * Message SimulatedInput. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SimulatedInput = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SimulatedInput, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SimulatedInput.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SimulatedInput} The cloned message. + * @override + */ +sketchology.proto.SimulatedInput.prototype.clone; + + +/** + * Gets the value of the xs field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getXs = function(index) { + return /** @type {?number} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the xs field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getXsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the xs field. + * @param {number} value The value to add. + */ +sketchology.proto.SimulatedInput.prototype.addXs = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the xs field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.SimulatedInput.prototype.xsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the xs field has a value. + */ +sketchology.proto.SimulatedInput.prototype.hasXs = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the xs field. + */ +sketchology.proto.SimulatedInput.prototype.xsCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the xs field. + */ +sketchology.proto.SimulatedInput.prototype.clearXs = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the ys field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getYs = function(index) { + return /** @type {?number} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the ys field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getYsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the ys field. + * @param {number} value The value to add. + */ +sketchology.proto.SimulatedInput.prototype.addYs = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the ys field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.SimulatedInput.prototype.ysArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the ys field has a value. + */ +sketchology.proto.SimulatedInput.prototype.hasYs = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the ys field. + */ +sketchology.proto.SimulatedInput.prototype.ysCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the ys field. + */ +sketchology.proto.SimulatedInput.prototype.clearYs = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the ts_secs field at the index given. + * @param {number} index The index to lookup. + * @return {?number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getTsSecs = function(index) { + return /** @type {?number} */ (this.get$Value(3, index)); +}; + + +/** + * Gets the value of the ts_secs field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {number} The value. + */ +sketchology.proto.SimulatedInput.prototype.getTsSecsOrDefault = function(index) { + return /** @type {number} */ (this.get$ValueOrDefault(3, index)); +}; + + +/** + * Adds a value to the ts_secs field. + * @param {number} value The value to add. + */ +sketchology.proto.SimulatedInput.prototype.addTsSecs = function(value) { + this.add$Value(3, value); +}; + + +/** + * Returns the array of values in the ts_secs field. + * @return {!Array<number>} The values in the field. + */ +sketchology.proto.SimulatedInput.prototype.tsSecsArray = function() { + return /** @type {!Array<number>} */ (this.array$Values(3)); +}; + + +/** + * @return {boolean} Whether the ts_secs field has a value. + */ +sketchology.proto.SimulatedInput.prototype.hasTsSecs = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the ts_secs field. + */ +sketchology.proto.SimulatedInput.prototype.tsSecsCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the ts_secs field. + */ +sketchology.proto.SimulatedInput.prototype.clearTsSecs = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the source_details field. + * @return {?sketchology.proto.SourceDetails} The value. + */ +sketchology.proto.SimulatedInput.prototype.getSourceDetails = function() { + return /** @type {?sketchology.proto.SourceDetails} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the source_details field or the default value if not set. + * @return {!sketchology.proto.SourceDetails} The value. + */ +sketchology.proto.SimulatedInput.prototype.getSourceDetailsOrDefault = function() { + return /** @type {!sketchology.proto.SourceDetails} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the source_details field. + * @param {!sketchology.proto.SourceDetails} value The value. + */ +sketchology.proto.SimulatedInput.prototype.setSourceDetails = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the source_details field has a value. + */ +sketchology.proto.SimulatedInput.prototype.hasSourceDetails = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the source_details field. + */ +sketchology.proto.SimulatedInput.prototype.sourceDetailsCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the source_details field. + */ +sketchology.proto.SimulatedInput.prototype.clearSourceDetails = function() { + this.clear$Field(4); +}; + + + +/** + * Message SequencePoint. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SequencePoint = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SequencePoint, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SequencePoint.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SequencePoint} The cloned message. + * @override + */ +sketchology.proto.SequencePoint.prototype.clone; + + +/** + * Gets the value of the id field. + * @return {?number} The value. + */ +sketchology.proto.SequencePoint.prototype.getId = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the id field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.SequencePoint.prototype.getIdOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the id field. + * @param {number} value The value. + */ +sketchology.proto.SequencePoint.prototype.setId = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the id field has a value. + */ +sketchology.proto.SequencePoint.prototype.hasId = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the id field. + */ +sketchology.proto.SequencePoint.prototype.idCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the id field. + */ +sketchology.proto.SequencePoint.prototype.clearId = function() { + this.clear$Field(1); +}; + + + +/** + * Message SetCallbackFlags. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SetCallbackFlags = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SetCallbackFlags, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SetCallbackFlags.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SetCallbackFlags} The cloned message. + * @override + */ +sketchology.proto.SetCallbackFlags.prototype.clone; + + +/** + * Gets the value of the source_details field. + * @return {?sketchology.proto.SourceDetails} The value. + */ +sketchology.proto.SetCallbackFlags.prototype.getSourceDetails = function() { + return /** @type {?sketchology.proto.SourceDetails} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the source_details field or the default value if not set. + * @return {!sketchology.proto.SourceDetails} The value. + */ +sketchology.proto.SetCallbackFlags.prototype.getSourceDetailsOrDefault = function() { + return /** @type {!sketchology.proto.SourceDetails} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the source_details field. + * @param {!sketchology.proto.SourceDetails} value The value. + */ +sketchology.proto.SetCallbackFlags.prototype.setSourceDetails = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the source_details field has a value. + */ +sketchology.proto.SetCallbackFlags.prototype.hasSourceDetails = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the source_details field. + */ +sketchology.proto.SetCallbackFlags.prototype.sourceDetailsCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the source_details field. + */ +sketchology.proto.SetCallbackFlags.prototype.clearSourceDetails = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the callback_flags field. + * @return {?sketchology.proto.CallbackFlags} The value. + */ +sketchology.proto.SetCallbackFlags.prototype.getCallbackFlags = function() { + return /** @type {?sketchology.proto.CallbackFlags} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the callback_flags field or the default value if not set. + * @return {!sketchology.proto.CallbackFlags} The value. + */ +sketchology.proto.SetCallbackFlags.prototype.getCallbackFlagsOrDefault = function() { + return /** @type {!sketchology.proto.CallbackFlags} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the callback_flags field. + * @param {!sketchology.proto.CallbackFlags} value The value. + */ +sketchology.proto.SetCallbackFlags.prototype.setCallbackFlags = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the callback_flags field has a value. + */ +sketchology.proto.SetCallbackFlags.prototype.hasCallbackFlags = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the callback_flags field. + */ +sketchology.proto.SetCallbackFlags.prototype.callbackFlagsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the callback_flags field. + */ +sketchology.proto.SetCallbackFlags.prototype.clearCallbackFlags = function() { + this.clear$Field(2); +}; + + + +/** + * Message EngineState. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.EngineState = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.EngineState, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.EngineState.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.EngineState} The cloned message. + * @override + */ +sketchology.proto.EngineState.prototype.clone; + + +/** + * Gets the value of the camera_position field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.EngineState.prototype.getCameraPosition = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the camera_position field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.EngineState.prototype.getCameraPositionOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the camera_position field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.EngineState.prototype.setCameraPosition = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the camera_position field has a value. + */ +sketchology.proto.EngineState.prototype.hasCameraPosition = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the camera_position field. + */ +sketchology.proto.EngineState.prototype.cameraPositionCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the camera_position field. + */ +sketchology.proto.EngineState.prototype.clearCameraPosition = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the page_bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.EngineState.prototype.getPageBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the page_bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.EngineState.prototype.getPageBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the page_bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.EngineState.prototype.setPageBounds = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the page_bounds field has a value. + */ +sketchology.proto.EngineState.prototype.hasPageBounds = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the page_bounds field. + */ +sketchology.proto.EngineState.prototype.pageBoundsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the page_bounds field. + */ +sketchology.proto.EngineState.prototype.clearPageBounds = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the selection_is_live field. + * @return {?boolean} The value. + */ +sketchology.proto.EngineState.prototype.getSelectionIsLive = function() { + return /** @type {?boolean} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the selection_is_live field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.EngineState.prototype.getSelectionIsLiveOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the selection_is_live field. + * @param {boolean} value The value. + */ +sketchology.proto.EngineState.prototype.setSelectionIsLive = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the selection_is_live field has a value. + */ +sketchology.proto.EngineState.prototype.hasSelectionIsLive = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the selection_is_live field. + */ +sketchology.proto.EngineState.prototype.selectionIsLiveCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the selection_is_live field. + */ +sketchology.proto.EngineState.prototype.clearSelectionIsLive = function() { + this.clear$Field(3); +}; + + + +/** + * Message CameraBoundsConfig. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.CameraBoundsConfig = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.CameraBoundsConfig, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.CameraBoundsConfig.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.CameraBoundsConfig} The cloned message. + * @override + */ +sketchology.proto.CameraBoundsConfig.prototype.clone; + + +/** + * Gets the value of the margin_left_px field. + * @return {?number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginLeftPx = function() { + return /** @type {?number} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the margin_left_px field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginLeftPxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the margin_left_px field. + * @param {number} value The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.setMarginLeftPx = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the margin_left_px field has a value. + */ +sketchology.proto.CameraBoundsConfig.prototype.hasMarginLeftPx = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the margin_left_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.marginLeftPxCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the margin_left_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.clearMarginLeftPx = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the margin_right_px field. + * @return {?number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginRightPx = function() { + return /** @type {?number} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the margin_right_px field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginRightPxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the margin_right_px field. + * @param {number} value The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.setMarginRightPx = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the margin_right_px field has a value. + */ +sketchology.proto.CameraBoundsConfig.prototype.hasMarginRightPx = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the margin_right_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.marginRightPxCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the margin_right_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.clearMarginRightPx = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the margin_bottom_px field. + * @return {?number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginBottomPx = function() { + return /** @type {?number} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the margin_bottom_px field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginBottomPxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the margin_bottom_px field. + * @param {number} value The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.setMarginBottomPx = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the margin_bottom_px field has a value. + */ +sketchology.proto.CameraBoundsConfig.prototype.hasMarginBottomPx = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the margin_bottom_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.marginBottomPxCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the margin_bottom_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.clearMarginBottomPx = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the margin_top_px field. + * @return {?number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginTopPx = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the margin_top_px field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getMarginTopPxOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the margin_top_px field. + * @param {number} value The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.setMarginTopPx = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the margin_top_px field has a value. + */ +sketchology.proto.CameraBoundsConfig.prototype.hasMarginTopPx = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the margin_top_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.marginTopPxCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the margin_top_px field. + */ +sketchology.proto.CameraBoundsConfig.prototype.clearMarginTopPx = function() { + this.clear$Field(4); +}; + + +/** + * Gets the value of the fraction_padding field. + * @return {?number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getFractionPadding = function() { + return /** @type {?number} */ (this.get$Value(5)); +}; + + +/** + * Gets the value of the fraction_padding field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.getFractionPaddingOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(5)); +}; + + +/** + * Sets the value of the fraction_padding field. + * @param {number} value The value. + */ +sketchology.proto.CameraBoundsConfig.prototype.setFractionPadding = function(value) { + this.set$Value(5, value); +}; + + +/** + * @return {boolean} Whether the fraction_padding field has a value. + */ +sketchology.proto.CameraBoundsConfig.prototype.hasFractionPadding = function() { + return this.has$Value(5); +}; + + +/** + * @return {number} The number of values in the fraction_padding field. + */ +sketchology.proto.CameraBoundsConfig.prototype.fractionPaddingCount = function() { + return this.count$Values(5); +}; + + +/** + * Clears the values in the fraction_padding field. + */ +sketchology.proto.CameraBoundsConfig.prototype.clearFractionPadding = function() { + this.clear$Field(5); +}; + + + +/** + * Message ImageInfo. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ImageInfo = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ImageInfo, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ImageInfo.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ImageInfo} The cloned message. + * @override + */ +sketchology.proto.ImageInfo.prototype.clone; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.ImageInfo.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ImageInfo.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.ImageInfo.prototype.setUri = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.ImageInfo.prototype.hasUri = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.ImageInfo.prototype.uriCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.ImageInfo.prototype.clearUri = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the asset_type field. + * @return {?sketchology.proto.ImageInfo.AssetType} The value. + */ +sketchology.proto.ImageInfo.prototype.getAssetType = function() { + return /** @type {?sketchology.proto.ImageInfo.AssetType} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the asset_type field or the default value if not set. + * @return {!sketchology.proto.ImageInfo.AssetType} The value. + */ +sketchology.proto.ImageInfo.prototype.getAssetTypeOrDefault = function() { + return /** @type {!sketchology.proto.ImageInfo.AssetType} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the asset_type field. + * @param {!sketchology.proto.ImageInfo.AssetType} value The value. + */ +sketchology.proto.ImageInfo.prototype.setAssetType = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the asset_type field has a value. + */ +sketchology.proto.ImageInfo.prototype.hasAssetType = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the asset_type field. + */ +sketchology.proto.ImageInfo.prototype.assetTypeCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the asset_type field. + */ +sketchology.proto.ImageInfo.prototype.clearAssetType = function() { + this.clear$Field(2); +}; + + +/** + * Enumeration AssetType. + * @enum {number} + */ +sketchology.proto.ImageInfo.AssetType = { + DEFAULT: 0, + BORDER: 1, + STICKER: 2, + GRID: 3 +}; + + + +/** + * Message ImageRect. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ImageRect = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ImageRect, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ImageRect.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ImageRect} The cloned message. + * @override + */ +sketchology.proto.ImageRect.prototype.clone; + + +/** + * Gets the value of the rect field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.ImageRect.prototype.getRect = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the rect field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.ImageRect.prototype.getRectOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the rect field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.ImageRect.prototype.setRect = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the rect field has a value. + */ +sketchology.proto.ImageRect.prototype.hasRect = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the rect field. + */ +sketchology.proto.ImageRect.prototype.rectCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the rect field. + */ +sketchology.proto.ImageRect.prototype.clearRect = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the bitmap_uri field. + * @return {?string} The value. + */ +sketchology.proto.ImageRect.prototype.getBitmapUri = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the bitmap_uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ImageRect.prototype.getBitmapUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the bitmap_uri field. + * @param {string} value The value. + */ +sketchology.proto.ImageRect.prototype.setBitmapUri = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the bitmap_uri field has a value. + */ +sketchology.proto.ImageRect.prototype.hasBitmapUri = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the bitmap_uri field. + */ +sketchology.proto.ImageRect.prototype.bitmapUriCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the bitmap_uri field. + */ +sketchology.proto.ImageRect.prototype.clearBitmapUri = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the attributes field. + * @return {?sketchology.proto.ElementAttributes} The value. + */ +sketchology.proto.ImageRect.prototype.getAttributes = function() { + return /** @type {?sketchology.proto.ElementAttributes} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the attributes field or the default value if not set. + * @return {!sketchology.proto.ElementAttributes} The value. + */ +sketchology.proto.ImageRect.prototype.getAttributesOrDefault = function() { + return /** @type {!sketchology.proto.ElementAttributes} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the attributes field. + * @param {!sketchology.proto.ElementAttributes} value The value. + */ +sketchology.proto.ImageRect.prototype.setAttributes = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the attributes field has a value. + */ +sketchology.proto.ImageRect.prototype.hasAttributes = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the attributes field. + */ +sketchology.proto.ImageRect.prototype.attributesCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the attributes field. + */ +sketchology.proto.ImageRect.prototype.clearAttributes = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the rotation_radians field. + * @return {?number} The value. + */ +sketchology.proto.ImageRect.prototype.getRotationRadians = function() { + return /** @type {?number} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the rotation_radians field or the default value if not set. + * @return {number} The value. + */ +sketchology.proto.ImageRect.prototype.getRotationRadiansOrDefault = function() { + return /** @type {number} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the rotation_radians field. + * @param {number} value The value. + */ +sketchology.proto.ImageRect.prototype.setRotationRadians = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the rotation_radians field has a value. + */ +sketchology.proto.ImageRect.prototype.hasRotationRadians = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the rotation_radians field. + */ +sketchology.proto.ImageRect.prototype.rotationRadiansCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the rotation_radians field. + */ +sketchology.proto.ImageRect.prototype.clearRotationRadians = function() { + this.clear$Field(4); +}; + + + +/** + * Message GridInfo. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.GridInfo = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.GridInfo, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.GridInfo.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.GridInfo} The cloned message. + * @override + */ +sketchology.proto.GridInfo.prototype.clone; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.GridInfo.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.GridInfo.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.GridInfo.prototype.setUri = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.GridInfo.prototype.hasUri = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.GridInfo.prototype.uriCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.GridInfo.prototype.clearUri = function() { + this.clear$Field(1); +}; + + + +/** + * Message CreateDocument. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.CreateDocument = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.CreateDocument, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.CreateDocument.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.CreateDocument} The cloned message. + * @override + */ +sketchology.proto.CreateDocument.prototype.clone; + + +/** + * Gets the value of the document_type field. + * @return {?sketchology.proto.DocumentType} The value. + */ +sketchology.proto.CreateDocument.prototype.getDocumentType = function() { + return /** @type {?sketchology.proto.DocumentType} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the document_type field or the default value if not set. + * @return {!sketchology.proto.DocumentType} The value. + */ +sketchology.proto.CreateDocument.prototype.getDocumentTypeOrDefault = function() { + return /** @type {!sketchology.proto.DocumentType} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the document_type field. + * @param {!sketchology.proto.DocumentType} value The value. + */ +sketchology.proto.CreateDocument.prototype.setDocumentType = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the document_type field has a value. + */ +sketchology.proto.CreateDocument.prototype.hasDocumentType = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the document_type field. + */ +sketchology.proto.CreateDocument.prototype.documentTypeCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the document_type field. + */ +sketchology.proto.CreateDocument.prototype.clearDocumentType = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the storage_type field. + * @return {?sketchology.proto.StorageType} The value. + */ +sketchology.proto.CreateDocument.prototype.getStorageType = function() { + return /** @type {?sketchology.proto.StorageType} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the storage_type field or the default value if not set. + * @return {!sketchology.proto.StorageType} The value. + */ +sketchology.proto.CreateDocument.prototype.getStorageTypeOrDefault = function() { + return /** @type {!sketchology.proto.StorageType} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the storage_type field. + * @param {!sketchology.proto.StorageType} value The value. + */ +sketchology.proto.CreateDocument.prototype.setStorageType = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the storage_type field has a value. + */ +sketchology.proto.CreateDocument.prototype.hasStorageType = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the storage_type field. + */ +sketchology.proto.CreateDocument.prototype.storageTypeCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the storage_type field. + */ +sketchology.proto.CreateDocument.prototype.clearStorageType = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the storage_path field. + * @return {?string} The value. + */ +sketchology.proto.CreateDocument.prototype.getStoragePath = function() { + return /** @type {?string} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the storage_path field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.CreateDocument.prototype.getStoragePathOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the storage_path field. + * @param {string} value The value. + */ +sketchology.proto.CreateDocument.prototype.setStoragePath = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the storage_path field has a value. + */ +sketchology.proto.CreateDocument.prototype.hasStoragePath = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the storage_path field. + */ +sketchology.proto.CreateDocument.prototype.storagePathCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the storage_path field. + */ +sketchology.proto.CreateDocument.prototype.clearStoragePath = function() { + this.clear$Field(3); +}; + + +/** + * Gets the value of the snapshot field. + * @return {?sketchology.proto.Snapshot} The value. + */ +sketchology.proto.CreateDocument.prototype.getSnapshot = function() { + return /** @type {?sketchology.proto.Snapshot} */ (this.get$Value(4)); +}; + + +/** + * Gets the value of the snapshot field or the default value if not set. + * @return {!sketchology.proto.Snapshot} The value. + */ +sketchology.proto.CreateDocument.prototype.getSnapshotOrDefault = function() { + return /** @type {!sketchology.proto.Snapshot} */ (this.get$ValueOrDefault(4)); +}; + + +/** + * Sets the value of the snapshot field. + * @param {!sketchology.proto.Snapshot} value The value. + */ +sketchology.proto.CreateDocument.prototype.setSnapshot = function(value) { + this.set$Value(4, value); +}; + + +/** + * @return {boolean} Whether the snapshot field has a value. + */ +sketchology.proto.CreateDocument.prototype.hasSnapshot = function() { + return this.has$Value(4); +}; + + +/** + * @return {number} The number of values in the snapshot field. + */ +sketchology.proto.CreateDocument.prototype.snapshotCount = function() { + return this.count$Values(4); +}; + + +/** + * Clears the values in the snapshot field. + */ +sketchology.proto.CreateDocument.prototype.clearSnapshot = function() { + this.clear$Field(4); +}; + + + +/** + * Message AddPath. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.AddPath = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.AddPath, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.AddPath.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.AddPath} The cloned message. + * @override + */ +sketchology.proto.AddPath.prototype.clone; + + +/** + * Gets the value of the path field. + * @return {?sketchology.proto.Path} The value. + */ +sketchology.proto.AddPath.prototype.getPath = function() { + return /** @type {?sketchology.proto.Path} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the path field or the default value if not set. + * @return {!sketchology.proto.Path} The value. + */ +sketchology.proto.AddPath.prototype.getPathOrDefault = function() { + return /** @type {!sketchology.proto.Path} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the path field. + * @param {!sketchology.proto.Path} value The value. + */ +sketchology.proto.AddPath.prototype.setPath = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the path field has a value. + */ +sketchology.proto.AddPath.prototype.hasPath = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the path field. + */ +sketchology.proto.AddPath.prototype.pathCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the path field. + */ +sketchology.proto.AddPath.prototype.clearPath = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.AddPath.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.AddPath.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.AddPath.prototype.setUuid = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.AddPath.prototype.hasUuid = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.AddPath.prototype.uuidCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.AddPath.prototype.clearUuid = function() { + this.clear$Field(2); +}; + + + +/** + * Message PusherPositionUpdate. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.PusherPositionUpdate = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.PusherPositionUpdate, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.PusherPositionUpdate.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.PusherPositionUpdate} The cloned message. + * @override + */ +sketchology.proto.PusherPositionUpdate.prototype.clone; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.PusherPositionUpdate.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.PusherPositionUpdate.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.PusherPositionUpdate.prototype.setUuid = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.PusherPositionUpdate.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.PusherPositionUpdate.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.PusherPositionUpdate.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the pointer_location field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.Point} The value. + */ +sketchology.proto.PusherPositionUpdate.prototype.getPointerLocation = function(index) { + return /** @type {?sketchology.proto.Point} */ (this.get$Value(2, index)); +}; + + +/** + * Gets the value of the pointer_location field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.Point} The value. + */ +sketchology.proto.PusherPositionUpdate.prototype.getPointerLocationOrDefault = function(index) { + return /** @type {!sketchology.proto.Point} */ (this.get$ValueOrDefault(2, index)); +}; + + +/** + * Adds a value to the pointer_location field. + * @param {!sketchology.proto.Point} value The value to add. + */ +sketchology.proto.PusherPositionUpdate.prototype.addPointerLocation = function(value) { + this.add$Value(2, value); +}; + + +/** + * Returns the array of values in the pointer_location field. + * @return {!Array<!sketchology.proto.Point>} The values in the field. + */ +sketchology.proto.PusherPositionUpdate.prototype.pointerLocationArray = function() { + return /** @type {!Array<!sketchology.proto.Point>} */ (this.array$Values(2)); +}; + + +/** + * @return {boolean} Whether the pointer_location field has a value. + */ +sketchology.proto.PusherPositionUpdate.prototype.hasPointerLocation = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the pointer_location field. + */ +sketchology.proto.PusherPositionUpdate.prototype.pointerLocationCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the pointer_location field. + */ +sketchology.proto.PusherPositionUpdate.prototype.clearPointerLocation = function() { + this.clear$Field(2); +}; + + + +/** + * Message ElementQueryData. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementQueryData = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementQueryData, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementQueryData.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementQueryData} The cloned message. + * @override + */ +sketchology.proto.ElementQueryData.prototype.clone; + + +/** + * Gets the value of the item field at the index given. + * @param {number} index The index to lookup. + * @return {?sketchology.proto.ElementQueryItem} The value. + */ +sketchology.proto.ElementQueryData.prototype.getItem = function(index) { + return /** @type {?sketchology.proto.ElementQueryItem} */ (this.get$Value(1, index)); +}; + + +/** + * Gets the value of the item field at the index given or the default value if not set. + * @param {number} index The index to lookup. + * @return {!sketchology.proto.ElementQueryItem} The value. + */ +sketchology.proto.ElementQueryData.prototype.getItemOrDefault = function(index) { + return /** @type {!sketchology.proto.ElementQueryItem} */ (this.get$ValueOrDefault(1, index)); +}; + + +/** + * Adds a value to the item field. + * @param {!sketchology.proto.ElementQueryItem} value The value to add. + */ +sketchology.proto.ElementQueryData.prototype.addItem = function(value) { + this.add$Value(1, value); +}; + + +/** + * Returns the array of values in the item field. + * @return {!Array<!sketchology.proto.ElementQueryItem>} The values in the field. + */ +sketchology.proto.ElementQueryData.prototype.itemArray = function() { + return /** @type {!Array<!sketchology.proto.ElementQueryItem>} */ (this.array$Values(1)); +}; + + +/** + * @return {boolean} Whether the item field has a value. + */ +sketchology.proto.ElementQueryData.prototype.hasItem = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the item field. + */ +sketchology.proto.ElementQueryData.prototype.itemCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the item field. + */ +sketchology.proto.ElementQueryData.prototype.clearItem = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the up_world_location field. + * @return {?sketchology.proto.Point} The value. + */ +sketchology.proto.ElementQueryData.prototype.getUpWorldLocation = function() { + return /** @type {?sketchology.proto.Point} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the up_world_location field or the default value if not set. + * @return {!sketchology.proto.Point} The value. + */ +sketchology.proto.ElementQueryData.prototype.getUpWorldLocationOrDefault = function() { + return /** @type {!sketchology.proto.Point} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the up_world_location field. + * @param {!sketchology.proto.Point} value The value. + */ +sketchology.proto.ElementQueryData.prototype.setUpWorldLocation = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the up_world_location field has a value. + */ +sketchology.proto.ElementQueryData.prototype.hasUpWorldLocation = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the up_world_location field. + */ +sketchology.proto.ElementQueryData.prototype.upWorldLocationCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the up_world_location field. + */ +sketchology.proto.ElementQueryData.prototype.clearUpWorldLocation = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the down_world_location field. + * @return {?sketchology.proto.Point} The value. + */ +sketchology.proto.ElementQueryData.prototype.getDownWorldLocation = function() { + return /** @type {?sketchology.proto.Point} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the down_world_location field or the default value if not set. + * @return {!sketchology.proto.Point} The value. + */ +sketchology.proto.ElementQueryData.prototype.getDownWorldLocationOrDefault = function() { + return /** @type {!sketchology.proto.Point} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the down_world_location field. + * @param {!sketchology.proto.Point} value The value. + */ +sketchology.proto.ElementQueryData.prototype.setDownWorldLocation = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the down_world_location field has a value. + */ +sketchology.proto.ElementQueryData.prototype.hasDownWorldLocation = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the down_world_location field. + */ +sketchology.proto.ElementQueryData.prototype.downWorldLocationCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the down_world_location field. + */ +sketchology.proto.ElementQueryData.prototype.clearDownWorldLocation = function() { + this.clear$Field(3); +}; + + + +/** + * Message ElementQueryItem. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ElementQueryItem = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ElementQueryItem, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ElementQueryItem.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ElementQueryItem} The cloned message. + * @override + */ +sketchology.proto.ElementQueryItem.prototype.clone; + + +/** + * Gets the value of the uuid field. + * @return {?string} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getUuid = function() { + return /** @type {?string} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the uuid field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getUuidOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the uuid field. + * @param {string} value The value. + */ +sketchology.proto.ElementQueryItem.prototype.setUuid = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the uuid field has a value. + */ +sketchology.proto.ElementQueryItem.prototype.hasUuid = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the uuid field. + */ +sketchology.proto.ElementQueryItem.prototype.uuidCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the uuid field. + */ +sketchology.proto.ElementQueryItem.prototype.clearUuid = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the world_bounds field. + * @return {?sketchology.proto.Rect} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getWorldBounds = function() { + return /** @type {?sketchology.proto.Rect} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the world_bounds field or the default value if not set. + * @return {!sketchology.proto.Rect} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getWorldBoundsOrDefault = function() { + return /** @type {!sketchology.proto.Rect} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the world_bounds field. + * @param {!sketchology.proto.Rect} value The value. + */ +sketchology.proto.ElementQueryItem.prototype.setWorldBounds = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the world_bounds field has a value. + */ +sketchology.proto.ElementQueryItem.prototype.hasWorldBounds = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the world_bounds field. + */ +sketchology.proto.ElementQueryItem.prototype.worldBoundsCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the world_bounds field. + */ +sketchology.proto.ElementQueryItem.prototype.clearWorldBounds = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the uri field. + * @return {?string} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getUri = function() { + return /** @type {?string} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the uri field or the default value if not set. + * @return {string} The value. + */ +sketchology.proto.ElementQueryItem.prototype.getUriOrDefault = function() { + return /** @type {string} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the uri field. + * @param {string} value The value. + */ +sketchology.proto.ElementQueryItem.prototype.setUri = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the uri field has a value. + */ +sketchology.proto.ElementQueryItem.prototype.hasUri = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the uri field. + */ +sketchology.proto.ElementQueryItem.prototype.uriCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the uri field. + */ +sketchology.proto.ElementQueryItem.prototype.clearUri = function() { + this.clear$Field(3); +}; + + + +/** + * Message SelectionState. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.SelectionState = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.SelectionState, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.SelectionState.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.SelectionState} The cloned message. + * @override + */ +sketchology.proto.SelectionState.prototype.clone; + + +/** + * Gets the value of the anything_selected field. + * @return {?boolean} The value. + */ +sketchology.proto.SelectionState.prototype.getAnythingSelected = function() { + return /** @type {?boolean} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the anything_selected field or the default value if not set. + * @return {boolean} The value. + */ +sketchology.proto.SelectionState.prototype.getAnythingSelectedOrDefault = function() { + return /** @type {boolean} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the anything_selected field. + * @param {boolean} value The value. + */ +sketchology.proto.SelectionState.prototype.setAnythingSelected = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the anything_selected field has a value. + */ +sketchology.proto.SelectionState.prototype.hasAnythingSelected = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the anything_selected field. + */ +sketchology.proto.SelectionState.prototype.anythingSelectedCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the anything_selected field. + */ +sketchology.proto.SelectionState.prototype.clearAnythingSelected = function() { + this.clear$Field(1); +}; + + + +/** + * Message ToolEvent. + * @constructor + * @extends {goog.proto2.Message} + * @final + */ +sketchology.proto.ToolEvent = function() { + goog.proto2.Message.call(this); +}; +goog.inherits(sketchology.proto.ToolEvent, goog.proto2.Message); + + +/** + * Descriptor for this message, deserialized lazily in getDescriptor(). + * @private {?goog.proto2.Descriptor} + */ +sketchology.proto.ToolEvent.descriptor_ = null; + + +/** + * Overrides {@link goog.proto2.Message#clone} to specify its exact return type. + * @return {!sketchology.proto.ToolEvent} The cloned message. + * @override + */ +sketchology.proto.ToolEvent.prototype.clone; + + +/** + * Gets the value of the pusher_position_update field. + * @return {?sketchology.proto.PusherPositionUpdate} The value. + */ +sketchology.proto.ToolEvent.prototype.getPusherPositionUpdate = function() { + return /** @type {?sketchology.proto.PusherPositionUpdate} */ (this.get$Value(1)); +}; + + +/** + * Gets the value of the pusher_position_update field or the default value if not set. + * @return {!sketchology.proto.PusherPositionUpdate} The value. + */ +sketchology.proto.ToolEvent.prototype.getPusherPositionUpdateOrDefault = function() { + return /** @type {!sketchology.proto.PusherPositionUpdate} */ (this.get$ValueOrDefault(1)); +}; + + +/** + * Sets the value of the pusher_position_update field. + * @param {!sketchology.proto.PusherPositionUpdate} value The value. + */ +sketchology.proto.ToolEvent.prototype.setPusherPositionUpdate = function(value) { + this.set$Value(1, value); +}; + + +/** + * @return {boolean} Whether the pusher_position_update field has a value. + */ +sketchology.proto.ToolEvent.prototype.hasPusherPositionUpdate = function() { + return this.has$Value(1); +}; + + +/** + * @return {number} The number of values in the pusher_position_update field. + */ +sketchology.proto.ToolEvent.prototype.pusherPositionUpdateCount = function() { + return this.count$Values(1); +}; + + +/** + * Clears the values in the pusher_position_update field. + */ +sketchology.proto.ToolEvent.prototype.clearPusherPositionUpdate = function() { + this.clear$Field(1); +}; + + +/** + * Gets the value of the element_query_data field. + * @return {?sketchology.proto.ElementQueryData} The value. + */ +sketchology.proto.ToolEvent.prototype.getElementQueryData = function() { + return /** @type {?sketchology.proto.ElementQueryData} */ (this.get$Value(2)); +}; + + +/** + * Gets the value of the element_query_data field or the default value if not set. + * @return {!sketchology.proto.ElementQueryData} The value. + */ +sketchology.proto.ToolEvent.prototype.getElementQueryDataOrDefault = function() { + return /** @type {!sketchology.proto.ElementQueryData} */ (this.get$ValueOrDefault(2)); +}; + + +/** + * Sets the value of the element_query_data field. + * @param {!sketchology.proto.ElementQueryData} value The value. + */ +sketchology.proto.ToolEvent.prototype.setElementQueryData = function(value) { + this.set$Value(2, value); +}; + + +/** + * @return {boolean} Whether the element_query_data field has a value. + */ +sketchology.proto.ToolEvent.prototype.hasElementQueryData = function() { + return this.has$Value(2); +}; + + +/** + * @return {number} The number of values in the element_query_data field. + */ +sketchology.proto.ToolEvent.prototype.elementQueryDataCount = function() { + return this.count$Values(2); +}; + + +/** + * Clears the values in the element_query_data field. + */ +sketchology.proto.ToolEvent.prototype.clearElementQueryData = function() { + this.clear$Field(2); +}; + + +/** + * Gets the value of the selection_state field. + * @return {?sketchology.proto.SelectionState} The value. + */ +sketchology.proto.ToolEvent.prototype.getSelectionState = function() { + return /** @type {?sketchology.proto.SelectionState} */ (this.get$Value(3)); +}; + + +/** + * Gets the value of the selection_state field or the default value if not set. + * @return {!sketchology.proto.SelectionState} The value. + */ +sketchology.proto.ToolEvent.prototype.getSelectionStateOrDefault = function() { + return /** @type {!sketchology.proto.SelectionState} */ (this.get$ValueOrDefault(3)); +}; + + +/** + * Sets the value of the selection_state field. + * @param {!sketchology.proto.SelectionState} value The value. + */ +sketchology.proto.ToolEvent.prototype.setSelectionState = function(value) { + this.set$Value(3, value); +}; + + +/** + * @return {boolean} Whether the selection_state field has a value. + */ +sketchology.proto.ToolEvent.prototype.hasSelectionState = function() { + return this.has$Value(3); +}; + + +/** + * @return {number} The number of values in the selection_state field. + */ +sketchology.proto.ToolEvent.prototype.selectionStateCount = function() { + return this.count$Values(3); +}; + + +/** + * Clears the values in the selection_state field. + */ +sketchology.proto.ToolEvent.prototype.clearSelectionState = function() { + this.clear$Field(3); +}; + + +/** @override */ +sketchology.proto.Command.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Command.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Command', + fullName: 'sketchology.proto.Command' + }, + 1: { + name: 'set_viewport', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Viewport + }, + 2: { + name: 'tool_params', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ToolParams + }, + 3: { + name: 'add_path', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AddPath + }, + 4: { + name: 'camera_position', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 5: { + name: 'page_bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 6: { + name: 'image_export', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ImageExport + }, + 7: { + name: 'flag_assignment', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.FlagAssignment + }, + 8: { + name: 'set_element_transforms', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementMutation + }, + 9: { + name: 'add_element', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.AddElement + }, + 10: { + name: 'background_image', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.BackgroundImageInfo + }, + 11: { + name: 'background_color', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.BackgroundColor + }, + 12: { + name: 'set_out_of_bounds_color', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.OutOfBoundsColor + }, + 13: { + name: 'set_page_border', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Border + }, + 14: { + name: 'send_input_stream', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SInputStream + }, + 15: { + name: 'sequence_point', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SequencePoint + }, + 16: { + name: 'set_callback_flags', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SetCallbackFlags + }, + 17: { + name: 'set_camera_bounds_config', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.CameraBoundsConfig + }, + 18: { + name: 'deselect_all', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 19: { + name: 'add_image_rect', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ImageRect + }, + 21: { + name: 'clear', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 22: { + name: 'remove_all_elements', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 23: { + name: 'undo', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 24: { + name: 'redo', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 25: { + name: 'evict_image_data', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.EvictImageData + }, + 26: { + name: 'replace_elements', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ReplaceElementsCommand + }, + 27: { + name: 'commit_crop', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + }, + 28: { + name: 'element_animation', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementAnimation + }, + 29: { + name: 'set_grid', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.GridInfo + }, + 30: { + name: 'clear_grid', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.NoArgCommand + } + }; + sketchology.proto.Command.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Command, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Command.getDescriptor = + sketchology.proto.Command.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.CommandList.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.CommandList.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'CommandList', + fullName: 'sketchology.proto.CommandList' + }, + 1: { + name: 'commands', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Command + } + }; + sketchology.proto.CommandList.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.CommandList, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.CommandList.getDescriptor = + sketchology.proto.CommandList.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.NoArgCommand.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.NoArgCommand.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'NoArgCommand', + fullName: 'sketchology.proto.NoArgCommand' + } + }; + sketchology.proto.NoArgCommand.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.NoArgCommand, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.NoArgCommand.getDescriptor = + sketchology.proto.NoArgCommand.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ReplaceElementsCommand.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ReplaceElementsCommand.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ReplaceElementsCommand', + fullName: 'sketchology.proto.ReplaceElementsCommand' + }, + 1: { + name: 'uuids_to_remove', + repeated: true, + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'paths_to_add', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Path + } + }; + sketchology.proto.ReplaceElementsCommand.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ReplaceElementsCommand, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ReplaceElementsCommand.getDescriptor = + sketchology.proto.ReplaceElementsCommand.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.EvictImageData.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.EvictImageData.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'EvictImageData', + fullName: 'sketchology.proto.EvictImageData' + }, + 1: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.EvictImageData.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.EvictImageData, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.EvictImageData.getDescriptor = + sketchology.proto.EvictImageData.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.Viewport.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.Viewport.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'Viewport', + fullName: 'sketchology.proto.Viewport' + }, + 1: { + name: 'fbo_handle', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 2: { + name: 'width', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 3: { + name: 'height', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 4: { + name: 'ppi', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.Viewport.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.Viewport, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.Viewport.getDescriptor = + sketchology.proto.Viewport.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ImageExport.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ImageExport.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ImageExport', + fullName: 'sketchology.proto.ImageExport' + }, + 1: { + name: 'max_dimension_px', + fieldType: goog.proto2.Message.FieldType.UINT32, + defaultValue: 1024, + type: Number + }, + 2: { + name: 'should_draw_background', + fieldType: goog.proto2.Message.FieldType.BOOL, + defaultValue: true, + type: Boolean + } + }; + sketchology.proto.ImageExport.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ImageExport, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ImageExport.getDescriptor = + sketchology.proto.ImageExport.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.LinearPathAnimation.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.LinearPathAnimation.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'LinearPathAnimation', + fullName: 'sketchology.proto.LinearPathAnimation' + }, + 1: { + name: 'rgba_from', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 2: { + name: 'rgba_seconds', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + type: Number + }, + 3: { + name: 'dilation_from', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 4: { + name: 'dilation_seconds', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + type: Number + } + }; + sketchology.proto.LinearPathAnimation.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.LinearPathAnimation, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.LinearPathAnimation.getDescriptor = + sketchology.proto.LinearPathAnimation.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.LineSize.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.LineSize.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'LineSize', + fullName: 'sketchology.proto.LineSize' + }, + 7: { + name: 'stroke_width', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 8: { + name: 'units', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.LineSize.SizeType.WORLD_UNITS, + type: sketchology.proto.LineSize.SizeType + } + }; + sketchology.proto.LineSize.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.LineSize, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.LineSize.getDescriptor = + sketchology.proto.LineSize.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.PusherToolParams.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.PusherToolParams.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'PusherToolParams', + fullName: 'sketchology.proto.PusherToolParams' + }, + 1: { + name: 'manipulate_stickers', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + }, + 2: { + name: 'manipulate_text', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + sketchology.proto.PusherToolParams.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.PusherToolParams, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.PusherToolParams.getDescriptor = + sketchology.proto.PusherToolParams.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ToolParams.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ToolParams.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ToolParams', + fullName: 'sketchology.proto.ToolParams' + }, + 1: { + name: 'tool', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.ToolParams.ToolType.UNKNOWN, + type: sketchology.proto.ToolParams.ToolType + }, + 2: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 3: { + name: 'line_size', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.LineSize + }, + 4: { + name: 'pusher_tool_params', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.PusherToolParams + }, + 5: { + name: 'brush_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.BrushType.UNKNOWN_BRUSH, + type: sketchology.proto.BrushType + }, + 6: { + name: 'linear_path_animation', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.LinearPathAnimation + } + }; + sketchology.proto.ToolParams.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ToolParams, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ToolParams.getDescriptor = + sketchology.proto.ToolParams.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.FlagAssignment.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.FlagAssignment.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'FlagAssignment', + fullName: 'sketchology.proto.FlagAssignment' + }, + 1: { + name: 'flag', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.Flag.UNKNOWN, + type: sketchology.proto.Flag + }, + 2: { + name: 'bool_value', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + sketchology.proto.FlagAssignment.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.FlagAssignment, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.FlagAssignment.getDescriptor = + sketchology.proto.FlagAssignment.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.AddElement.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.AddElement.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'AddElement', + fullName: 'sketchology.proto.AddElement' + }, + 1: { + name: 'bundle', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementBundle + }, + 2: { + name: 'below_element_with_uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.AddElement.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.AddElement, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.AddElement.getDescriptor = + sketchology.proto.AddElement.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.OutOfBoundsColor.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.OutOfBoundsColor.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'OutOfBoundsColor', + fullName: 'sketchology.proto.OutOfBoundsColor' + }, + 1: { + name: 'rgba', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + } + }; + sketchology.proto.OutOfBoundsColor.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.OutOfBoundsColor, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.OutOfBoundsColor.getDescriptor = + sketchology.proto.OutOfBoundsColor.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SInputStream.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SInputStream.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SInputStream', + fullName: 'sketchology.proto.SInputStream' + }, + 1: { + name: 'screen_width', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 2: { + name: 'screen_height', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 3: { + name: 'screen_ppi', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 4: { + name: 'input', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SInput + } + }; + sketchology.proto.SInputStream.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SInputStream, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SInputStream.getDescriptor = + sketchology.proto.SInputStream.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SInput.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SInput.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SInput', + fullName: 'sketchology.proto.SInput' + }, + 1: { + name: 'type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.SInput.InputType.UNKNOWN, + type: sketchology.proto.SInput.InputType + }, + 2: { + name: 'id', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 3: { + name: 'flags', + fieldType: goog.proto2.Message.FieldType.UINT32, + type: Number + }, + 4: { + name: 'time_s', + fieldType: goog.proto2.Message.FieldType.DOUBLE, + type: Number + }, + 5: { + name: 'screen_pos_x', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 6: { + name: 'screen_pos_y', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 7: { + name: 'pressure', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 8: { + name: 'wheel_delta', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 9: { + name: 'tilt', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 10: { + name: 'orientation', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.SInput.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SInput, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SInput.getDescriptor = + sketchology.proto.SInput.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SimulatedInput.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SimulatedInput.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SimulatedInput', + fullName: 'sketchology.proto.SimulatedInput' + }, + 1: { + name: 'xs', + repeated: true, + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'ys', + repeated: true, + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 3: { + name: 'ts_secs', + repeated: true, + fieldType: goog.proto2.Message.FieldType.DOUBLE, + type: Number + }, + 4: { + name: 'source_details', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SourceDetails + } + }; + sketchology.proto.SimulatedInput.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SimulatedInput, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SimulatedInput.getDescriptor = + sketchology.proto.SimulatedInput.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SequencePoint.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SequencePoint.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SequencePoint', + fullName: 'sketchology.proto.SequencePoint' + }, + 1: { + name: 'id', + fieldType: goog.proto2.Message.FieldType.INT32, + type: Number + } + }; + sketchology.proto.SequencePoint.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SequencePoint, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SequencePoint.getDescriptor = + sketchology.proto.SequencePoint.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SetCallbackFlags.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SetCallbackFlags.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SetCallbackFlags', + fullName: 'sketchology.proto.SetCallbackFlags' + }, + 1: { + name: 'source_details', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SourceDetails + }, + 2: { + name: 'callback_flags', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.CallbackFlags + } + }; + sketchology.proto.SetCallbackFlags.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SetCallbackFlags, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SetCallbackFlags.getDescriptor = + sketchology.proto.SetCallbackFlags.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.EngineState.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.EngineState.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'EngineState', + fullName: 'sketchology.proto.EngineState' + }, + 1: { + name: 'camera_position', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 2: { + name: 'page_bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 3: { + name: 'selection_is_live', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + sketchology.proto.EngineState.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.EngineState, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.EngineState.getDescriptor = + sketchology.proto.EngineState.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.CameraBoundsConfig.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.CameraBoundsConfig.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'CameraBoundsConfig', + fullName: 'sketchology.proto.CameraBoundsConfig' + }, + 1: { + name: 'margin_left_px', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 2: { + name: 'margin_right_px', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 3: { + name: 'margin_bottom_px', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 4: { + name: 'margin_top_px', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + }, + 5: { + name: 'fraction_padding', + fieldType: goog.proto2.Message.FieldType.FLOAT, + defaultValue: 0.1, + type: Number + } + }; + sketchology.proto.CameraBoundsConfig.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.CameraBoundsConfig, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.CameraBoundsConfig.getDescriptor = + sketchology.proto.CameraBoundsConfig.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ImageInfo.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ImageInfo.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ImageInfo', + fullName: 'sketchology.proto.ImageInfo' + }, + 1: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'asset_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.ImageInfo.AssetType.DEFAULT, + type: sketchology.proto.ImageInfo.AssetType + } + }; + sketchology.proto.ImageInfo.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ImageInfo, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ImageInfo.getDescriptor = + sketchology.proto.ImageInfo.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ImageRect.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ImageRect.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ImageRect', + fullName: 'sketchology.proto.ImageRect' + }, + 1: { + name: 'rect', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 2: { + name: 'bitmap_uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 3: { + name: 'attributes', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementAttributes + }, + 4: { + name: 'rotation_radians', + fieldType: goog.proto2.Message.FieldType.FLOAT, + type: Number + } + }; + sketchology.proto.ImageRect.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ImageRect, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ImageRect.getDescriptor = + sketchology.proto.ImageRect.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.GridInfo.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.GridInfo.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'GridInfo', + fullName: 'sketchology.proto.GridInfo' + }, + 1: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.GridInfo.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.GridInfo, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.GridInfo.getDescriptor = + sketchology.proto.GridInfo.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.CreateDocument.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.CreateDocument.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'CreateDocument', + fullName: 'sketchology.proto.CreateDocument' + }, + 1: { + name: 'document_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.DocumentType.SINGLE_USER_DOCUMENT, + type: sketchology.proto.DocumentType + }, + 2: { + name: 'storage_type', + fieldType: goog.proto2.Message.FieldType.ENUM, + defaultValue: sketchology.proto.StorageType.IN_MEMORY_STORAGE, + type: sketchology.proto.StorageType + }, + 3: { + name: 'storage_path', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 4: { + name: 'snapshot', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Snapshot + } + }; + sketchology.proto.CreateDocument.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.CreateDocument, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.CreateDocument.getDescriptor = + sketchology.proto.CreateDocument.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.AddPath.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.AddPath.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'AddPath', + fullName: 'sketchology.proto.AddPath' + }, + 1: { + name: 'path', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Path + }, + 2: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.AddPath.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.AddPath, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.AddPath.getDescriptor = + sketchology.proto.AddPath.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.PusherPositionUpdate.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.PusherPositionUpdate.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'PusherPositionUpdate', + fullName: 'sketchology.proto.PusherPositionUpdate' + }, + 1: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'pointer_location', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Point + } + }; + sketchology.proto.PusherPositionUpdate.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.PusherPositionUpdate, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.PusherPositionUpdate.getDescriptor = + sketchology.proto.PusherPositionUpdate.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementQueryData.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementQueryData.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementQueryData', + fullName: 'sketchology.proto.ElementQueryData' + }, + 1: { + name: 'item', + repeated: true, + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementQueryItem + }, + 2: { + name: 'up_world_location', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Point + }, + 3: { + name: 'down_world_location', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Point + } + }; + sketchology.proto.ElementQueryData.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementQueryData, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementQueryData.getDescriptor = + sketchology.proto.ElementQueryData.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ElementQueryItem.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ElementQueryItem.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ElementQueryItem', + fullName: 'sketchology.proto.ElementQueryItem' + }, + 1: { + name: 'uuid', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + }, + 2: { + name: 'world_bounds', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.Rect + }, + 3: { + name: 'uri', + fieldType: goog.proto2.Message.FieldType.STRING, + type: String + } + }; + sketchology.proto.ElementQueryItem.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ElementQueryItem, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ElementQueryItem.getDescriptor = + sketchology.proto.ElementQueryItem.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.SelectionState.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.SelectionState.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'SelectionState', + fullName: 'sketchology.proto.SelectionState' + }, + 1: { + name: 'anything_selected', + fieldType: goog.proto2.Message.FieldType.BOOL, + type: Boolean + } + }; + sketchology.proto.SelectionState.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.SelectionState, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.SelectionState.getDescriptor = + sketchology.proto.SelectionState.prototype.getDescriptor; + + +/** @override */ +sketchology.proto.ToolEvent.prototype.getDescriptor = function() { + var descriptor = sketchology.proto.ToolEvent.descriptor_; + if (!descriptor) { + // The descriptor is created lazily when we instantiate a new instance. + var descriptorObj = { + 0: { + name: 'ToolEvent', + fullName: 'sketchology.proto.ToolEvent' + }, + 1: { + name: 'pusher_position_update', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.PusherPositionUpdate + }, + 2: { + name: 'element_query_data', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.ElementQueryData + }, + 3: { + name: 'selection_state', + fieldType: goog.proto2.Message.FieldType.MESSAGE, + type: sketchology.proto.SelectionState + } + }; + sketchology.proto.ToolEvent.descriptor_ = descriptor = + goog.proto2.Message.createDescriptor( + sketchology.proto.ToolEvent, descriptorObj); + } + return descriptor; +}; + + +/** @nocollapse */ +sketchology.proto.ToolEvent.getDescriptor = + sketchology.proto.ToolEvent.prototype.getDescriptor;
diff --git a/third_party/ink/sketchology/public/js/common/brush_model.js b/third_party/ink/sketchology/public/js/common/brush_model.js new file mode 100644 index 0000000..8ffbe4c0 --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/brush_model.js
@@ -0,0 +1,243 @@ +// Copyright 2017 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. +goog.provide('ink.BrushModel'); + +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('ink.Model'); +goog.require('sketchology.proto.BrushType'); +goog.require('sketchology.proto.ToolParams.ToolType'); + + + +/** + * Holds the state of the ink brush. Toolbar widgets update BrushModel, which in + * turn dispatches CHANGE events to update the toolbar components' states. + * @constructor + * @extends {ink.Model} + * @param {!goog.events.EventTarget} root Unused + */ +ink.BrushModel = function(root) { + ink.BrushModel.base(this, 'constructor'); + /** + * Last brush color (not including erase color). + * @private {string} + */ + this.color_ = ink.BrushModel.DEFAULT_DRAW_COLOR; + + /** + * The active stroke width of the brush (includes erase size). + * @private {number} + */ + this.strokeWidth_ = ink.BrushModel.DEFAULT_DRAW_SIZE; + + /** + * @private {boolean} + */ + this.isErasing_ = ink.BrushModel.DEFAULT_ISERASING; + + /** + * @private {sketchology.proto.ToolParams.ToolType} + */ + this.toolType_ = sketchology.proto.ToolParams.ToolType.LINE; + + /** + * @private {sketchology.proto.BrushType} + */ + this.brushType_ = sketchology.proto.BrushType.CALLIGRAPHY; + + /** + * @private {string} + */ + this.shape_ = 'CALLIGRAPHY'; +}; +goog.inherits(ink.BrushModel, ink.Model); +ink.Model.addGetter(ink.BrushModel); + + +/** + * @const {string} + */ +ink.BrushModel.DEFAULT_DRAW_COLOR = '#000000'; + + +/** + * @const {number} + */ +ink.BrushModel.DEFAULT_DRAW_SIZE = 0.6; + + +/** + * @const {string} + */ +ink.BrushModel.DEFAULT_ERASE_COLOR = '#FFFFFF'; + +/** + * @const {boolean} + */ +ink.BrushModel.DEFAULT_ISERASING = false; + + +/** + * The events fired by the BrushModel. + * @enum {string} The event types for the BrushModel. + */ +ink.BrushModel.EventType = { + /** + * Fired when the BrushModel is changed. + */ + CHANGE: goog.events.getUniqueId('change') +}; + + +/** + * @const {Object} + */ +ink.BrushModel.SHAPE_TO_TOOLTYPE = { + 'AIRBRUSH': sketchology.proto.ToolParams.ToolType.LINE, + 'CALLIGRAPHY': sketchology.proto.ToolParams.ToolType.LINE, + 'EDIT': sketchology.proto.ToolParams.ToolType.EDIT, + 'ERASER': sketchology.proto.ToolParams.ToolType.LINE, + 'HIGHLIGHTER': sketchology.proto.ToolParams.ToolType.LINE, + 'INKPEN': sketchology.proto.ToolParams.ToolType.LINE, + 'MAGIC_ERASE': sketchology.proto.ToolParams.ToolType.MAGIC_ERASE, + 'MARKER': sketchology.proto.ToolParams.ToolType.LINE, + 'PENCIL': sketchology.proto.ToolParams.ToolType.LINE, + 'BALLPOINT': sketchology.proto.ToolParams.ToolType.LINE, + 'BALLPOINT_IN_PEN_MODE_ELSE_MARKER': + sketchology.proto.ToolParams.ToolType.LINE, + 'QUERY': sketchology.proto.ToolParams.ToolType.QUERY, +}; + + +/** + * @const {Object} + */ +ink.BrushModel.SHAPE_TO_BRUSHTYPE = { + 'AIRBRUSH': sketchology.proto.BrushType.AIRBRUSH, + 'CALLIGRAPHY': sketchology.proto.BrushType.CALLIGRAPHY, + 'ERASER': sketchology.proto.BrushType.ERASER, + 'HIGHLIGHTER': sketchology.proto.BrushType.HIGHLIGHTER, + 'INKPEN': sketchology.proto.BrushType.INKPEN, + 'MARKER': sketchology.proto.BrushType.MARKER, + 'BALLPOINT': sketchology.proto.BrushType.BALLPOINT, + 'BALLPOINT_IN_PEN_MODE_ELSE_MARKER': + sketchology.proto.BrushType.BALLPOINT_IN_PEN_MODE_ELSE_MARKER, + 'PENCIL': sketchology.proto.BrushType.PENCIL, +}; + + +/** + * @param {string} color The color in hex. + */ +ink.BrushModel.prototype.setColor = function(color) { + this.color_ = color; + this.dispatchEvent(ink.BrushModel.EventType.CHANGE); +}; + + +/** + * @param {number} strokeWidth The brush's stroke width. + */ +ink.BrushModel.prototype.setStrokeWidth = function(strokeWidth) { + this.strokeWidth_ = strokeWidth; + this.dispatchEvent(ink.BrushModel.EventType.CHANGE); +}; + + +/** + * @param {boolean} isErasing Whether user is erasing or not. + */ +ink.BrushModel.prototype.setIsErasing = function(isErasing) { + this.isErasing_ = isErasing; + this.dispatchEvent(ink.BrushModel.EventType.CHANGE); +}; + + +/** + * @param {string} shape The brush shape, which is either a brush type or a tool + * type. If it's a brush type, implies tool type LINE. + */ +ink.BrushModel.prototype.setShape = function(shape) { + this.toolType_ = ink.BrushModel.SHAPE_TO_TOOLTYPE[shape]; + this.brushType_ = ink.BrushModel.SHAPE_TO_BRUSHTYPE[shape] !== undefined ? + ink.BrushModel.SHAPE_TO_BRUSHTYPE[shape] : + this.brushType_; + this.shape_ = shape; + this.dispatchEvent(ink.BrushModel.EventType.CHANGE); +}; + + +/** + * @return {string} The last used shape. + */ +ink.BrushModel.prototype.getShape = function() { + return this.shape_; +}; + + +/** + * @return {string} The last draw color in hex (excluding erase color). + */ +ink.BrushModel.prototype.getColor = function() { + return this.color_; +}; + + +/** + * Gets the current color being drawn on the screen (including erase color). + * @return {string} The brush color in hex. + */ +ink.BrushModel.prototype.getActiveColor = function() { + if (!this.isErasing_) { + return this.color_; + } else { + return ink.BrushModel.DEFAULT_ERASE_COLOR; + } +}; + + +/** + * Wraps getActiveColor() by returning the numeric rgb of the color. + * @return {number} The brush color in numeric rbg. + */ +ink.BrushModel.prototype.getActiveColorNumericRbg = function() { + return parseInt(this.getActiveColor().substring(1), 16); +}; + + +/** + * @return {number} Percentage size for stroke width, [0, 1]. + * + * See sengine.proto SizeType + */ +ink.BrushModel.prototype.getStrokeWidth = function() { + return this.strokeWidth_; +}; + + +/** + * @return {boolean} Whether user is erasing. + */ +ink.BrushModel.prototype.getIsErasing = function() { + return this.isErasing_; +}; + + +/** + * @return {sketchology.proto.BrushType} The brush type for line + * tool. + */ +ink.BrushModel.prototype.getBrushType = function() { + return this.brushType_; +}; + + +/** + * @return {sketchology.proto.ToolParams.ToolType} The tool type. + */ +ink.BrushModel.prototype.getToolType = function() { + return this.toolType_; +}; +
diff --git a/third_party/ink/sketchology/public/js/common/color.js b/third_party/ink/sketchology/public/js/common/color.js new file mode 100644 index 0000000..1a61210 --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/color.js
@@ -0,0 +1,113 @@ +// Copyright 2017 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. +goog.provide('ink.Color'); + + + +/** + * 32bit color representation. Each channels is a 8 bit uint. + * @param {number} argb The 32-bit color. + * @constructor + * @struct + */ +ink.Color = function(argb) { + /** @type {number} */ + this.argb = argb; + + /** @type {number} */ + this.a = ink.Color.alphaFromArgb(argb); + + /** @type {number} */ + this.r = ink.Color.redFromArgb(argb); + + /** @type {number} */ + this.g = ink.Color.greenFromArgb(argb); + + /** @type {number} */ + this.b = ink.Color.blueFromArgb(argb); +}; + + +/** + * @return {string} The color string (excluding alpha) that can be used as a + * fillStyle. + */ +ink.Color.prototype.getRgbString = function() { + return 'rgb(' + [this.r, this.g, this.b].join(',') + ')'; +}; + + +/** @return {string} The color string that can be used as a fillStyle. */ +ink.Color.prototype.getRgbaString = function() { + return 'rgba(' + [this.r, this.g, this.b, this.a / 255].join(',') + ')'; +}; + + +/** @return {Uint32Array} color as rgba 32-bit unsigned integer */ +ink.Color.prototype.getRgbaUint32 = function() { + return new Uint32Array( + [(this.r << 24) | (this.g << 16) | (this.b << 8) | this.a]); +}; + + +/** + * @return {number} The alpha in the range 0-1 that can be used as a + * globalAlpha. + */ +ink.Color.prototype.getAlphaAsFloat = function() { + return this.a / 255; +}; + + +/** + * Helper function that returns a function that right logical shifts by the + * provided amount and masks off the result. + * @param {number} shiftAmount The amount that the function should shift by. + * @return {!Function} + * @private + */ +ink.Color.shiftAndMask_ = function(shiftAmount) { + return function(argb) { + return (argb >>> shiftAmount) & 0xFF; + }; +}; + + +/** + * @param {number} argb The argb number. + * @return {number} alpha in the range 0 to 255. + */ +ink.Color.alphaFromArgb = ink.Color.shiftAndMask_(24); + + +/** + * @param {number} argb The argb number. + * @return {number} red in the range 0 to 255. + */ +ink.Color.redFromArgb = ink.Color.shiftAndMask_(16); + + +/** + * @param {number} argb The argb number. + * @return {number} green in the range 0 to 255. + */ +ink.Color.greenFromArgb = ink.Color.shiftAndMask_(8); + + +/** + * @param {number} argb The argb number. + * @return {number} blue in the range 0 to 255. + */ +ink.Color.blueFromArgb = ink.Color.shiftAndMask_(0); + + +/** @type {!ink.Color} */ +ink.Color.BLACK = new ink.Color(0xFF000000); + + +/** @type {!ink.Color} */ +ink.Color.WHITE = new ink.Color(0xFFFFFFFF); + +/** @type {!ink.Color} */ +ink.Color.DEFAULT_BACKGROUND_COLOR = new ink.Color(0xFFFAFAFA);
diff --git a/third_party/ink/sketchology/public/js/common/element_listener.js b/third_party/ink/sketchology/public/js/common/element_listener.js new file mode 100644 index 0000000..a149f25 --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/element_listener.js
@@ -0,0 +1,32 @@ +// Copyright 2017 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. +/** + * @fileoverview Element listener interface declaration + */ +goog.provide('ink.ElementListener'); + +/** + * @interface + */ +ink.ElementListener = function() {}; + +/** + * @param {string} uuid + * @param {string} encodedElement + * @param {string} encodedTransform + */ +ink.ElementListener.prototype.onElementCreated = function( + uuid, encodedElement, encodedTransform) {}; + +/** + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + */ +ink.ElementListener.prototype.onElementsMutated = function( + uuids, encodedTransforms) {}; + +/** + * @param {Array.<string>} uuids + */ +ink.ElementListener.prototype.onElementsRemoved = function(uuids) {};
diff --git a/third_party/ink/sketchology/public/js/common/model.js b/third_party/ink/sketchology/public/js/common/model.js new file mode 100644 index 0000000..0a726a4 --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/model.js
@@ -0,0 +1,89 @@ +// Copyright 2017 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. +goog.provide('ink.Model'); + +goog.require('goog.asserts'); +goog.require('goog.events.EventTarget'); +goog.require('ink.util'); + + + +/** + * A generic model base class. Each models here is a singleton per EventTarget + * hierarchy tree. + * + * @extends {goog.events.EventTarget} + * @constructor + * @struct + */ +ink.Model = function() { + ink.Model.base(this, 'constructor'); +}; +goog.inherits(ink.Model, goog.events.EventTarget); + + +/** + * The models are attached to the root parent EventTargets. To have the models + * be automatically gc properly the relevant models need to actually be + * properties of those EventTargets. This property is initialized to avoid + * collisions with other JavaScript on the same page, similarly to the property + * that goog.getUid uses. + * @private {string} + */ +ink.Model.MODEL_INSTANCES_PROPERTY_ = 'ink_model_instances_' + Math.random(); + + +/** + * Adds the getter to the model constructor, allowing for the simpler + * ink.BrushModel.getInstance(this); instead of + * ink.Model.get(ink.BrushModel, this); + * @param {!function(new:ink.Model, !goog.events.EventTarget)} modelCtor + * The model constructor. + */ +ink.Model.addGetter = function(modelCtor) { + /** + * @param {!goog.events.EventTarget} observer + * @return {!ink.Model} + */ + modelCtor.getInstance = function(observer) { + goog.asserts.assertObject(observer); + return ink.Model.get(modelCtor, observer); + }; +}; + + +/** + * Gets the relevant model for the provided viewer. The viewer should be a + * goog.ui.Component that has entered the document or a goog.events.EventTarget + * that has already had its parentEventTarget set. + * + * Note: This currently assumes that the provided models are singletons per + * EventTarget hierarchy tree. A more flexible design for deciding what level + * to have models should be added here if usage demands it. + * + * @param {!function(new:ink.Model, !goog.events.EventTarget)} modelCtor + * @param {!goog.events.EventTarget} observer + * @return {!ink.Model} + */ +ink.Model.get = function(modelCtor, observer) { + // TODO(esrauch): Maybe this should be implemented based on dom elements + // instead of the goog.ui.Component hierarchy. As it is, a stray setParent() + // call could cause the Model instance to suprisingly change for the same + // observer. On the other hand, reading the dom is slower and also can cause + // a brower reflow unnecessarily and this way also allows for vanilla + // EventTargets to get the relevant Models. + var root = ink.util.getRootParentComponent(observer); + var models = root[ink.Model.MODEL_INSTANCES_PROPERTY_]; + if (!models) { + root[ink.Model.MODEL_INSTANCES_PROPERTY_] = models = {}; + } + var key = goog.getUid(modelCtor); + var oldInstance = models[key]; + if (oldInstance) { + return oldInstance; + } + var newInstance = new modelCtor(root); + models[key] = newInstance; + return newInstance; +};
diff --git a/third_party/ink/sketchology/public/js/common/proto_serializer.js b/third_party/ink/sketchology/public/js/common/proto_serializer.js new file mode 100644 index 0000000..dc99eeb --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/proto_serializer.js
@@ -0,0 +1,77 @@ +// Copyright 2017 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. +goog.provide('ink.ProtoSerializer'); + + +goog.require('goog.crypt.base64'); +goog.require('goog.proto2.ObjectSerializer'); // for debugging +goog.require('net.proto2.contrib.WireSerializer'); + + + +/** + * A proto serializer / deserializer to and from base-64 encoded wire format. + * @constructor + * @struct + */ +ink.ProtoSerializer = function() { + /** @private {!net.proto2.contrib.WireSerializer} */ + this.wireSerializer_ = new net.proto2.contrib.WireSerializer(); +}; + + +/** + * @param {!goog.proto2.Message} e proto to serialize + * @return {string} The serialized proto as a base 64 encoded string. + */ +ink.ProtoSerializer.prototype.serializeToBase64 = function(e) { + var buf = this.wireSerializer_.serialize(e); + return goog.crypt.base64.encodeByteArray(buf); +}; + + +/** + * Deserializes the given opaque serialized object to a jspb object. + * + * @param {string} item serialized object as base64 text + * @param {!goog.proto2.Message} proto Proto to deserialize into + * @return {!goog.proto2.Message} + */ +ink.ProtoSerializer.prototype.safeDeserialize = function(item, proto) { + var buf = goog.crypt.base64.decodeStringToByteArray(item); + this.wireSerializer_.deserializeTo(proto, new Uint8Array(buf)); + return proto; +}; + + +/** + * @param {!sketchology.proto.Element} p + * @return {boolean} Whether the provided Element appears valid. + * @private + */ +ink.ProtoSerializer.prototype.isValid_ = function(p) { + if (p == null) { + return false; + } + + if (!p.hasStroke()) { + return false; + } + + return true; +}; + + +/** + * Returns a human-readable representation of a proto. + * + * @param {!goog.proto2.Message} p The proto to debug. + * @return {string} A nice string to ponder. + * @private + */ +ink.ProtoSerializer.prototype.debugProto_ = function(p) { + var obj = new goog.proto2.ObjectSerializer( + goog.proto2.ObjectSerializer.KeyOption.NAME).serialize(p); + return JSON.stringify(obj, null, ' '); +};
diff --git a/third_party/ink/sketchology/public/js/common/undo_state_change_event.js b/third_party/ink/sketchology/public/js/common/undo_state_change_event.js new file mode 100644 index 0000000..5b65a277 --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/undo_state_change_event.js
@@ -0,0 +1,28 @@ +// Copyright 2017 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. +goog.provide('ink.UndoStateChangeEvent'); + +goog.require('goog.events'); +goog.require('goog.events.Event'); + + + +/** + * @param {boolean} canUndo Whether there is undo state available. + * @param {boolean} canRedo Whether there is redo state available. + * @constructor + * @struct + * @extends {goog.events.Event} + */ +ink.UndoStateChangeEvent = function(canUndo, canRedo) { + ink.UndoStateChangeEvent.base( + this, 'constructor',ink.UndoStateChangeEvent.EVENT_TYPE); + this.canUndo = canUndo; + this.canRedo = canRedo; +}; +goog.inherits(ink.UndoStateChangeEvent, goog.events.Event); + + +/** @type {string} */ +ink.UndoStateChangeEvent.EVENT_TYPE = goog.events.getUniqueId('undo-state');
diff --git a/third_party/ink/sketchology/public/js/common/util.js b/third_party/ink/sketchology/public/js/common/util.js new file mode 100644 index 0000000..85fd56e --- /dev/null +++ b/third_party/ink/sketchology/public/js/common/util.js
@@ -0,0 +1,292 @@ +// Copyright 2017 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. +goog.provide('ink.util'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Size'); +goog.require('protos.research.ink.InkEvent'); + + +/** @enum {string} */ +ink.util.SEngineType = { + IN_MEMORY: 'makeSEngineInMemory', + LONGFORM: 'makeLongformSEngine', + PASSTHROUGH_DOCUMENT: 'makeSEnginePassthroughDocument' +}; + + +/** + * @typedef {{ + * getModel: (function():ink.util.RealtimeModel) + * }} + */ +ink.util.RealtimeDocument; + + +/** + * @typedef {{ + * getRoot: (function():ink.util.RealtimeRoot) + * }} + */ +ink.util.RealtimeModel; + + +/** + * @typedef {{ + * get: (function(string):ink.util.RealtimePages) + * }} + */ +ink.util.RealtimeRoot; + + +/** + * @typedef {{ + * get: (function(number):ink.util.RealtimePage), + * xhigh: number, + * xlow: number, + * yhigh: number, + * ylow: number + * }} + */ +ink.util.RealtimePages; + + +/** + * @typedef {{ + * get: (function(string):ink.util.RealtimeElements) + * }} + */ +ink.util.RealtimePage; + + +/** + * @typedef {{ + * asArray: (function():Array.<ink.util.RealtimeElement>) + * }} + */ +ink.util.RealtimeElements; + + +/** + * @typedef {{ + * get: (function(string):string) + * }} + */ +ink.util.RealtimeElement; + + +/** + * Wrapper used for event handlers to pass the event target instead of the + * event. + * @param {!Function} callback The event handler function. + * @param {Object=} opt_handler Element in whole scope to call the callback. + * @return {!Function} Wrapped function. + */ +ink.util.eventTargetWrapper = function(callback, opt_handler) { + return function(evt) { + var self = /** @type {Object} */ (this); + callback.call(opt_handler || self, evt.target); + }; +}; + + +/** + * @param {!goog.events.EventTarget} child The child to get the root parent + * EventTarget for. + * @return {!goog.events.EventTarget} The root parent. + * TODO(esrauch): Rename this function and consider moving it to a new + * whiteboard/util.js file. + */ +ink.util.getRootParentComponent = function(child) { + var current = child; + var parent = current.getParentEventTarget(); + while (parent) { + current = parent; + parent = current.getParentEventTarget(); + } + return current; +}; + + +/** + * Downloads an image, renders it to a canvas, returns the raw bytes. + * @param {string} imgSrc + * @param {Function} callback + */ +ink.util.getImageBytes = function(imgSrc, callback) { + var canvasElement = goog.dom.createElement(goog.dom.TagName.CANVAS); + var imgElement = goog.dom.createElement(goog.dom.TagName.IMG); + imgElement.setAttribute( + 'style', + 'position:absolute;visibility:hidden;top:-1000px;left:-1000px;'); + imgElement.crossOrigin = 'Anonymous'; + + goog.events.listenOnce(imgElement, 'load', function() { + var width = imgElement.width; + var height = imgElement.height; + canvasElement.width = width; + canvasElement.height = height; + var ctx = canvasElement.getContext('2d'); + ctx.drawImage(imgElement, 0, 0); + var data = ctx.getImageData(0, 0, width, height); + + document.body.removeChild(imgElement); + + callback(data.data, new goog.math.Size(width, height)); + }); + + imgElement.setAttribute('src', imgSrc); + document.body.appendChild(imgElement); +}; + + +/** + * Creates document events. + * @param {!protos.research.ink.InkEvent.Host} host + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentEventType} type + * @return {!protos.research.ink.InkEvent} + */ +ink.util.createDocumentEvent = function(host, type) { + var eventProto = new protos.research.ink.InkEvent(); + eventProto.setHost(host); + eventProto.setEventType( + protos.research.ink.InkEvent.EventType.DOCUMENT_EVENT); + var documentEvent = new protos.research.ink.InkEvent.DocumentEvent(); + documentEvent.setEventType(type); + eventProto.setDocumentEvent(documentEvent); + return eventProto; +}; + + +/** + * Helper for constructing a document created event + * + * @param {!protos.research.ink.InkEvent.Host} host + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} state + * @param {number} firstLoadTime (which is when the first byte is loaded) + * @param {number} startLoadTime (which is when the document is first editable). + * @return {!protos.research.ink.InkEvent} + */ +ink.util.createDocumentOpenedEvent = function( + host, state, firstLoadTime, startLoadTime) { + var type = + protos.research.ink.InkEvent.DocumentEvent.DocumentEventType.OPENED; + var ev = ink.util.createDocumentEvent(host, type); + var openedEvent = + new protos.research.ink.InkEvent.DocumentEvent.OpenedEvent(); + openedEvent.setMillisUntilFirstByteLoaded(firstLoadTime.toString()); + openedEvent.setMillisUntilEditable((goog.now() - startLoadTime).toString()); + var documentEvent = ev.getDocumentEventOrDefault(); + documentEvent.setOpenedEvent(openedEvent); + documentEvent.setDocumentState(state); + return ev; +}; + + +/** + * Helper for constructing a collaborator joined logging event. + * + * @param {!protos.research.ink.InkEvent.Host} host + * @param {!protos.research.ink.InkEvent.DocumentEvent.DocumentState} state + * @param {boolean} isMe + * @return {!protos.research.ink.InkEvent} + */ +ink.util.createCollaboratorJoinedDocumentEvent = function(host, state, isMe) { + var type = protos.research.ink.InkEvent.DocumentEvent.DocumentEventType + .COLLABORATOR_JOINED; + var ev = ink.util.createDocumentEvent(host, type); + var collaboratorJoinedEvent = + new protos.research.ink.InkEvent.DocumentEvent.CollaboratorJoined(); + collaboratorJoinedEvent.setIsMe(isMe); + var documentEvent = ev.getDocumentEventOrDefault(); + documentEvent.setCollaboratorJoinedEvent(collaboratorJoinedEvent); + documentEvent.setDocumentState(state); + return ev; +}; + + +/** + * @const + */ +ink.util.SHAPE_TO_LOG_TOOLTYPE = { + 'CALLIGRAPHY': protos.research.ink.InkEvent.ToolbarEvent.ToolType.CALLIGRAPHY, + 'EDIT': protos.research.ink.InkEvent.ToolbarEvent.ToolType.EDIT_TOOL, + 'HIGHLIGHTER': protos.research.ink.InkEvent.ToolbarEvent.ToolType.HIGHLIGHTER, + 'MAGIC_ERASE': protos.research.ink.InkEvent.ToolbarEvent.ToolType.MAGIC_ERASER, + 'MARKER': protos.research.ink.InkEvent.ToolbarEvent.ToolType.MARKER +}; + + +/** + * Creates toolbar events. + * @param {protos.research.ink.InkEvent.Host} host + * @param {protos.research.ink.InkEvent.ToolbarEvent.ToolEventType} type + * @param {string} toolType + * @param {string} color + * @return {protos.research.ink.InkEvent} + */ +ink.util.createToolbarEvent = function(host, type, toolType, color) { + var eventProto = new protos.research.ink.InkEvent(); + eventProto.setHost(host); + eventProto.setEventType(protos.research.ink.InkEvent.EventType.TOOLBAR_EVENT); + var toolbarEvent = new protos.research.ink.InkEvent.ToolbarEvent(); + // Alpha is always 0xFF. This does #RRGGBB -> #AARRGGBB -> 0xAARRGGBB. + var colorAsNumber = parseInt('ff' + color.substring(1, 7), 16); + toolbarEvent.setColor(colorAsNumber); + toolbarEvent.setToolType( + ink.util.SHAPE_TO_LOG_TOOLTYPE[toolType] || + protos.research.ink.InkEvent.ToolbarEvent.ToolType.UNKNOWN_TOOL_TYPE); + toolbarEvent.setToolEventType(type); + eventProto.setToolbarEvent(toolbarEvent); + return eventProto; +}; + + +// Note: These helpers are included here because we do not use the closure +// browser event wrappers to avoid additional GC pauses. Logic forked from +// cs/piper///depot/google3/javascript/closure/events/browserevent.js?l=337 + +/** + * "Action button" is MouseEvent.button equal to 0 (main button) and no + * control-key for Mac right-click action. The button property is the one + * that was responsible for triggering a mousedown or mouseup event. + * + * @param {MouseEvent} evt + * @return {boolean} + */ +ink.util.isMouseActionButton = function(evt) { + return evt.button == 0 && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && evt.ctrlKey); +}; + + +/** + * MouseEvent.buttons has 1 (main button) held down, and no control-key + * for Mac right-click drag. This is for which button is currently held down, + * e.g. during a mousemove event. + * + * @param {MouseEvent} evt + * @return {boolean} + */ +ink.util.hasMouseActionButton = function(evt) { + return (evt.buttons & 1) == 1 && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && evt.ctrlKey); +}; + + +/** + * Checks MouseEvent.buttons has 2 (secondary button) held down, or 1 (main + * button) with control key for Mac right-click drag. This is for which + * button is currently held down, e.g. during a mousemove event. + * + * @param {MouseEvent} evt + * @return {boolean} + */ +ink.util.hasMouseSecondaryButton = function(evt) { + return (evt.buttons & 2) == 2 || + ((evt.buttons & 1) == 1 && goog.userAgent.WEBKIT && + goog.userAgent.MAC && evt.ctrlKey); +}; +
diff --git a/third_party/ink/sketchology/public/nacl/embed.soy.js b/third_party/ink/sketchology/public/nacl/embed.soy.js new file mode 100644 index 0000000..e0b4325 --- /dev/null +++ b/third_party/ink/sketchology/public/nacl/embed.soy.js
@@ -0,0 +1,50 @@ +// Copyright 2017 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 was automatically generated from embed.soy. +// Please don't edit this file by hand. + +/** + * @fileoverview Templates in namespace ink.soy.nacl. + * @public + */ + +goog.provide('ink.soy.nacl.canvasHTML'); + +goog.require('goog.soy.data.SanitizedContent'); +goog.require('soy'); +goog.require('soy.asserts'); +goog.require('soydata.VERY_UNSAFE'); + + +/** + * @param {ink.soy.nacl.canvasHTML.Params} opt_data + * @param {Object<string, *>=} opt_ijData + * @param {Object<string, *>=} opt_ijData_deprecated + * @return {!goog.soy.data.SanitizedHtml} + * @suppress {checkTypes} + */ +ink.soy.nacl.canvasHTML = function(opt_data, opt_ijData, opt_ijData_deprecated) { + opt_ijData = opt_ijData_deprecated || opt_ijData; + /** @type {!goog.soy.data.SanitizedContent|string} */ + var manifestUrl = soy.asserts.assertType(goog.isString(opt_data.manifestUrl) || opt_data.manifestUrl instanceof goog.soy.data.SanitizedContent, 'manifestUrl', opt_data.manifestUrl, '!goog.soy.data.SanitizedContent|string'); + /** @type {!goog.soy.data.SanitizedContent|string} */ + var useMSAA = soy.asserts.assertType(goog.isString(opt_data.useMSAA) || opt_data.useMSAA instanceof goog.soy.data.SanitizedContent, 'useMSAA', opt_data.useMSAA, '!goog.soy.data.SanitizedContent|string'); + /** @type {!goog.soy.data.SanitizedContent|string} */ + var useSingleBuffer = soy.asserts.assertType(goog.isString(opt_data.useSingleBuffer) || opt_data.useSingleBuffer instanceof goog.soy.data.SanitizedContent, 'useSingleBuffer', opt_data.useSingleBuffer, '!goog.soy.data.SanitizedContent|string'); + /** @type {!goog.soy.data.SanitizedContent|string} */ + var sengineType = soy.asserts.assertType(goog.isString(opt_data.sengineType) || opt_data.sengineType instanceof goog.soy.data.SanitizedContent, 'sengineType', opt_data.sengineType, '!goog.soy.data.SanitizedContent|string'); + return soydata.VERY_UNSAFE.ordainSanitizedHtml(((goog.DEBUG && soy.$$debugSoyTemplateInfo) ? '<!--dta_of(ink.soy.nacl.canvasHTML, third_party/sketchology/public/nacl/embed.soy, 3)-->' : '') + '<style' + (opt_ijData && opt_ijData.csp_nonce ? ' nonce="' + soy.$$escapeHtmlAttribute(opt_ijData && opt_ijData.csp_nonce) + '"' : '') + '>\n #ink-engine-hwoverlay {\n display: none;\n position: absolute;\n width: 5px;\n height: 5px;\n left: 0px;\n top: 0px;\n /* Transforms and semi-transparent color are used to ensure the div\n * prevents use of a hardware overlay for the underlying canvas element,\n * despite future optimizations to the hardware overlay eligibility\n * detection in ChromeOS. See b/64569245 for details */\n background-color: rgba(0, 0, 0, 0.01);\n transform: translate3d(0.33, 0.14, 0);\n }\n </style><embed id="ink-engine" use_msaa="' + soy.$$escapeHtmlAttribute(useMSAA) + '" use_single_buffer="' + soy.$$escapeHtmlAttribute(useSingleBuffer) + '" src="' + soy.$$escapeHtmlAttribute(soy.$$filterNormalizeUri(manifestUrl)) + '" type="application/x-nacl" sengine_type="' + soy.$$escapeHtmlAttribute(sengineType) + '"><div id="ink-engine-hwoverlay"></div>' + ((goog.DEBUG && soy.$$debugSoyTemplateInfo) ? '<!--dta_cf(ink.soy.nacl.canvasHTML)-->' : '')); +}; +/** + * @typedef {{ + * manifestUrl: (!goog.soy.data.SanitizedContent|string), + * useMSAA: (!goog.soy.data.SanitizedContent|string), + * useSingleBuffer: (!goog.soy.data.SanitizedContent|string), + * sengineType: (!goog.soy.data.SanitizedContent|string), + * }} + */ +ink.soy.nacl.canvasHTML.Params; +if (goog.DEBUG) { + ink.soy.nacl.canvasHTML.soyTemplateName = 'ink.soy.nacl.canvasHTML'; +}
diff --git a/third_party/ink/sketchology/public/nacl/sketchology_engine_wrapper.js b/third_party/ink/sketchology/public/nacl/sketchology_engine_wrapper.js new file mode 100644 index 0000000..8ef2088 --- /dev/null +++ b/third_party/ink/sketchology/public/nacl/sketchology_engine_wrapper.js
@@ -0,0 +1,764 @@ +// Copyright 2017 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. +/** + * @fileoverview Wrapper to call the Sketchology engine. + */ + +goog.provide('ink.SketchologyEngineWrapper'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.labs.userAgent.platform'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Rect'); +goog.require('goog.math.Size'); +goog.require('goog.soy'); +goog.require('goog.ui.Component'); +goog.require('ink.Color'); +goog.require('ink.ElementListener'); +goog.require('ink.UndoStateChangeEvent'); +goog.require('ink.soy.nacl.canvasHTML'); +goog.require('ink.util'); +goog.require('net.proto2.contrib.WireSerializer'); +goog.require('sketchology.proto.BackgroundColor'); +goog.require('sketchology.proto.BackgroundImageInfo'); +goog.require('sketchology.proto.Command'); +goog.require('sketchology.proto.Flag'); +goog.require('sketchology.proto.FlagAssignment'); +goog.require('sketchology.proto.ImageInfo'); +goog.require('sketchology.proto.MutationPacket'); +goog.require('sketchology.proto.NoArgCommand'); +goog.require('sketchology.proto.OutOfBoundsColor'); +goog.require('sketchology.proto.PageProperties'); +goog.require('sketchology.proto.Rect'); +goog.require('sketchology.proto.SequencePoint'); +goog.require('sketchology.proto.SetCallbackFlags'); +goog.require('sketchology.proto.Snapshot'); +goog.require('sketchology.proto.ToolParams'); + + + +/** + * @param {?string} engineUrl URL to the native client manifest file + * @param {ink.ElementListener} elementListener + * @param {function(number, number, Uint8ClampedArray)} onImageExportComplete + * @param {!ink.util.SEngineType} sengineType + * @struct + * @constructor + * @extends {goog.ui.Component} + */ +ink.SketchologyEngineWrapper = function( + engineUrl, elementListener, onImageExportComplete, sengineType) { + ink.SketchologyEngineWrapper.base(this, 'constructor'); + + goog.asserts.assert(engineUrl); + /** @private {string} */ + this.engineUrl_ = engineUrl; + + /** @private {ink.ElementListener} */ + this.elementListener_ = elementListener; + + /** @private {function(number, number, Uint8ClampedArray)} */ + this.onImageExportComplete_ = onImageExportComplete; + + // default document bounds + this.pageLeft_ = 0; + this.pageTop_ = 600; + this.pageRight_ = 800; + this.pageBottom_ = 0; + + /** + * Protocol Buffer wire format serializer. + * @type {!net.proto2.contrib.WireSerializer} + * @private + */ + this.wireSerializer_ = new net.proto2.contrib.WireSerializer(); + + /** @private */ + this.lastBrushUpdate_ = goog.nullFunction; + + /** @private */ + this.penModeEnabled_ = false; + + /** @private {boolean} */ + this.listenersAdded_ = false; + + /** @private {string} */ + this.sengineType_ = /** @type {string} */ (sengineType); + + /** @private {Array.<function(!sketchology.proto.Snapshot)>} */ + this.snapshotCallbacks_ = []; + + /** @private {Array.<function(!sketchology.proto.Snapshot)>} */ + this.brixConversionCallbacks_ = []; + + /** @private {Array.<!ink.util.RealtimeDocument>} */ + this.brixDocuments_ = []; + + /** @private {Array.<function(boolean)>} */ + this.snapshotHasPendingMutationsCallbacks_ = []; + + /** @private {Array.<function(sketchology.proto.MutationPacket)>} */ + this.extractMutationPacketCallbacks_ = []; + + /** @private {Array.<function(sketchology.proto.Snapshot)>} */ + this.clearPendingMutationsCallbacks_ = []; + + /** @private {Element} */ + this.engineElement_; + + /** @private {Object<number, !Function>} */ + this.sequencePointCallbacks_ = {}; + + /** @private {number} */ + this.nextSequencePointId_ = 0; +}; +goog.inherits(ink.SketchologyEngineWrapper, goog.ui.Component); + +/** @override */ +ink.SketchologyEngineWrapper.prototype.createDom = function() { + const useMSAA = !goog.labs.userAgent.platform.isMacintosh() || + // MSAA is disabled for MacOS 10.12.4 and prior: b/38280481 + goog.labs.userAgent.platform.isVersionOrHigher('10.12.5'); + const useSingleBuffer = goog.labs.userAgent.platform.isChromeOS(); + const elem = goog.soy.renderAsElement(ink.soy.nacl.canvasHTML, { + manifestUrl: this.engineUrl_, + useMSAA: !!useMSAA + '', + useSingleBuffer: !!useSingleBuffer + '', + sengineType: this.sengineType_ + }); + this.setElementInternal(elem); + this.engineElement_ = elem.querySelector('#ink-engine'); +}; + +/** @override */ +ink.SketchologyEngineWrapper.prototype.enterDocument = function() { + this.engineElement_.addEventListener(goog.events.EventType.LOAD, () => { + this.initGl(); + this.lastBrushUpdate_(); + this.assignFlag( + sketchology.proto.Flag.ENABLE_PEN_MODE, this.penModeEnabled_); + }); +}; + + +/** @enum {string} */ +ink.SketchologyEngineWrapper.EventType = { + CANVAS_INITIALIZED: goog.events.getUniqueId('gl_canvas_initialized'), + CANVAS_FATAL_ERROR: goog.events.getUniqueId('fatal_error'), + PEN_MODE_ENABLED: goog.events.getUniqueId('pen_mode_enabled') +}; + + +/** + * An event fired when pen mode is enabled or disabled. + * + * @param {boolean} enabled + * + * @extends {goog.events.Event} + * @constructor + * @struct + */ +ink.SketchologyEngineWrapper.PenModeEnabled = function(enabled) { + ink.SketchologyEngineWrapper.PenModeEnabled.base(this, 'constructor', + ink.SketchologyEngineWrapper.EventType.PEN_MODE_ENABLED); + + /** @type {boolean} */ + this.enabled = enabled; +}; +goog.inherits(ink.SketchologyEngineWrapper.PenModeEnabled, goog.events.Event); + + +/** Poke the engine to wake up and start drawing */ +ink.SketchologyEngineWrapper.prototype.poke = function() { + this.engineElement_.postMessage(['poke', '']); +}; + +/** + * Global exit function for emscripten to call. + * @export + */ +ink.SketchologyEngineWrapper.exit = function() { + console.log('Engine requested exit.'); +}; + +/** + * @param {sketchology.proto.Command} command + */ +ink.SketchologyEngineWrapper.prototype.handleCommand = function(command) { + var commandBytes = this.wireSerializer_.serialize(command); + var buf = new Uint8Array(commandBytes); + this.engineElement_.postMessage(['handleCommand', buf.buffer]); +}; + +/** + * Tells the engine to handle a message received remotely. + * + * @param {!Object<string, string>} bundle + */ +ink.SketchologyEngineWrapper.prototype.addElement = function(bundle) { + goog.asserts.assert(bundle); + this.engineElement_.postMessage(['addElementToEngine', {'bundle': bundle}]); +}; + + +/** + * Add an encoded element bundle to the engine. + * + * @param {!Object<string, string>} bundle + * @param {string} belowUUID + */ +ink.SketchologyEngineWrapper.prototype.addElementBelow = function( + bundle, belowUUID) { + goog.asserts.assert(bundle); + this.engineElement_.postMessage( + ['addElementToEngineBelow', {'bundle': bundle, 'below_uuid': belowUUID}]); +}; + + +/** + * @param {string} uuid + */ +ink.SketchologyEngineWrapper.prototype.removeElement = function(uuid) { + this.engineElement_.postMessage(['removeElement', uuid]); +}; + + +/** + * NaCl does its own GL initialization, so we just hook up listeners. + */ +ink.SketchologyEngineWrapper.prototype.initGl = function() { + var elem = this.engineElement_; + this.setPageBounds( + this.pageLeft_, this.pageTop_, this.pageRight_, this.pageBottom_); + if (!this.listenersAdded_) { + this.listenersAdded_ = true; + elem.addEventListener('message', goog.bind(function(msg) { + if (!('event_type' in msg['data'])) { + return; // Unknown event type! + } + var data = msg['data']; + switch (data['event_type']) { + case 'exit': + ink.SketchologyEngineWrapper.exit(); + break; + case 'debug': + if (goog.DEBUG) { + console.log(data['message']); + } + break; + case 'image_export': + this.onImageExportComplete_( + data['width'], data['height'], + new Uint8ClampedArray(data['bytes'])); + break; + case 'element_added': + if (this.elementListener_) { + this.elementListener_.onElementCreated(data['uuid'], + data['encoded_element'], data['encoded_transform']); + } + break; + case 'elements_mutated': + if (this.elementListener_) { + this.elementListener_.onElementsMutated(data['uuids'], + data['encoded_transforms']); + } + break; + case 'elements_removed': + if (this.elementListener_) { + this.elementListener_.onElementsRemoved(data['uuids']); + } + break; + case 'flag_changed': + if (data['which'] == sketchology.proto.Flag.ENABLE_PEN_MODE) { + this.penModeEnabled_ = data['enabled']; + this.dispatchEvent( + new ink.SketchologyEngineWrapper.PenModeEnabled( + data['enabled'])); + } + break; + case 'undo_redo_state_changed': + this.dispatchEvent(new ink.UndoStateChangeEvent( + !!data['can_undo'], !!data['can_redo'])); + break; + case 'snapshot_gotten': + var proto = new sketchology.proto.Snapshot(); + this.wireSerializer_.deserializeTo(proto, data['snapshot']); + this.snapshotCallbacks_.shift().call(null, proto); + break; + case 'brix_elements_converted': + var proto = new sketchology.proto.Snapshot(); + this.wireSerializer_.deserializeTo(proto, data['snapshot']); + var pageProperties = new sketchology.proto.PageProperties(); + var rect = new sketchology.proto.Rect(); + var brixDoc = this.brixDocuments_.shift(); + var model = brixDoc.getModel(); + var root = model.getRoot(); + var brixBounds = root.get('bounds'); + rect.setXhigh(brixBounds.xhigh || 0); + rect.setXlow(brixBounds.xlow || 0); + rect.setYhigh(brixBounds.yhigh || 0); + rect.setYlow(brixBounds.ylow || 0); + pageProperties.setBounds(rect); + proto.setPageProperties(pageProperties); + this.brixConversionCallbacks_.shift().call(null, proto); + break; + case 'snapshot_has_pending_mutations': + this.snapshotHasPendingMutationsCallbacks_.shift().call( + null, data['has_mutations']); + break; + case 'extracted_mutation_packet': + var proto = new sketchology.proto.MutationPacket(); + this.wireSerializer_.deserializeTo(proto, data['extraction_packet']); + this.extractMutationPacketCallbacks_.shift().call(null, proto); + break; + case 'cleared_pending_mutations': + var proto = new sketchology.proto.Snapshot(); + this.wireSerializer_.deserializeTo(proto, data['snapshot']); + this.clearPendingMutationsCallbacks_.shift().call(null, proto); + break; + case 'hwoverlay': + this.setHardwareOverlay(!!data['enable']); + break; + case 'single_buffer': + // If the Native Client module was able to obtain a single buffered + // graphics context, flip the embed element to allow promotion to + // hardware overlay on Eve in landscape mode. + // TODO(b/64569245): Add support for all device orientations + this.engineElement_.style.transform = 'scaleY(-1)'; + break; + case 'sequence_point_reached': + var id = data['id']; + var cb = this.sequencePointCallbacks_[id]; + delete this.sequencePointCallbacks_[id]; + cb(); + break; + } + }, this)); + } + this.dispatchEvent(ink.SketchologyEngineWrapper.EventType.CANVAS_INITIALIZED); +}; + + +/** + * Sets the border image. + * @param {Uint8ClampedArray} data + * @param {goog.math.Size} size + * @param {string} uri + * @param {!sketchology.proto.Border} borderImageProto + * @param {number} outOfBoundsColor The out of bounds color in rgba 8888. + */ +ink.SketchologyEngineWrapper.prototype.setBorderImage = function( + data, size, uri, borderImageProto, outOfBoundsColor) { + var outOfBoundsColorProto = new sketchology.proto.OutOfBoundsColor(); + outOfBoundsColorProto.setRgba(outOfBoundsColor); + var commandProto = new sketchology.proto.Command(); + commandProto.setSetOutOfBoundsColor(outOfBoundsColorProto); + this.handleCommand(commandProto); + + var msg = { + 'imageData': data.buffer, + 'uri': uri, + 'width': size.width, + 'height': size.height, + 'assetType': sketchology.proto.ImageInfo.AssetType.BORDER + }; + this.engineElement_.postMessage(['addImageData', msg]); + + commandProto = new sketchology.proto.Command(); + commandProto.setSetPageBorder(borderImageProto); + this.handleCommand(commandProto); +}; + + +/** + * Sets the background image from a data URI. + * + * @param {Uint8ClampedArray} data + * @param {goog.math.Size} size + * @param {string} uri + * @param {!sketchology.proto.BackgroundImageInfo} bgImageProto + */ +ink.SketchologyEngineWrapper.prototype.setBackgroundImage = function( + data, size, uri, bgImageProto) { + var msg = { + 'imageData': data.buffer, + 'uri': uri, + 'width': size.width, + 'height': size.height, + 'assetType': sketchology.proto.ImageInfo.AssetType.DEFAULT + }; + this.engineElement_.postMessage(['addImageData', msg]); + + var commandProto = new sketchology.proto.Command(); + commandProto.setBackgroundImage(bgImageProto); + this.handleCommand(commandProto); +}; + + +/** + * Set the background color + * @param {ink.Color} color + */ +ink.SketchologyEngineWrapper.prototype.setBackgroundColor = function(color) { + var bgColorProto = new sketchology.proto.BackgroundColor(); + bgColorProto.setRgba(color.getRgbaUint32()[0]); + var commandProto = new sketchology.proto.Command(); + commandProto.setBackgroundColor(bgColorProto); + this.handleCommand(commandProto); +}; + + +/** + * Sets the camera position. + * + * @param {!goog.math.Rect} cameraRect The camera rect. + */ +ink.SketchologyEngineWrapper.prototype.setCamera = function(cameraRect) { + var camera = cameraRect.toBox(); + var rectProto = new sketchology.proto.Rect(); + // Top and bottom are reversed in Sketchology for "reasons." + rectProto.setYhigh(camera.bottom); + rectProto.setXhigh(camera.right); + rectProto.setYlow(camera.top); + rectProto.setXlow(camera.left); + var commandProto = new sketchology.proto.Command(); + commandProto.setCameraPosition(rectProto); + this.handleCommand(commandProto); +}; + + +/** + * @private + * @param {sketchology.proto.Rect} rectProto rect js proto with top/bottom + * reversed + * @return {goog.math.Rect} proper CSS rect with top/bottom correct + */ +ink.SketchologyEngineWrapper.prototype.convertRect_ = function(rectProto) { + // Top and bottom are reversed in Sketchology for "reasons." + var box = new goog.math.Box( + rectProto.getYlowOrDefault(), rectProto.getXhighOrDefault(), + rectProto.getYhighOrDefault(), rectProto.getXlowOrDefault()); + return box ? + goog.math.Rect.createFromBox(box) : + null; +}; + + +/** + * Create a scaled rectangle with a given size/center scaled by factor. + * + * @param {!goog.math.Coordinate} center + * @param {!goog.math.Size} size + * @param {number} factor The scale factor + * + * @return {goog.math.Rect} The scaled rectangle. + * @private + */ +ink.SketchologyEngineWrapper.prototype.getScaledRect_ = function( + center, size, factor) { + size.width /= factor; + size.height /= factor; + + var x = center.x - size.width / 2; + var y = center.y - size.height / 2; + + var cameraRect = new goog.math.Rect(x, y, size.width, size.height); + + goog.asserts.assert( + Math.round(cameraRect.getCenter().x) === Math.round(center.x) && + Math.round(cameraRect.getCenter().y) === Math.round(center.y)); + + return cameraRect; +}; + + +/** + * Sets the brush parameters. + * + * @param {Uint32Array} color rgba 32-bit unsigned color + * @param {number} strokeWidth brush size percent [0,1] + * @param {sketchology.proto.ToolParams.ToolType} toolType + * @param {sketchology.proto.BrushType} brushType + */ +ink.SketchologyEngineWrapper.prototype.brushUpdate = + function(color, strokeWidth, toolType, brushType) { + var self = this; + this.lastBrushUpdate_ = function() { + // LINE tools need special handling + if (toolType != sketchology.proto.ToolParams.ToolType.LINE) { + var toolParamsProto = new sketchology.proto.ToolParams(); + toolParamsProto.setTool(toolType); + var commandProto = new sketchology.proto.Command(); + commandProto.setToolParams(toolParamsProto); + this.handleCommand(commandProto); + } else { + var updateBrushData = { + 'brush': brushType, + 'rgba': color[0], + 'stroke_width': strokeWidth + }; + self.engineElement_.postMessage(['updateBrush', updateBrushData]); + } + }; + this.lastBrushUpdate_(); +}; + + +/** Clears the canvas. */ +ink.SketchologyEngineWrapper.prototype.clear = function() { + this.engineElement_.postMessage(['clear', '']); +}; + + +/** Removes all elements from the document. */ +ink.SketchologyEngineWrapper.prototype.removeAll = function() { + this.engineElement_.postMessage(['removeAll', '']); +}; + + +/** + * Sets or unsets readOnly on the canvas. + * @param {boolean} readOnly + */ +ink.SketchologyEngineWrapper.prototype.setReadOnly = function(readOnly) { + this.assignFlag(sketchology.proto.Flag.READ_ONLY_MODE, !!readOnly); +}; + + +/** + * Assign a flag on the canvas + * @param {sketchology.proto.Flag} flag + * @param {boolean} enable + */ +ink.SketchologyEngineWrapper.prototype.assignFlag = function(flag, enable) { + var flagProto = new sketchology.proto.FlagAssignment(); + flagProto.setFlag(flag); + flagProto.setBoolValue(!!enable); + var commandProto = new sketchology.proto.Command(); + commandProto.setFlagAssignment(flagProto); + this.handleCommand(commandProto); +}; + + + + +/** + * Sets element transforms. + * @param {Array.<string>} uuids + * @param {Array.<string>} encodedTransforms + */ +ink.SketchologyEngineWrapper.prototype.setElementTransforms = function( + uuids, encodedTransforms) { + if (uuids.length !== encodedTransforms.length) { + throw new Error('mismatch in transform array lengths'); + } + this.engineElement_.postMessage([ + 'setElementTransforms', + {'uuids': uuids, 'encoded_transforms': encodedTransforms} + ]); +}; + +/** + * Set callback flags for what data is attached to element callbacks. + * @param {!sketchology.proto.SetCallbackFlags} setCallbackFlags + */ +ink.SketchologyEngineWrapper.prototype.setCallbackFlags = function( + setCallbackFlags) { + var commandProto = new sketchology.proto.Command(); + commandProto.setSetCallbackFlags(setCallbackFlags); + this.handleCommand(commandProto); +}; + + +/** + * Sets the size of the page. + * @param {number} left + * @param {number} top + * @param {number} right + * @param {number} bottom + */ +ink.SketchologyEngineWrapper.prototype.setPageBounds = + function(left, top, right, bottom) { + this.pageLeft_ = left; + this.pageTop_ = top; + this.pageRight_ = right; + this.pageBottom_ = bottom; + var pageBounds = new sketchology.proto.Rect(); + pageBounds.setXlow(this.pageLeft_); + pageBounds.setYlow(this.pageBottom_); + pageBounds.setXhigh(this.pageRight_); + pageBounds.setYhigh(this.pageTop_); + var commandProto = new sketchology.proto.Command(); + commandProto.setPageBounds(pageBounds); + this.handleCommand(commandProto); +}; + + +/** + * Deselects anything selected with the edit tool. + */ +ink.SketchologyEngineWrapper.prototype.deselectAll = function() { + throw new Error('deselectAll not yet implemented for NaCl.'); +}; + + +/** + * Start the PNG export process. + * + * @param {!sketchology.proto.ImageExport} exportProto + */ +ink.SketchologyEngineWrapper.prototype.exportPng = function(exportProto) { + var commandProto = new sketchology.proto.Command(); + commandProto.setImageExport(exportProto); + this.handleCommand(commandProto); +}; + + +/** + * Simple undo. + */ +ink.SketchologyEngineWrapper.prototype.undo = function() { + var commandProto = new sketchology.proto.Command(); + commandProto.setUndo(new sketchology.proto.NoArgCommand()); + this.handleCommand(commandProto); +}; + + +/** + * Simple redo. + */ +ink.SketchologyEngineWrapper.prototype.redo = function() { + var commandProto = new sketchology.proto.Command(); + commandProto.setRedo(new sketchology.proto.NoArgCommand()); + this.handleCommand(commandProto); +}; + + +/** + * Returns the current snapshot. + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.SketchologyEngineWrapper.prototype.getSnapshot = function(callback) { + this.snapshotCallbacks_.push(callback); + this.engineElement_.postMessage(['getSnapshot']); +}; + + +/** + * Loads a document from a snapshot. + * + * @param {!sketchology.proto.Snapshot} snapshotProto + */ +ink.SketchologyEngineWrapper.prototype.loadFromSnapshot = + function(snapshotProto) { + var bytes = this.wireSerializer_.serialize(snapshotProto); + var buf = new Uint8Array(bytes); + this.engineElement_.postMessage(['loadFromSnapshot', buf.buffer]); +}; + + +/** + * Gets the raw engine object. Do not use this. + * @return {Object} + */ +ink.SketchologyEngineWrapper.prototype.getRawEngineObject = function() { + throw new Error('getRawEngineObject not supported for NaCl.'); +}; + + +/** + * Generates a snapshot based on a brix document. + * @param {!ink.util.RealtimeDocument} brixDoc + * @param {function(!sketchology.proto.Snapshot)} callback + */ +ink.SketchologyEngineWrapper.prototype.convertBrixDocumentToSnapshot = + function(brixDoc, callback) { + var model = brixDoc.getModel(); + var root = model.getRoot(); + var pages = root.get('pages'); + var page = pages.get(0); + if (!page) { + throw Error('unable to get page from brix document.'); + } + var elements = page.get('elements').asArray(); + + var jsonElements = []; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + jsonElements.push({'id': element.get('id'), + 'proto': element.get('proto'), + 'transform': element.get('transform')}); + } + this.brixDocuments_.push(brixDoc); + this.brixConversionCallbacks_.push(callback); + this.engineElement_.postMessage(['convertBrixElements', jsonElements]); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(boolean)} callback + */ +ink.SketchologyEngineWrapper.prototype.snapshotHasPendingMutations = + function(snapshot, callback) { + var bytes = this.wireSerializer_.serialize(snapshot); + var buf = new Uint8Array(bytes); + this.snapshotHasPendingMutationsCallbacks_.push(callback); + this.engineElement_.postMessage(['snapshotHasPendingMutations', buf.buffer]); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.MutationPacket)} callback + */ +ink.SketchologyEngineWrapper.prototype.extractMutationPacket = + function(snapshot, callback) { + var bytes = this.wireSerializer_.serialize(snapshot); + var buf = new Uint8Array(bytes); + this.extractMutationPacketCallbacks_.push(callback); + this.engineElement_.postMessage(['extractMutationPacket', buf.buffer]); +}; + + +/** + * @param {!sketchology.proto.Snapshot} snapshot + * @param {function(sketchology.proto.Snapshot)} callback + */ +ink.SketchologyEngineWrapper.prototype.clearPendingMutations = function( + snapshot, callback) { + var bytes = this.wireSerializer_.serialize(snapshot); + var buf = new Uint8Array(bytes); + this.clearPendingMutationsCallbacks_.push(callback); + this.engineElement_.postMessage(['clearPendingMutations', buf.buffer]); +}; + + +/** + * Enable or disable hardware overlay by hiding or showing a 1-pixel div over + * the canvas. + * + * @param {boolean} enable + */ +ink.SketchologyEngineWrapper.prototype.setHardwareOverlay = function(enable) { + document.querySelector('#ink-engine-hwoverlay').style.display = + enable ? 'none' : 'block'; +}; + + +/** + * Calls the given callback once all previous asynchronous engine operations + * have been applied. + * @param {!Function} callback + */ +ink.SketchologyEngineWrapper.prototype.flush = function(callback) { + var commandProto = new sketchology.proto.Command(); + var sp = new sketchology.proto.SequencePoint(); + sp.setId(this.nextSequencePointId_); + this.sequencePointCallbacks_[this.nextSequencePointId_++] = callback; + commandProto.setSequencePoint(sp); + this.handleCommand(commandProto); +};
diff --git a/third_party/ink/template/soy/soyutils_usegoog.js b/third_party/ink/template/soy/soyutils_usegoog.js new file mode 100644 index 0000000..8720f1ca --- /dev/null +++ b/third_party/ink/template/soy/soyutils_usegoog.js
@@ -0,0 +1,2401 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Utility functions and classes for Soy gencode + * + * <p> + * This file contains utilities that should only be called by Soy-generated + * JS code. Please do not use these functions directly from + * your hand-written code. Their names all start with '$$', or exist within the + * soydata.VERY_UNSAFE namespace. + * + * <p>TODO(lukes): ensure that the above pattern is actually followed + * consistently. + * + * @author Garrett Boyer + * @author Mike Samuel + * @author Kai Huang + * @author Aharon Lanin + */ +goog.provide('soy'); +goog.provide('soy.asserts'); +goog.provide('soy.esc'); +goog.provide('soydata'); +goog.provide('soydata.SanitizedHtml'); +goog.provide('soydata.VERY_UNSAFE'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.debug'); +goog.require('goog.format'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.html.uncheckedconversions'); +goog.require('goog.i18n.BidiFormatter'); +goog.require('goog.i18n.bidi'); +goog.require('goog.object'); +goog.require('goog.soy.data.SanitizedContent'); +goog.require('goog.soy.data.SanitizedContentKind'); +goog.require('goog.soy.data.SanitizedCss'); +goog.require('goog.soy.data.SanitizedHtml'); +goog.require('goog.soy.data.SanitizedHtmlAttribute'); +goog.require('goog.soy.data.SanitizedJs'); +goog.require('goog.soy.data.SanitizedStyle'); +goog.require('goog.soy.data.SanitizedTrustedResourceUri'); +goog.require('goog.soy.data.SanitizedUri'); +goog.require('goog.soy.data.UnsanitizedText'); +goog.require('goog.string'); +goog.require('goog.string.Const'); + +// ----------------------------------------------------------------------------- +// soydata: Defines typed strings, e.g. an HTML string {@code "a<b>c"} is +// semantically distinct from the plain text string {@code "a<b>c"} and smart +// templates can take that distinction into account. + +/** + * Checks whether a given value is of a given content kind. + * + * @param {*} value The value to be examined. + * @param {goog.soy.data.SanitizedContentKind} contentKind The desired content + * kind. + * @return {boolean} Whether the given value is of the given kind. + * @private + */ +soydata.isContentKind_ = function(value, contentKind) { + // TODO(aharon): This function should really include the assert on + // value.constructor that is currently sprinkled at most of the call sites. + // Unfortunately, that would require a (debug-mode-only) switch statement. + // TODO(aharon): Perhaps we should get rid of the contentKind property + // altogether and only at the constructor. + return value != null && value.contentKind === contentKind; +}; + + +/** + * Returns a given value's contentDir property, constrained to a + * goog.i18n.bidi.Dir value or null. Returns null if the value is null, + * undefined, a primitive or does not have a contentDir property, or the + * property's value is not 1 (for LTR), -1 (for RTL), or 0 (for neutral). + * + * @param {*} value The value whose contentDir property, if any, is to + * be returned. + * @return {?goog.i18n.bidi.Dir} The contentDir property. + */ +soydata.getContentDir = function(value) { + if (value != null) { + switch (value.contentDir) { + case goog.i18n.bidi.Dir.LTR: + return goog.i18n.bidi.Dir.LTR; + case goog.i18n.bidi.Dir.RTL: + return goog.i18n.bidi.Dir.RTL; + case goog.i18n.bidi.Dir.NEUTRAL: + return goog.i18n.bidi.Dir.NEUTRAL; + } + } + return null; +}; + + +/** + * This class is only a holder for {@code soydata.SanitizedHtml.from}. Do not + * instantiate or extend it. Use {@code goog.soy.data.SanitizedHtml} instead. + * + * @constructor + * @extends {goog.soy.data.SanitizedHtml} + * @abstract + */ +soydata.SanitizedHtml = function() { + soydata.SanitizedHtml.base(this, 'constructor'); // Throws an exception. +}; +goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedHtml); + +/** + * Returns a SanitizedHtml object for a particular value. The content direction + * is preserved. + * + * This HTML-escapes the value unless it is already SanitizedHtml or SafeHtml. + * + * @param {*} value The value to convert. If it is already a SanitizedHtml + * object, it is left alone. + * @return {!goog.soy.data.SanitizedHtml} A SanitizedHtml object derived from + * the stringified value. It is escaped unless the input is SanitizedHtml or + * SafeHtml. + */ +soydata.SanitizedHtml.from = function(value) { + // The check is soydata.isContentKind_() inlined for performance. + if (value != null && + value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return /** @type {!goog.soy.data.SanitizedHtml} */ (value); + } + if (value instanceof goog.html.SafeHtml) { + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + goog.html.SafeHtml.unwrap(value), value.getDirection()); + } + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + soy.esc.$$escapeHtmlHelper(String(value)), soydata.getContentDir(value)); +}; + + +/** + * Empty string, used as a type in Soy templates. + * @enum {string} + * @private + */ +soydata.$$EMPTY_STRING_ = { + VALUE: '' +}; + + +/** + * Creates a factory for SanitizedContent types. + * + * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can + * instantiate Sanitized* classes, without making the Sanitized* constructors + * publicly usable. Requiring all construction to use the VERY_UNSAFE names + * helps callers and their reviewers easily tell that creating SanitizedContent + * is not always safe and calls for careful review. + * + * @param {function(new: T)} ctor A constructor. + * @return {!function(*, ?goog.i18n.bidi.Dir=): T} A factory that takes + * content and an optional content direction and returns a new instance. If + * the content direction is undefined, ctor.prototype.contentDir is used. + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactory_ = function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If + * undefined, ctor.prototype.contentDir is used. + * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually + * of type T above (ctor's type, a descendant of SanitizedContent), but + * there is no way to express that here. + */ + function sanitizedContentFactory(content, opt_contentDir) { + var result = new InstantiableCtor(String(content)); + if (opt_contentDir !== undefined) { + result.contentDir = opt_contentDir; + } + return result; + } + return sanitizedContentFactory; +}; + + +/** + * Creates a factory for SanitizedContent types that should always have their + * default directionality. + * + * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can + * instantiate Sanitized* classes, without making the Sanitized* constructors + * publicly usable. Requiring all construction to use the VERY_UNSAFE names + * helps callers and their reviewers easily tell that creating SanitizedContent + * is not always safe and calls for careful review. + * + * @param {function(new: T, string)} ctor A constructor. + * @return {!function(*): T} A factory that takes content and returns a new + * instance (with default directionality, i.e. ctor.prototype.contentDir). + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_ = function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @return {!goog.soy.data.SanitizedContent} The new instance. It is actually + * of type T above (ctor's type, a descendant of SanitizedContent), but + * there is no way to express that here. + */ + function sanitizedContentFactory(content) { + var result = new InstantiableCtor(String(content)); + return result; + } + return sanitizedContentFactory; +}; + + +// ----------------------------------------------------------------------------- +// Sanitized content ordainers. Please use these with extreme caution (with the +// exception of markUnsanitizedText). A good recommendation is to limit usage +// of these to just a handful of files in your source tree where usages can be +// carefully audited. + + +/** + * Protects a string from being used in an noAutoescaped context. + * + * This is useful for content where there is significant risk of accidental + * unescaped usage in a Soy template. A great case is for user-controlled + * data that has historically been a source of vulernabilities. + * + * @param {*} content Text to protect. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.UnsanitizedText} A wrapper that is rejected by the + * Soy noAutoescape print directive. + */ +soydata.markUnsanitizedText = function(content, opt_contentDir) { + return new goog.soy.data.UnsanitizedText(content, opt_contentDir); +}; + + +/** + * Takes a leap of faith that the provided content is "safe" HTML. + * + * @param {*} content A string of HTML that can safely be embedded in + * a PCDATA context in your app. If you would be surprised to find that an + * HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs) + * and you wouldn't write a template that produces {@code s} on security or + * privacy grounds, then don't pass {@code s} here. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.SanitizedHtml} Sanitized content wrapper that + * indicates to Soy not to escape when printed as HTML. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtml = + soydata.$$makeSanitizedContentFactory_(goog.soy.data.SanitizedHtml); + + +/** + * Takes a leap of faith that the provided content is "safe" (non-attacker- + * controlled, XSS-free) Javascript. + * + * @param {*} content Javascript source that when evaluated does not + * execute any attacker-controlled scripts. + * @return {!goog.soy.data.SanitizedJs} Sanitized content wrapper that indicates + * to Soy not to escape when printed as Javascript source. + */ +soydata.VERY_UNSAFE.ordainSanitizedJs = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedJs); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as a URI + * in a Soy template. + * + * This creates a Soy SanitizedContent object which indicates to Soy there is + * no need to escape it when printed as a URI (e.g. in an href or src + * attribute), such as if it's already been encoded or if it's a Javascript: + * URI. + * + * @param {*} content A chunk of URI that the caller knows is safe to + * emit in a template. + * @return {!goog.soy.data.SanitizedUri} Sanitized content wrapper that + * indicates to Soy not to escape or filter when printed in URI context. + */ +soydata.VERY_UNSAFE.ordainSanitizedUri = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedUri); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as a + * TrustedResourceUri in a Soy template. + * + * This creates a Soy SanitizedContent object which indicates to Soy there is + * no need to filter it when printed as a TrustedResourceUri. + * + * @param {*} content A chunk of TrustedResourceUri such as that the caller + * knows is safe to emit in a template. + * @return {!goog.soy.data.SanitizedTrustedResourceUri} Sanitized content + * wrapper that indicates to Soy not to escape or filter when printed in + * TrustedResourceUri context. + */ +soydata.VERY_UNSAFE.ordainSanitizedTrustedResourceUri = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedTrustedResourceUri); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as an + * HTML attribute. + * + * @param {*} content An attribute name and value, such as + * {@code dir="ltr"}. + * @return {!goog.soy.data.SanitizedHtmlAttribute} Sanitized content wrapper + * that indicates to Soy not to escape when printed as an HTML attribute. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedHtmlAttribute); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as STYLE + * in a style attribute. + * + * @param {*} content CSS, such as {@code color:#c3d9ff}. + * @return {!goog.soy.data.SanitizedStyle} Sanitized style wrapper that + * indicates to Soy there is no need to escape or filter when printed in CSS + * context. + */ +soydata.VERY_UNSAFE.ordainSanitizedStyle = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedStyle); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as CSS + * in a style block. + * + * @param {*} content CSS, such as {@code color:#c3d9ff}. + * @return {!goog.soy.data.SanitizedCss} Sanitized CSS wrapper that indicates to + * Soy there is no need to escape or filter when printed in CSS context. + */ +soydata.VERY_UNSAFE.ordainSanitizedCss = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnly_( + goog.soy.data.SanitizedCss); + + +// ----------------------------------------------------------------------------- +// Soy-generated utilities in the soy namespace. Contains implementations for +// common soyfunctions (e.g. keys()) and escaping/print directives. + + +/** + * Whether the locale is right-to-left. + * + * @type {boolean} + */ +soy.$$IS_LOCALE_RTL = goog.i18n.bidi.IS_RTL; + + +/** + * Builds an augmented map. The returned map will contain mappings from both + * the base map and the additional map. If the same key appears in both, then + * the value from the additional map will be visible, while the value from the + * base map will be hidden. The base map will be used, but not modified. + * + * @param {!Object} baseMap The original map to augment. + * @param {!Object} additionalMap A map containing the additional mappings. + * @return {!Object} An augmented map containing both the original and + * additional mappings. + */ +soy.$$augmentMap = function(baseMap, additionalMap) { + return soy.$$assignDefaults(soy.$$assignDefaults({}, additionalMap), baseMap); +}; + + +/** + * Copies extra properties into an object if they do not already exist. The + * destination object is mutated in the process. + * + * @param {!Object} obj The destination object to update. + * @param {!Object} defaults An object with default properties to apply. + * @return {!Object} The destination object for convenience. + */ +soy.$$assignDefaults = function(obj, defaults) { + for (var key in defaults) { + if (!(key in obj)) { + obj[key] = defaults[key]; + } + } + + return obj; +}; + + +/** + * Checks that the given map key is a string. + * @param {*} key Key to check. + * @return {string} The given key. + */ +soy.$$checkMapKey = function(key) { + // TODO: Support map literal with nonstring key. + if ((typeof key) != 'string') { + throw Error( + 'Map literal\'s key expression must evaluate to string' + + ' (encountered type "' + (typeof key) + '").'); + } + return key; +}; + + +/** + * Gets the keys in a map as an array. There are no guarantees on the order. + * @param {Object} map The map to get the keys of. + * @return {!Array<string>} The array of keys in the given map. + */ +soy.$$getMapKeys = function(map) { + var mapKeys = []; + for (var key in map) { + mapKeys.push(key); + } + return mapKeys; +}; + + +/** + * Returns the argument if it is not null. + * + * @param {T} val The value to check + * @return {T} val if is isn't null + * @template T + */ +soy.$$checkNotNull = function(val) { + if (val == null) { + throw Error('unexpected null value'); + } + return val; +}; + + +/** + * Parses the given string into a base 10 integer. Returns null if parse is + * unsuccessful. + * @param {string} str The string to parse + * @return {?number} The string parsed as a base 10 integer, or null if + * unsuccessful + */ +soy.$$parseInt = function(str) { + var parsed = parseInt(str, 10); + return isNaN(parsed) ? null : parsed; +}; + + +/** + * Parses the given string into a float. Returns null if parse is unsuccessful. + * @param {string} str The string to parse + * @return {?number} The string parsed as a float, or null if unsuccessful. + */ +soy.$$parseFloat = function(str) { + var parsed = parseFloat(str); + return isNaN(parsed) ? null : parsed; +}; + + +/** + * Gets a consistent unique id for the given delegate template name. Two calls + * to this function will return the same id if and only if the input names are + * the same. + * + * <p> Important: This function must always be called with a string constant. + * + * <p> If Closure Compiler is not being used, then this is just this identity + * function. If Closure Compiler is being used, then each call to this function + * will be replaced with a short string constant, which will be consistent per + * input name. + * + * @param {string} delTemplateName The delegate template name for which to get a + * consistent unique id. + * @return {string} A unique id that is consistent per input name. + * + * @idGenerator {consistent} + */ +soy.$$getDelTemplateId = function(delTemplateName) { + return delTemplateName; +}; + + +/** + * Map from registered delegate template key to the priority of the + * implementation. + * @type {Object} + * @private + */ +soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {}; + +/** + * Map from registered delegate template key to the implementation function. + * @type {Object} + * @private + */ +soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {}; + + +/** + * Registers a delegate implementation. If the same delegate template key (id + * and variant) has been registered previously, then priority values are + * compared and only the higher priority implementation is stored (if + * priorities are equal, an error is thrown). + * + * @param {string} delTemplateId The delegate template id. + * @param {string} delTemplateVariant The delegate template variant (can be + * empty string). + * @param {number} delPriority The implementation's priority value. + * @param {Function} delFn The implementation function. + */ +soy.$$registerDelegateFn = function( + delTemplateId, delTemplateVariant, delPriority, delFn) { + + var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant; + var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey]; + if (currPriority === undefined || delPriority > currPriority) { + // Registering new or higher-priority function: replace registry entry. + soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority; + soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn; + } else if (delPriority == currPriority) { + // Registering same-priority function: error. + throw Error( + 'Encountered two active delegates with the same priority ("' + + delTemplateId + ':' + delTemplateVariant + '").'); + } else { + // Registering lower-priority function: do nothing. + } +}; + + +/** + * Retrieves the (highest-priority) implementation that has been registered for + * a given delegate template key (id and variant). If no implementation has + * been registered for the key, then the fallback is the same id with empty + * variant. If the fallback is also not registered, and allowsEmptyDefault is + * true, then returns an implementation that is equivalent to an empty template + * (i.e. rendered output would be empty string). + * + * @param {string} delTemplateId The delegate template id. + * @param {string} delTemplateVariant The delegate template variant (can be + * empty string). + * @param {boolean} allowsEmptyDefault Whether to default to the empty template + * function if there's no active implementation. + * @return {Function} The retrieved implementation function. + */ +soy.$$getDelegateFn = function( + delTemplateId, delTemplateVariant, allowsEmptyDefault) { + + var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_[ + 'key_' + delTemplateId + ':' + delTemplateVariant]; + if (! delFn && delTemplateVariant != '') { + // Fallback to empty variant. + delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':']; + } + + if (delFn) { + return delFn; + } else if (allowsEmptyDefault) { + return soy.$$EMPTY_TEMPLATE_FN_; + } else { + throw Error( + 'Found no active impl for delegate call to "' + delTemplateId + ':' + + delTemplateVariant + '" (and not allowemptydefault="true").'); + } +}; + + +/** + * Private helper soy.$$getDelegateFn(). This is the empty template function + * that is returned whenever there's no delegate implementation found. + * + * @param {Object<string, *>=} opt_data + * @param {Object<string, *>=} opt_ijData + * @param {Object<string, *>=} opt_ijData_deprecated TODO(b/36644846): remove + * @return {string} + * @private + */ +soy.$$EMPTY_TEMPLATE_FN_ = function( + opt_data, opt_ijData, opt_ijData_deprecated) { + return ''; +}; + + +// ----------------------------------------------------------------------------- +// Internal sanitized content wrappers. + + +/** + * Creates a SanitizedContent factory for SanitizedContent types for internal + * Soy let and param blocks. + * + * This is a hack within Soy so that SanitizedContent objects created via let + * and param blocks will truth-test as false if they are empty string. + * Tricking the Javascript runtime to treat empty SanitizedContent as falsey is + * not possible, and changing the Soy compiler to wrap every boolean statement + * for just this purpose is impractical. Instead, we just avoid wrapping empty + * string as SanitizedContent, since it's a no-op for empty strings anyways. + * + * @param {function(new: T)} ctor A constructor. + * @return {!function(*, ?goog.i18n.bidi.Dir=): (T|soydata.$$EMPTY_STRING_)} + * A factory that takes content and an optional content direction and + * returns a new instance, or an empty string. If the content direction is + * undefined, ctor.prototype.contentDir is used. + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactoryForInternalBlocks_ = function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction. If + * undefined, ctor.prototype.contentDir is used. + * @return {!goog.soy.data.SanitizedContent|soydata.$$EMPTY_STRING_} The new + * instance, or an empty string. A new instance is actually of type T + * above (ctor's type, a descendant of SanitizedContent), but there's no + * way to express that here. + */ + function sanitizedContentFactory(content, opt_contentDir) { + var contentString = String(content); + if (!contentString) { + return soydata.$$EMPTY_STRING_.VALUE; + } + var result = new InstantiableCtor(contentString); + if (opt_contentDir !== undefined) { + result.contentDir = opt_contentDir; + } + return result; + } + return sanitizedContentFactory; +}; + + +/** + * Creates a SanitizedContent factory for SanitizedContent types that should + * always have their default directionality for internal Soy let and param + * blocks. + * + * This is a hack within Soy so that SanitizedContent objects created via let + * and param blocks will truth-test as false if they are empty string. + * Tricking the Javascript runtime to treat empty SanitizedContent as falsey is + * not possible, and changing the Soy compiler to wrap every boolean statement + * for just this purpose is impractical. Instead, we just avoid wrapping empty + * string as SanitizedContent, since it's a no-op for empty strings anyways. + * + * @param {function(new: T)} ctor A constructor. + * @return {!function(*): (T|soydata.$$EMPTY_STRING_)} A + * factory that takes content and returns a + * new instance (with default directionality, i.e. + * ctor.prototype.contentDir), or an empty string. + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_ = + function(ctor) { + /** + * @param {string} content + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ + function InstantiableCtor(content) { + /** @override */ + this.content = content; + } + InstantiableCtor.prototype = ctor.prototype; + /** + * Creates a ctor-type SanitizedContent instance. + * + * @param {*} content The content to put in the instance. + * @return {!goog.soy.data.SanitizedContent|soydata.$$EMPTY_STRING_} The new + * instance, or an empty string. A new instance is actually of type T + * above (ctor's type, a descendant of SanitizedContent), but there's no + * way to express that here. + */ + function sanitizedContentFactory(content) { + var contentString = String(content); + if (!contentString) { + return soydata.$$EMPTY_STRING_.VALUE; + } + var result = new InstantiableCtor(contentString); + return result; + } + return sanitizedContentFactory; +}; + + +/** + * Creates kind="text" block contents (internal use only). + * + * @param {*} content Text. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.UnsanitizedText|soydata.$$EMPTY_STRING_} Wrapped result. + */ +soydata.$$markUnsanitizedTextForInternalBlocks = function( + content, opt_contentDir) { + var contentString = String(content); + if (!contentString) { + return soydata.$$EMPTY_STRING_.VALUE; + } + return new goog.soy.data.UnsanitizedText(contentString, opt_contentDir); +}; + + +/** + * Creates kind="html" block contents (internal use only). + * + * @param {*} content Text. + * @param {?goog.i18n.bidi.Dir=} opt_contentDir The content direction; null if + * unknown and thus to be estimated when necessary. Default: null. + * @return {!goog.soy.data.SanitizedHtml|soydata.$$EMPTY_STRING_} Wrapped + * result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedHtmlForInternalBlocks = + soydata.$$makeSanitizedContentFactoryForInternalBlocks_( + goog.soy.data.SanitizedHtml); + + +/** + * Creates kind="js" block contents (internal use only). + * + * @param {*} content Text. + * @return {!goog.soy.data.SanitizedJs|soydata.$$EMPTY_STRING_} Wrapped result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedJsForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedJs); + + +/** + * Creates kind="trustedResourceUri" block contents (internal use only). + * + * @param {*} content Text. + * @return {goog.soy.data.SanitizedTrustedResourceUri|soydata.$$EMPTY_STRING_} + * Wrapped result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedTrustedResourceUriForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedTrustedResourceUri); + + +/** + * Creates kind="uri" block contents (internal use only). + * + * @param {*} content Text. + * @return {goog.soy.data.SanitizedUri|soydata.$$EMPTY_STRING_} Wrapped result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedUriForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedUri); + + +/** + * Creates kind="attributes" block contents (internal use only). + * + * @param {*} content Text. + * @return {goog.soy.data.SanitizedHtmlAttribute|soydata.$$EMPTY_STRING_} + * Wrapped result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedAttributesForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedHtmlAttribute); + + +/** + * Creates kind="style" block contents (internal use only). + * + * @param {*} content Text. + * @return {goog.soy.data.SanitizedStyle|soydata.$$EMPTY_STRING_} Wrapped + * result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedStyleForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedStyle); + + +/** + * Creates kind="css" block contents (internal use only). + * + * @param {*} content Text. + * @return {goog.soy.data.SanitizedCss|soydata.$$EMPTY_STRING_} Wrapped result. + */ +soydata.VERY_UNSAFE.$$ordainSanitizedCssForInternalBlocks = + soydata.$$makeSanitizedContentFactoryWithDefaultDirOnlyForInternalBlocks_( + goog.soy.data.SanitizedCss); + + +// ----------------------------------------------------------------------------- +// Escape/filter/normalize. + + +/** + * Returns a SanitizedHtml object for a particular value. The content direction + * is preserved. + * + * This HTML-escapes the value unless it is already SanitizedHtml. Escapes + * double quote '"' in addition to '&', '<', and '>' so that a string can be + * included in an HTML tag attribute value within double quotes. + * + * @param {*} value The value to convert. If it is already a SanitizedHtml + * object, it is left alone. + * @return {!goog.soy.data.SanitizedHtml} An escaped version of value. + */ +soy.$$escapeHtml = function(value) { + return soydata.SanitizedHtml.from(value); +}; + + +/** + * Strips unsafe tags to convert a string of untrusted HTML into HTML that + * is safe to embed. The content direction is preserved. + * + * @param {?} value The string-like value to be escaped. May not be a string, + * but the value will be coerced to a string. + * @param {Array<string>=} opt_safeTags Additional tag names to whitelist. + * @return {!goog.soy.data.SanitizedHtml} A sanitized and normalized version of + * value. + */ +soy.$$cleanHtml = function(value, opt_safeTags) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return /** @type {!goog.soy.data.SanitizedHtml} */ (value); + } + var tagWhitelist; + if (opt_safeTags) { + tagWhitelist = goog.object.createSet(opt_safeTags); + goog.object.extend(tagWhitelist, soy.esc.$$SAFE_TAG_WHITELIST_); + } else { + tagWhitelist = soy.esc.$$SAFE_TAG_WHITELIST_; + } + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + soy.$$stripHtmlTags(value, tagWhitelist), soydata.getContentDir(value)); +}; + + +/** + * Escapes HTML, except preserves entities. + * + * Used mainly internally for escaping message strings in attribute and rcdata + * context, where we explicitly want to preserve any existing entities. + * + * @param {*} value Value to normalize. + * @return {string} A value safe to insert in HTML without any quotes or angle + * brackets. + */ +soy.$$normalizeHtml = function(value) { + return soy.esc.$$normalizeHtmlHelper(value); +}; + + +/** + * Escapes HTML special characters in a string so that it can be embedded in + * RCDATA. + * <p> + * Escapes HTML special characters so that the value will not prematurely end + * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags + * cannot contain other HTML entities, so it is not strictly necessary to escape + * HTML special characters except when part of that text looks like an HTML + * entity or like a close tag : {@code </textarea>}. + * <p> + * Will normalize known safe HTML to make sure that sanitized HTML (which could + * contain an innocuous {@code </textarea>} don't prematurely end an RCDATA + * element. + * + * @param {?} value The string-like value to be escaped. May not be a string, + * but the value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlRcdata = function(value) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return soy.esc.$$normalizeHtmlHelper(value.getContent()); + } + return soy.esc.$$escapeHtmlHelper(value); +}; + + +/** + * Matches any/only HTML5 void elements' start tags. + * See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements + * @type {RegExp} + * @private + */ +soy.$$HTML5_VOID_ELEMENTS_ = new RegExp( + '^<(?:area|base|br|col|command|embed|hr|img|input' + + '|keygen|link|meta|param|source|track|wbr)\\b'); + + +/** + * Removes HTML tags from a string of known safe HTML. + * If opt_tagWhitelist is not specified or is empty, then + * the result can be used as an attribute value. + * + * @param {*} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @param {Object<string, boolean>=} opt_tagWhitelist Has an own property whose + * name is a lower-case tag name and whose value is {@code 1} for + * each element that is allowed in the output. + * @return {string} A representation of value without disallowed tags, + * HTML comments, or other non-text content. + */ +soy.$$stripHtmlTags = function(value, opt_tagWhitelist) { + if (!opt_tagWhitelist) { + // If we have no white-list, then use a fast track which elides all tags. + return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '') + // This is just paranoia since callers should normalize the result + // anyway, but if they didn't, it would be necessary to ensure that + // after the first replace non-tag uses of < do not recombine into + // tags as in "<<foo>script>alert(1337)</<foo>script>". + .replace(soy.esc.$$LT_REGEX_, '<'); + } + + // Escapes '[' so that we can use [123] below to mark places where tags + // have been removed. + var html = String(value).replace(/\[/g, '['); + + // Consider all uses of '<' and replace whitelisted tags with markers like + // [1] which are indices into a list of approved tag names. + // Replace all other uses of < and > with entities. + var tags = []; + var attrs = []; + html = html.replace( + soy.esc.$$HTML_TAG_REGEX_, + function(tok, tagName) { + if (tagName) { + tagName = tagName.toLowerCase(); + if (opt_tagWhitelist.hasOwnProperty(tagName) && + opt_tagWhitelist[tagName]) { + var isClose = tok.charAt(1) == '/'; + var index = tags.length; + var start = '</'; + var attributes = ''; + if (!isClose) { + start = '<'; + var match; + while ((match = soy.esc.$$HTML_ATTRIBUTE_REGEX_.exec(tok))) { + if (match[1] && match[1].toLowerCase() == 'dir') { + var dir = match[2]; + if (dir) { + if (dir.charAt(0) == '\'' || dir.charAt(0) == '"') { + dir = dir.substr(1, dir.length - 2); + } + dir = dir.toLowerCase(); + if (dir == 'ltr' || dir == 'rtl' || dir == 'auto') { + attributes = ' dir="' + dir + '"'; + } + } + break; + } + } + soy.esc.$$HTML_ATTRIBUTE_REGEX_.lastIndex = 0; + } + tags[index] = start + tagName + '>'; + attrs[index] = attributes; + return '[' + index + ']'; + } + } + return ''; + }); + + // Escape HTML special characters. Now there are no '<' in html that could + // start a tag. + html = soy.esc.$$normalizeHtmlHelper(html); + + var finalCloseTags = soy.$$balanceTags_(tags); + + // Now html contains no tags or less-than characters that could become + // part of a tag via a replacement operation and tags only contains + // approved tags. + // Reinsert the white-listed tags. + html = html.replace(/\[(\d+)\]/g, function(_, index) { + if (attrs[index] && tags[index]) { + return tags[index].substr(0, tags[index].length - 1) + attrs[index] + '>'; + } + return tags[index]; + }); + + // Close any still open tags. + // This prevents unclosed formatting elements like <ol> and <table> from + // breaking the layout of containing HTML. + return html + finalCloseTags; +}; + + +/** + * Make sure that tag boundaries are not broken by Safe CSS when embedded in a + * {@code <style>} element. + * @param {string} css + * @return {string} + * @private + */ +soy.$$embedCssIntoHtml_ = function(css) { + // Port of a method of the same name in + // com.google.template.soy.shared.restricted.Sanitizers + return css.replace(/<\//g, '<\\/').replace(/\]\]>/g, ']]\\>'); +}; + + +/** + * Throw out any close tags that don't correspond to start tags. + * If {@code <table>} is used for formatting, embedded HTML shouldn't be able + * to use a mismatched {@code </table>} to break page layout. + * + * @param {Array<string>} tags Array of open/close tags (e.g. '<p>', '</p>') + * that will be modified in place to be either an open tag, one or more close + * tags concatenated, or the empty string. + * @return {string} zero or more closed tags that close all elements that are + * opened in tags but not closed. + * @private + */ +soy.$$balanceTags_ = function(tags) { + var open = []; + for (var i = 0, n = tags.length; i < n; ++i) { + var tag = tags[i]; + if (tag.charAt(1) == '/') { + var openTagIndex = goog.array.lastIndexOf(open, tag); + if (openTagIndex < 0) { + tags[i] = ''; // Drop close tag with no corresponding open tag. + } else { + tags[i] = open.slice(openTagIndex).reverse().join(''); + open.length = openTagIndex; + } + } else if (tag == '<li>' && + goog.array.lastIndexOf(open, '</ol>') < 0 && + goog.array.lastIndexOf(open, '</ul>') < 0) { + // Drop <li> if it isn't nested in a parent <ol> or <ul>. + tags[i] = ''; + } else if (!soy.$$HTML5_VOID_ELEMENTS_.test(tag)) { + open.push('</' + tag.substring(1)); + } + } + return open.reverse().join(''); +}; + + +/** + * Escapes HTML special characters in an HTML attribute value. + * + * @param {?} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlAttribute = function(value) { + // NOTE: We don't accept ATTRIBUTES here because ATTRIBUTES is actually not + // the attribute value context, but instead k/v pairs. + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + // NOTE: After removing tags, we also escape quotes ("normalize") so that + // the HTML can be embedded in attribute context. + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return soy.esc.$$normalizeHtmlHelper( + soy.$$stripHtmlTags(value.getContent())); + } + return soy.esc.$$escapeHtmlHelper(value); +}; + + +/** + * Escapes HTML special characters in a string including space and other + * characters that can end an unquoted HTML attribute value. + * + * @param {?} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlAttributeNospace = function(value) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedHtml); + return soy.esc.$$normalizeHtmlNospaceHelper( + soy.$$stripHtmlTags(value.getContent())); + } + return soy.esc.$$escapeHtmlNospaceHelper(value); +}; + + +/** + * Filters out strings that cannot be a substring of a valid HTML attribute. + * + * Note the input is expected to be key=value pairs. + * + * @param {?} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A valid HTML attribute name part or name/value pair. + * {@code "zSoyz"} if the input is invalid. + */ +soy.$$filterHtmlAttributes = function(value) { + // NOTE: Explicitly no support for SanitizedContentKind.HTML, since that is + // meaningless in this context, which is generally *between* html attributes. + if (soydata.isContentKind_( + value, goog.soy.data.SanitizedContentKind.ATTRIBUTES)) { + goog.asserts.assert( + value.constructor === goog.soy.data.SanitizedHtmlAttribute); + // Add a space at the end to ensure this won't get merged into following + // attributes, unless the interpretation is unambiguous (ending with quotes + // or a space). + return value.getContent().replace(/([^"'\s])$/, '$1 '); + } + // TODO: Dynamically inserting attributes that aren't marked as trusted is + // probably unnecessary. Any filtering done here will either be inadequate + // for security or not flexible enough. Having clients use kind="attributes" + // in parameters seems like a wiser idea. + return soy.esc.$$filterHtmlAttributesHelper(value); +}; + + +/** + * Filters out strings that cannot be a substring of a valid HTML element name. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A valid HTML element name part. + * {@code "zSoyz"} if the input is invalid. + */ +soy.$$filterHtmlElementName = function(value) { + // NOTE: We don't accept any SanitizedContent here. HTML indicates valid + // PCDATA, not tag names. A sloppy developer shouldn't be able to cause an + // exploit: + // ... {let userInput}script src=http://evil.com/evil.js{/let} ... + // ... {param tagName kind="html"}{$userInput}{/param} ... + // ... <{$tagName}>Hello World</{$tagName}> + return soy.esc.$$filterHtmlElementNameHelper(value); +}; + + +/** + * Escapes characters in the value to make it valid content for a JS string + * literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeJsString = function(value) { + return soy.esc.$$escapeJsStringHelper(value); +}; + + +/** + * Encodes a value as a JavaScript literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A JavaScript code representation of the input. + */ +soy.$$escapeJsValue = function(value) { + // We surround values with spaces so that they can't be interpolated into + // identifiers by accident. + // We could use parentheses but those might be interpreted as a function call. + if (value == null) { // Intentionally matches undefined. + // Java returns null from maps where there is no corresponding key while + // JS returns undefined. + // We always output null for compatibility with Java which does not have a + // distinct undefined value. + return ' null '; + } + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.JS)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedJs); + return value.getContent(); + } + if (value instanceof goog.html.SafeScript) { + return goog.html.SafeScript.unwrap(value); + } + switch (typeof value) { + case 'boolean': case 'number': + return ' ' + value + ' '; + default: + return "'" + soy.esc.$$escapeJsStringHelper(String(value)) + "'"; + } +}; + + +/** + * Escapes characters in the string to make it valid content for a JS regular + * expression literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeJsRegex = function(value) { + return soy.esc.$$escapeJsRegexHelper(value); +}; + + +/** + * Matches all URI mark characters that conflict with HTML attribute delimiters + * or that cannot appear in a CSS uri. + * From <a href="http://www.w3.org/TR/CSS2/grammar.html">G.2: CSS grammar</a> + * <pre> + * url ([!#$%&*-~]|{nonascii}|{escape})* + * </pre> + * + * @type {RegExp} + * @private + */ +soy.$$problematicUriMarks_ = /['()]/g; + +/** + * @param {string} ch A single character in {@link soy.$$problematicUriMarks_}. + * @return {string} + * @private + */ +soy.$$pctEncode_ = function(ch) { + return '%' + ch.charCodeAt(0).toString(16); +}; + +/** + * Escapes a string so that it can be safely included in a URI. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeUri = function(value) { + // NOTE: We don't check for SanitizedUri or SafeUri, because just because + // something is already a valid complete URL doesn't mean we don't want to + // encode it as a component. For example, it would be bad if + // ?redirect={$url} didn't escape ampersands, because in that template, the + // continue URL should be treated as a single unit. + + // Apostophes and parentheses are not matched by encodeURIComponent. + // They are technically special in URIs, but only appear in the obsolete mark + // production in Appendix D.2 of RFC 3986, so can be encoded without changing + // semantics. + var encoded = soy.esc.$$escapeUriHelper(value); + soy.$$problematicUriMarks_.lastIndex = 0; + if (soy.$$problematicUriMarks_.test(encoded)) { + return encoded.replace(soy.$$problematicUriMarks_, soy.$$pctEncode_); + } + return encoded; +}; + + +/** + * Removes rough edges from a URI by escaping any raw HTML/JS string delimiters. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$normalizeUri = function(value) { + return soy.esc.$$normalizeUriHelper(value); +}; + + +/** + * Vets a URI's protocol and removes rough edges from a URI by escaping + * any raw HTML/JS string delimiters. + * + * @param {?} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$filterNormalizeUri = function(value) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.URI)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedUri); + return soy.$$normalizeUri(value); + } + if (soydata.isContentKind_(value, + goog.soy.data.SanitizedContentKind.TRUSTED_RESOURCE_URI)) { + goog.asserts.assert( + value.constructor === goog.soy.data.SanitizedTrustedResourceUri); + return soy.$$normalizeUri(value); + } + if (value instanceof goog.html.SafeUrl) { + return soy.$$normalizeUri(goog.html.SafeUrl.unwrap(value)); + } + if (value instanceof goog.html.TrustedResourceUrl) { + return soy.$$normalizeUri(goog.html.TrustedResourceUrl.unwrap(value)); + } + return soy.esc.$$filterNormalizeUriHelper(value); +}; + + +/** + * Vets a URI for usage as an image source. + * + * @param {?} value The value to filter. Might not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$filterNormalizeMediaUri = function(value) { + // Image URIs are filtered strictly more loosely than other types of URIs. + // TODO(shwetakarwa): Add tests for this in soyutils_test_helper while adding + // tests for filterTrustedResourceUri. + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.URI)) { + goog.asserts.assert(value.constructor === goog.soy.data.SanitizedUri); + return soy.$$normalizeUri(value); + } + if (soydata.isContentKind_(value, + goog.soy.data.SanitizedContentKind.TRUSTED_RESOURCE_URI)) { + goog.asserts.assert( + value.constructor === goog.soy.data.SanitizedTrustedResourceUri); + return soy.$$normalizeUri(value); + } + if (value instanceof goog.html.SafeUrl) { + return soy.$$normalizeUri(goog.html.SafeUrl.unwrap(value)); + } + if (value instanceof goog.html.TrustedResourceUrl) { + return soy.$$normalizeUri(goog.html.TrustedResourceUrl.unwrap(value)); + } + return soy.esc.$$filterNormalizeMediaUriHelper(value); +}; + + +/** + * Vets a URI for usage as a resource. Makes sure the input value is a compile + * time constant or a TrustedResouce not in attacker's control. + * + * @param {?} value The value to filter. + * @return {string} The value content. + */ +soy.$$filterTrustedResourceUri = function(value) { + if (soydata.isContentKind_(value, + goog.soy.data.SanitizedContentKind.TRUSTED_RESOURCE_URI)) { + goog.asserts.assert( + value.constructor === goog.soy.data.SanitizedTrustedResourceUri); + return value.getContent(); + } + if (value instanceof goog.html.TrustedResourceUrl) { + return goog.html.TrustedResourceUrl.unwrap(value); + } + goog.asserts.fail('Bad value `%s` for |filterTrustedResourceUri', + [String(value)]); + return 'about:invalid#zSoyz'; +}; + + +/** + * For any resource string/variable which has + * |blessStringAsTrustedResuorceUrlForLegacy directive return the value as is. + * + * @param {*} value The value to be blessed. Might not be a string + * @return {*} value Return current value. + */ +soy.$$blessStringAsTrustedResourceUrlForLegacy = function(value) { + return value; +}; + + +/** + * Allows only data-protocol image URI's. + * + * @param {*} value The value to process. May not be a string, but the value + * will be coerced to a string. + * @return {!goog.soy.data.SanitizedUri} An escaped version of value. + */ +soy.$$filterImageDataUri = function(value) { + // NOTE: Even if it's a SanitizedUri, we will still filter it. + return soydata.VERY_UNSAFE.ordainSanitizedUri( + soy.esc.$$filterImageDataUriHelper(value)); +}; + + +/** + * Allows only tel URIs. + * + * @param {*} value The value to process. May not be a string, but the value + * will be coerced to a string. + * @return {!goog.soy.data.SanitizedUri} An escaped version of value. + */ +soy.$$filterTelUri = function(value) { + // NOTE: Even if it's a SanitizedUri, we will still filter it. + return soydata.VERY_UNSAFE.ordainSanitizedUri( + soy.esc.$$filterTelUriHelper(value)); +}; + + +/** + * Escapes a string so it can safely be included inside a quoted CSS string. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeCssString = function(value) { + return soy.esc.$$escapeCssStringHelper(value); +}; + + +/** + * Encodes a value as a CSS identifier part, keyword, or quantity. + * + * @param {?} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A safe CSS identifier part, keyword, or quanitity. + */ +soy.$$filterCssValue = function(value) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.CSS)) { + goog.asserts.assertInstanceof(value, goog.soy.data.SanitizedCss); + return soy.$$embedCssIntoHtml_(value.getContent()); + } + // Uses == to intentionally match null and undefined for Java compatibility. + if (value == null) { + return ''; + } + if (value instanceof goog.html.SafeStyle) { + return soy.$$embedCssIntoHtml_(goog.html.SafeStyle.unwrap(value)); + } + // Note: SoyToJsSrcCompiler uses soy.$$filterCssValue both for the contents of + // <style> (list of rules) and for the contents of style="" (one set of + // declarations). We support SafeStyleSheet here to be used inside <style> but + // it also wrongly allows it inside style="". We should instead change + // SoyToJsSrcCompiler to use a different function inside <style>. + if (value instanceof goog.html.SafeStyleSheet) { + return soy.$$embedCssIntoHtml_(goog.html.SafeStyleSheet.unwrap(value)); + } + return soy.esc.$$filterCssValueHelper(value); +}; + + +/** + * Sanity-checks noAutoescape input for explicitly tainted content. + * + * SanitizedContentKind.TEXT is used to explicitly mark input that was never + * meant to be used unescaped. + * + * @param {?} value The value to filter. + * @return {*} The value, that we dearly hope will not cause an attack. + */ +soy.$$filterNoAutoescape = function(value) { + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.TEXT)) { + // Fail in development mode. + goog.asserts.fail( + 'Tainted SanitizedContentKind.TEXT for |noAutoescape: `%s`', + [value.getContent()]); + // Return innocuous data in production. + return 'zSoyz'; + } + + return value; +}; + + +// ----------------------------------------------------------------------------- +// Basic directives/functions. + + +/** + * Converts \r\n, \r, and \n to <br>s + * @param {*} value The string in which to convert newlines. + * @return {string|!goog.soy.data.SanitizedHtml} A copy of {@code value} with + * converted newlines. If {@code value} is SanitizedHtml, the return value + * is also SanitizedHtml, of the same known directionality. + */ +soy.$$changeNewlineToBr = function(value) { + var result = goog.string.newLineToBr(String(value), false); + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + result, soydata.getContentDir(value)); + } + return result; +}; + + +/** + * Inserts word breaks ('wbr' tags) into a HTML string at a given interval. The + * counter is reset if a space is encountered. Word breaks aren't inserted into + * HTML tags or entities. Entites count towards the character count; HTML tags + * do not. + * + * @param {*} value The HTML string to insert word breaks into. Can be other + * types, but the value will be coerced to a string. + * @param {number} maxCharsBetweenWordBreaks Maximum number of non-space + * characters to allow before adding a word break. + * @return {string|!goog.soy.data.SanitizedHtml} The string including word + * breaks. If {@code value} is SanitizedHtml, the return value + * is also SanitizedHtml, of the same known directionality. + * @deprecated The |insertWordBreaks directive is deprecated. + * Prefer wrapping with CSS white-space: break-word. + */ +soy.$$insertWordBreaks = function(value, maxCharsBetweenWordBreaks) { + var result = goog.format.insertWordBreaks( + String(value), maxCharsBetweenWordBreaks); + if (soydata.isContentKind_(value, goog.soy.data.SanitizedContentKind.HTML)) { + return soydata.VERY_UNSAFE.ordainSanitizedHtml( + result, soydata.getContentDir(value)); + } + return result; +}; + + +/** + * Truncates a string to a given max length (if it's currently longer), + * optionally adding ellipsis at the end. + * + * @param {*} str The string to truncate. Can be other types, but the value will + * be coerced to a string. + * @param {number} maxLen The maximum length of the string after truncation + * (including ellipsis, if applicable). + * @param {boolean} doAddEllipsis Whether to add ellipsis if the string needs + * truncation. + * @return {string} The string after truncation. + */ +soy.$$truncate = function(str, maxLen, doAddEllipsis) { + + str = String(str); + if (str.length <= maxLen) { + return str; // no need to truncate + } + + // If doAddEllipsis, either reduce maxLen to compensate, or else if maxLen is + // too small, just turn off doAddEllipsis. + if (doAddEllipsis) { + if (maxLen > 3) { + maxLen -= 3; + } else { + doAddEllipsis = false; + } + } + + // Make sure truncating at maxLen doesn't cut up a unicode surrogate pair. + if (soy.$$isHighSurrogate_(str.charCodeAt(maxLen - 1)) && + soy.$$isLowSurrogate_(str.charCodeAt(maxLen))) { + maxLen -= 1; + } + + // Truncate. + str = str.substring(0, maxLen); + + // Add ellipsis. + if (doAddEllipsis) { + str += '...'; + } + + return str; +}; + +/** + * Private helper for $$truncate() to check whether a char is a high surrogate. + * @param {number} cc The codepoint to check. + * @return {boolean} Whether the given codepoint is a unicode high surrogate. + * @private + */ +soy.$$isHighSurrogate_ = function(cc) { + return 0xD800 <= cc && cc <= 0xDBFF; +}; + +/** + * Private helper for $$truncate() to check whether a char is a low surrogate. + * @param {number} cc The codepoint to check. + * @return {boolean} Whether the given codepoint is a unicode low surrogate. + * @private + */ +soy.$$isLowSurrogate_ = function(cc) { + return 0xDC00 <= cc && cc <= 0xDFFF; +}; + + +// ----------------------------------------------------------------------------- +// Bidi directives/functions. + + +/** + * Cache of bidi formatter by context directionality, so we don't keep on + * creating new objects. + * @type {!Object<!goog.i18n.BidiFormatter>} + * @private + */ +soy.$$bidiFormatterCache_ = {}; + + +/** + * Returns cached bidi formatter for bidiGlobalDir, or creates a new one. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @return {!goog.i18n.BidiFormatter} A formatter for bidiGlobalDir. + * @private + */ +soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) { + return soy.$$bidiFormatterCache_[bidiGlobalDir] || + (soy.$$bidiFormatterCache_[bidiGlobalDir] = + new goog.i18n.BidiFormatter(bidiGlobalDir)); +}; + + +/** + * Estimate the overall directionality of text. If opt_isHtml, makes sure to + * ignore the LTR nature of the mark-up and escapes in text, making the logic + * suitable for HTML and HTML-escaped text. + * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of + * estimating the directionality. + * + * @param {*} text The content whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {number} 1 if text is LTR, -1 if it is RTL, and 0 if it is neutral. + */ +soy.$$bidiTextDir = function(text, opt_isHtml) { + var contentDir = soydata.getContentDir(text); + if (contentDir != null) { + return contentDir; + } + var isHtml = opt_isHtml || + soydata.isContentKind_(text, goog.soy.data.SanitizedContentKind.HTML); + return goog.i18n.bidi.estimateDirection(text + '', isHtml); +}; + + +/** + * Returns 'dir="ltr"' or 'dir="rtl"', depending on text's estimated + * directionality, if it is not the same as bidiGlobalDir. + * Otherwise, returns the empty string. + * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes + * in text, making the logic suitable for HTML and HTML-escaped text. + * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of + * estimating the directionality. + * + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} text The content whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {!goog.soy.data.SanitizedHtmlAttribute} 'dir="rtl"' for RTL text in + * non-RTL context; 'dir="ltr"' for LTR text in non-LTR context; + * else, the empty string. + */ +soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + var contentDir = soydata.getContentDir(text); + if (contentDir == null) { + var isHtml = opt_isHtml || + soydata.isContentKind_(text, goog.soy.data.SanitizedContentKind.HTML); + contentDir = goog.i18n.bidi.estimateDirection(text + '', isHtml); + } + return soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute( + formatter.knownDirAttr(contentDir)); +}; + + +/** + * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the + * directionality or the exit directionality of text are opposite to + * bidiGlobalDir. Otherwise returns the empty string. + * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes + * in text, making the logic suitable for HTML and HTML-escaped text. + * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of + * estimating the directionality. + * + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} text The content whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty + * string when text's overall and exit directionalities both match + * bidiGlobalDir, or bidiGlobalDir is 0 (unknown). + */ +soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + var isHtml = opt_isHtml || + soydata.isContentKind_(text, goog.soy.data.SanitizedContentKind.HTML); + return formatter.markAfterKnownDir(soydata.getContentDir(text), text + '', + isHtml); +}; + + +/** + * Returns text wrapped in a <span dir="ltr|rtl"> according to its + * directionality - but only if that is neither neutral nor the same as the + * global context. Otherwise, returns text unchanged. + * Always treats text as HTML/HTML-escaped, i.e. ignores mark-up and escapes + * when estimating text's directionality. + * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of + * estimating the directionality. + * + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} text The string to be wrapped. Can be other types, but the value + * will be coerced to a string. + * @return {!goog.soy.data.SanitizedContent|string} The wrapped text. + */ +soy.$$bidiSpanWrap = function(bidiGlobalDir, text) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + + // We always treat the value as HTML, because span-wrapping is only useful + // when its output will be treated as HTML (without escaping), and because + // |bidiSpanWrap is not itself specified to do HTML escaping in Soy. (Both + // explicit and automatic HTML escaping, if any, is done before calling + // |bidiSpanWrap because the BidiSpanWrapDirective Java class implements + // SanitizedContentOperator, but this does not mean that the input has to be + // HTML SanitizedContent. In legacy usage, a string that is not + // SanitizedContent is often printed in an autoescape="false" template or by + // a print with a |noAutoescape, in which case our input is just SoyData.) If + // the output will be treated as HTML, the input had better be safe + // HTML/HTML-escaped (even if it isn't HTML SanitizedData), or we have an XSS + // opportunity and a much bigger problem than bidi garbling. + var html = goog.html.uncheckedconversions. + safeHtmlFromStringKnownToSatisfyTypeContract( + goog.string.Const.from( + 'Soy |bidiSpanWrap is applied on an autoescaped text.'), + String(text)); + var wrappedHtml = formatter.spanWrapSafeHtmlWithKnownDir( + soydata.getContentDir(text), html); + + // Like other directives whose Java class implements SanitizedContentOperator, + // |bidiSpanWrap is called after the escaping (if any) has already been done, + // and thus there is no need for it to produce actual SanitizedContent. + return goog.html.SafeHtml.unwrap(wrappedHtml); +}; + + +/** + * Returns text wrapped in Unicode BiDi formatting characters according to its + * directionality, i.e. either LRE or RLE at the beginning and PDF at the end - + * but only if text's directionality is neither neutral nor the same as the + * global context. Otherwise, returns text unchanged. + * Only treats SanitizedHtml as HTML/HTML-escaped, i.e. ignores mark-up + * and escapes when estimating text's directionality. + * If text has a goog.i18n.bidi.Dir-valued contentDir, this is used instead of + * estimating the directionality. + * + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} text The string to be wrapped. Can be other types, but the value + * will be coerced to a string. + * @return {!goog.soy.data.SanitizedContent|string} The wrapped string. + */ +soy.$$bidiUnicodeWrap = function(bidiGlobalDir, text) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + + // We treat the value as HTML if and only if it says it's HTML, even though in + // legacy usage, we sometimes have an HTML string (not SanitizedContent) that + // is passed to an autoescape="false" template or a {print $foo|noAutoescape}, + // with the output going into an HTML context without escaping. We simply have + // no way of knowing if this is what is happening when we get + // non-SanitizedContent input, and most of the time it isn't. + var isHtml = + soydata.isContentKind_(text, goog.soy.data.SanitizedContentKind.HTML); + var wrappedText = formatter.unicodeWrapWithKnownDir( + soydata.getContentDir(text), text + '', isHtml); + + // Bidi-wrapping a value converts it to the context directionality. Since it + // does not cost us anything, we will indicate this known direction in the + // output SanitizedContent, even though the intended consumer of that + // information - a bidi wrapping directive - has already been run. + var wrappedTextDir = formatter.getContextDir(); + + // Unicode-wrapping UnsanitizedText gives UnsanitizedText. + // Unicode-wrapping safe HTML or JS string data gives valid, safe HTML or JS + // string data. + // ATTENTION: Do these need to be ...ForInternalBlocks()? + if (soydata.isContentKind_(text, goog.soy.data.SanitizedContentKind.TEXT)) { + return new goog.soy.data.UnsanitizedText(wrappedText, wrappedTextDir); + } + if (isHtml) { + return soydata.VERY_UNSAFE.ordainSanitizedHtml(wrappedText, wrappedTextDir); + } + + // Unicode-wrapping does not conform to the syntax of the other types of + // content. For lack of anything better to do, we we do not declare a content + // kind at all by falling through to the non-SanitizedContent case below. + // TODO(aharon): Consider throwing a runtime error on receipt of + // SanitizedContent other than TEXT, HTML, or JS_STR_CHARS. + + // The input was not SanitizedContent, so our output isn't SanitizedContent + // either. + return wrappedText; +}; + +// ----------------------------------------------------------------------------- +// Assertion methods used by runtime. + +/** + * Checks if the type assertion is true if goog.asserts.ENABLE_ASSERTS is + * true. Report errors on runtime types if goog.DEBUG is true. + * @param {boolean} condition The type check condition. + * @param {string} paramName The Soy name of the parameter. + * @param {?} param The JS object for the parameter. + * @param {!string} jsDocTypeStr SoyDoc type str. + * @return {?} the param value + * @throws {goog.asserts.AssertionError} When the condition evaluates to false. + */ +soy.asserts.assertType = function(condition, paramName, param, jsDocTypeStr) { + if (goog.asserts.ENABLE_ASSERTS && !condition) { + var msg = 'expected param ' + paramName + ' of type ' + jsDocTypeStr + + (goog.DEBUG ? (', but got ' + goog.debug.runtimeType(param)) : '') + + '.'; + goog.asserts.fail(msg); + } + return param; +}; + +// ----------------------------------------------------------------------------- +// Used for inspecting Soy template information from rendered pages. + +/** + * Whether we should generate additional HTML comments. + * @type {boolean} + */ +soy.$$debugSoyTemplateInfo = false; + +if (goog.DEBUG) { + /** + * Configures whether we should generate additional HTML comments for + * inspecting Soy template information from rendered pages. + * @param {boolean} debugSoyTemplateInfo + */ + soy.setDebugSoyTemplateInfo = function(debugSoyTemplateInfo) { + soy.$$debugSoyTemplateInfo = debugSoyTemplateInfo; + }; +} + +// ----------------------------------------------------------------------------- +// Generated code. + + +// START GENERATED CODE FOR ESCAPERS. + +/** + * @type {function (*) : string} + */ +soy.esc.$$escapeHtmlHelper = function(v) { + return goog.string.htmlEscape(String(v)); +}; + +/** + * @type {function (*) : string} + */ +soy.esc.$$escapeUriHelper = function(v) { + return goog.string.urlEncode(String(v)); +}; + +/** + * Maps characters to the escaped versions for the named escape directives. + * @private {!Object<string, string>} + */ +soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = { + '\x00': '\x26#0;', + '\x09': '\x26#9;', + '\x0a': '\x26#10;', + '\x0b': '\x26#11;', + '\x0c': '\x26#12;', + '\x0d': '\x26#13;', + ' ': '\x26#32;', + '\x22': '\x26quot;', + '\x26': '\x26amp;', + '\x27': '\x26#39;', + '-': '\x26#45;', + '\/': '\x26#47;', + '\x3c': '\x26lt;', + '\x3d': '\x26#61;', + '\x3e': '\x26gt;', + '`': '\x26#96;', + '\x85': '\x26#133;', + '\xa0': '\x26#160;', + '\u2028': '\x26#8232;', + '\u2029': '\x26#8233;' +}; + +/** + * A function that can be used with String.replace. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_[ch]; +}; + +/** + * Maps characters to the escaped versions for the named escape directives. + * @private {!Object<string, string>} + */ +soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = { + '\x00': '\\x00', + '\x08': '\\x08', + '\x09': '\\t', + '\x0a': '\\n', + '\x0b': '\\x0b', + '\x0c': '\\f', + '\x0d': '\\r', + '\x22': '\\x22', + '$': '\\x24', + '\x26': '\\x26', + '\x27': '\\x27', + '(': '\\x28', + ')': '\\x29', + '*': '\\x2a', + '+': '\\x2b', + ',': '\\x2c', + '-': '\\x2d', + '.': '\\x2e', + '\/': '\\\/', + ':': '\\x3a', + '\x3c': '\\x3c', + '\x3d': '\\x3d', + '\x3e': '\\x3e', + '?': '\\x3f', + '\x5b': '\\x5b', + '\\': '\\\\', + '\x5d': '\\x5d', + '^': '\\x5e', + '\x7b': '\\x7b', + '|': '\\x7c', + '\x7d': '\\x7d', + '\x85': '\\x85', + '\u2028': '\\u2028', + '\u2029': '\\u2029' +}; + +/** + * A function that can be used with String.replace. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_[ch]; +}; + +/** + * Maps characters to the escaped versions for the named escape directives. + * @private {!Object<string, string>} + */ +soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_ = { + '\x00': '\\0 ', + '\x08': '\\8 ', + '\x09': '\\9 ', + '\x0a': '\\a ', + '\x0b': '\\b ', + '\x0c': '\\c ', + '\x0d': '\\d ', + '\x22': '\\22 ', + '\x26': '\\26 ', + '\x27': '\\27 ', + '(': '\\28 ', + ')': '\\29 ', + '*': '\\2a ', + '\/': '\\2f ', + ':': '\\3a ', + ';': '\\3b ', + '\x3c': '\\3c ', + '\x3d': '\\3d ', + '\x3e': '\\3e ', + '@': '\\40 ', + '\\': '\\5c ', + '\x7b': '\\7b ', + '\x7d': '\\7d ', + '\x85': '\\85 ', + '\xa0': '\\a0 ', + '\u2028': '\\2028 ', + '\u2029': '\\2029 ' +}; + +/** + * A function that can be used with String.replace. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_[ch]; +}; + +/** + * Maps characters to the escaped versions for the named escape directives. + * @private {!Object<string, string>} + */ +soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_ = { + '\x00': '%00', + '\x01': '%01', + '\x02': '%02', + '\x03': '%03', + '\x04': '%04', + '\x05': '%05', + '\x06': '%06', + '\x07': '%07', + '\x08': '%08', + '\x09': '%09', + '\x0a': '%0A', + '\x0b': '%0B', + '\x0c': '%0C', + '\x0d': '%0D', + '\x0e': '%0E', + '\x0f': '%0F', + '\x10': '%10', + '\x11': '%11', + '\x12': '%12', + '\x13': '%13', + '\x14': '%14', + '\x15': '%15', + '\x16': '%16', + '\x17': '%17', + '\x18': '%18', + '\x19': '%19', + '\x1a': '%1A', + '\x1b': '%1B', + '\x1c': '%1C', + '\x1d': '%1D', + '\x1e': '%1E', + '\x1f': '%1F', + ' ': '%20', + '\x22': '%22', + '\x27': '%27', + '(': '%28', + ')': '%29', + '\x3c': '%3C', + '\x3e': '%3E', + '\\': '%5C', + '\x7b': '%7B', + '\x7d': '%7D', + '\x7f': '%7F', + '\x85': '%C2%85', + '\xa0': '%C2%A0', + '\u2028': '%E2%80%A8', + '\u2029': '%E2%80%A9', + '\uff01': '%EF%BC%81', + '\uff03': '%EF%BC%83', + '\uff04': '%EF%BC%84', + '\uff06': '%EF%BC%86', + '\uff07': '%EF%BC%87', + '\uff08': '%EF%BC%88', + '\uff09': '%EF%BC%89', + '\uff0a': '%EF%BC%8A', + '\uff0b': '%EF%BC%8B', + '\uff0c': '%EF%BC%8C', + '\uff0f': '%EF%BC%8F', + '\uff1a': '%EF%BC%9A', + '\uff1b': '%EF%BC%9B', + '\uff1d': '%EF%BC%9D', + '\uff1f': '%EF%BC%9F', + '\uff20': '%EF%BC%A0', + '\uff3b': '%EF%BC%BB', + '\uff3d': '%EF%BC%BD' +}; + +/** + * A function that can be used with String.replace. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_[ch]; +}; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_ = /[\x00\x22\x27\x3c\x3e]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x26\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_ = /[\x00\x08-\x0d\x22\x26\x27\/\x3c-\x3e\x5b-\x5d\x7b\x7d\x85\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_ = /[\x00\x08-\x0d\x22\x24\x26-\/\x3a\x3c-\x3f\x5b-\x5e\x7b-\x7d\x85\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_ = /[\x00\x08-\x0d\x22\x26-\x2a\/\x3a-\x3e@\\\x7b\x7d\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @private {!RegExp} + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_ = /[\x00- \x22\x27-\x29\x3c\x3e\\\x7b\x7d\x7f\x85\xa0\u2028\u2029\uff01\uff03\uff04\uff06-\uff0c\uff0f\uff1a\uff1b\uff1d\uff1f\uff20\uff3b\uff3d]/g; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_ = /^(?!-*(?:expression|(?:moz-)?binding))(?!\s+)(?:[.#]?-?(?:[_a-z0-9-]+)(?:-[_a-z0-9-]+)*-?|(?:rgb|hsl)a?\([0-9.%,\u0020]+\)|-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[a-z]{1,2}|%)?|!important|\s+)*$/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_ = /^(?![^#?]*\/(?:\.|%2E){2}(?:[\/?#]|$))(?:(?:https?|mailto):|[^&:\/?#]*(?:[\/?#]|$))/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_MEDIA_URI_ = /^[^&:\/?#]*(?:[\/?#]|$)|^https?:|^data:image\/[a-z0-9+]+;base64,[a-z0-9+\/]+=*$|^blob:/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_IMAGE_DATA_URI_ = /^data:image\/(?:bmp|gif|jpe?g|png|tiff|webp);base64,[a-z0-9+\/]+=*$/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_TEL_URI_ = /^tel:[0-9a-z;=\-+._!~*'\u0020\/():&$#?@,]+$/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_ = /^(?!on|src|(?:style|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|usemap)\s*$)(?:[a-z0-9_$:-]*)$/i; + +/** + * A pattern that vets values produced by the named directives. + * @private {!RegExp} + */ +soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_ = /^(?!script|style|title|textarea|xmp|no)[a-z0-9_$:-]*$/i; + +/** + * A helper for the Soy directive |normalizeHtml + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeHtmlHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_, + soy.esc.$$REPLACER_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |escapeHtmlNospace + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeHtmlNospaceHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_, + soy.esc.$$REPLACER_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |normalizeHtmlNospace + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeHtmlNospaceHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_, + soy.esc.$$REPLACER_FOR_NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |escapeJsString + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeJsStringHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_, + soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_); +}; + +/** + * A helper for the Soy directive |escapeJsRegex + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeJsRegexHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_, + soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_); +}; + +/** + * A helper for the Soy directive |escapeCssString + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeCssStringHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_, + soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_); +}; + +/** + * A helper for the Soy directive |filterCssValue + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterCssValueHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterCssValue', [str]); + return 'zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |normalizeUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeUriHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_, + soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_); +}; + +/** + * A helper for the Soy directive |filterNormalizeUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterNormalizeUriHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterNormalizeUri', [str]); + return 'about:invalid#zSoyz'; + } + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_, + soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_); +}; + +/** + * A helper for the Soy directive |filterNormalizeMediaUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterNormalizeMediaUriHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_MEDIA_URI_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterNormalizeMediaUri', [str]); + return 'about:invalid#zSoyz'; + } + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_, + soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI__AND__FILTER_NORMALIZE_MEDIA_URI_); +}; + +/** + * A helper for the Soy directive |filterImageDataUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterImageDataUriHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_IMAGE_DATA_URI_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterImageDataUri', [str]); + return 'data:image/gif;base64,zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |filterTelUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterTelUriHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_TEL_URI_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterTelUri', [str]); + return 'about:invalid#zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |filterHtmlAttributes + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterHtmlAttributesHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterHtmlAttributes', [str]); + return 'zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |filterHtmlElementName + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterHtmlElementNameHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_.test(str)) { + goog.asserts.fail('Bad value `%s` for |filterHtmlElementName', [str]); + return 'zSoyz'; + } + return str; +}; + +/** + * Matches all tags, HTML comments, and DOCTYPEs in tag soup HTML. + * By removing these, and replacing any '<' or '>' characters with + * entities we guarantee that the result can be embedded into a + * an attribute without introducing a tag boundary. + * + * @private {!RegExp} + */ +soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?([a-zA-Z][a-zA-Z0-9:\-]*))(?:[^>'"]|"[^"]*"|'[^']*')*>/g; + +/** + * Matches all occurrences of '<'. + * + * @private {!RegExp} + */ +soy.esc.$$LT_REGEX_ = /</g; + +/** + * Maps lower-case names of innocuous tags to true. + * + * @private {!Object<string, boolean>} + */ +soy.esc.$$SAFE_TAG_WHITELIST_ = {'b': true, 'br': true, 'em': true, 'i': true, 's': true, 'sub': true, 'sup': true, 'u': true}; + +/** + * Pattern for matching attribute name and value, where value is single-quoted + * or double-quoted. + * See http://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0 + * + * @private {!RegExp} + */ +soy.esc.$$HTML_ATTRIBUTE_REGEX_ = /([a-zA-Z][a-zA-Z0-9:\-]*)[\t\n\r\u0020]*=[\t\n\r\u0020]*("[^"]*"|'[^']*')/g; + +// END GENERATED CODE
diff --git a/third_party/ink/wireserializer.js b/third_party/ink/wireserializer.js new file mode 100644 index 0000000..b88dcedf --- /dev/null +++ b/third_party/ink/wireserializer.js
@@ -0,0 +1,845 @@ +// Copyright 2017 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. +/** + * @fileoverview Protocol Buffer 2 Serializer which serializes and deserializes + * messages using the wire format. Note that this serializer requires protocol + * buffer reflection, which carries some overhead. + * @supported any browser with DataView implemented. For now Chrome9, FF15, IE10 + * + * @see https://developers.google.com/protocol-buffers/docs/encoding + * + * TODO(feinberg): Replace goog.math.Long with mutable long representation that + * permits in-place arithmetic to avoid allocations. + */ + + +goog.provide('net.proto2.contrib.WireSerializer'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math.Long'); +goog.require('goog.proto2.Message'); +goog.require('goog.proto2.Serializer'); + + + +/** + * Wire format serializer. + * + * @constructor + * @extends {goog.proto2.Serializer} + */ +net.proto2.contrib.WireSerializer = function() { + /** + * This array is where proto bytes go during serialization. + * It must be reset for each serialization. + * @type {!Array.<number>} + * @private + */ + this.buffer_ = []; + + /** + * Scratch workspace to avoid allocations during serialization. + * @type {{value: number, length: number}} + * @private + */ + this.scratchTag32_ = {value: 0, length: 0}; + + /** + * Scratch workspace to avoid allocations during serialization. + * @type {{value: !goog.math.Long, length: number}} + * @private + */ + this.scratchTag64_ = {value: goog.math.Long.getZero(), length: 0}; + + /** + * Scratch data view for coding/decoding little-endian numbers. + * @type {!DataView} + * @private + */ + this.dataView_ = new DataView(new ArrayBuffer(8)); +}; +goog.inherits(net.proto2.contrib.WireSerializer, goog.proto2.Serializer); + + +/** + * @return {!Array.<number>} The serialized form of the message. + * @override + */ +net.proto2.contrib.WireSerializer.prototype.serialize = function(message) { + if (message == null) { + return []; + } + + this.buffer_ = []; + + var descriptor = message.getDescriptor(); + var fields = descriptor.getFields(); + + // Add the known fields. + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + + if (!message.has(field)) { + continue; + } + + if (field.isRepeated()) { + if (field.isPacked()) { + this.serializePackedField_(message, field); + } else { + for (var j = 0, n = message.countOf(field); j < n; j++) { + var val = message.get(field, j); + this.getSerializedValue(field, val); + } + } + } else { + this.getSerializedValue(field, message.get(field)); + } + } + + return this.buffer_; +}; + + +/** + * Append the serialized packed field to our serialization buffer. + * @param {!goog.proto2.Message} message The message containing the field + * to serialize. + * @param {!goog.proto2.FieldDescriptor} field The field to serialize. + * @return {boolean} Whether the field tag was serialized. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializePackedField_ = + function(message, field) { + var buf = this.buffer_; + + var wireType = 2; // Per definition. + + // Tag. + this.serializeVarint_((field.getTag() << 3) | wireType); + + // Make note of the current buffer size. After serializing the repeated + // fields, splice the size header at the current position. + var savedBufferSize = buf.length; + for (var j = 0, n = message.countOf(field); j < n; j++) { + var val = message.get(field, j); + this.getSerializedValue(field, val, true /* omit tag */); + } + var serializedData = buf.splice( + savedBufferSize, buf.length - savedBufferSize); + this.serializeVarint_(serializedData.length); + + var args = [buf.length, 0].concat(serializedData); + buf.splice.apply(buf, args); + + return true; +}; + + +/** + * Append the serialized field tag to our serialization buffer. + * @param {goog.proto2.FieldDescriptor} field The field to serialize. + * @return {boolean} Whether the field tag was serialized. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeFieldTag_ = + function(field) { + var wireType = 0; + switch (field.getFieldType()) { + default: + return false; + case goog.proto2.Message.FieldType.SINT32: + case goog.proto2.Message.FieldType.SINT64: + case goog.proto2.Message.FieldType.BOOL: + case goog.proto2.Message.FieldType.INT64: + case goog.proto2.Message.FieldType.ENUM: + case goog.proto2.Message.FieldType.INT32: + case goog.proto2.Message.FieldType.UINT32: + case goog.proto2.Message.FieldType.UINT64: + wireType = 0; + break; + case goog.proto2.Message.FieldType.FIXED64: + case goog.proto2.Message.FieldType.SFIXED64: + case goog.proto2.Message.FieldType.DOUBLE: + wireType = 1; + break; + case goog.proto2.Message.FieldType.STRING: + case goog.proto2.Message.FieldType.BYTES: + case goog.proto2.Message.FieldType.MESSAGE: + wireType = 2; + break; + case goog.proto2.Message.FieldType.GROUP: + wireType = 3; + break; + case goog.proto2.Message.FieldType.FIXED32: + case goog.proto2.Message.FieldType.SFIXED32: + case goog.proto2.Message.FieldType.FLOAT: + wireType = 5; + break; + } + this.serializeVarint_((field.getTag() << 3) | wireType); + return true; +}; + + +/** + * Returns the serialized form of the given value for the given field if the + * field is a Message or Group and returns the value unchanged otherwise, except + * for Infinity, -Infinity and NaN numerical values which are converted to + * string representation. + * + * @param {goog.proto2.FieldDescriptor} field The field from which this + * value came. + * @param {*} value The value of the field. + * @param {boolean=} opt_omitTag If present and true, do not serialize a field + * tag. + * + * @return {*} The value. + * @protected + */ +net.proto2.contrib.WireSerializer.prototype.getSerializedValue = + function(field, value, opt_omitTag) { + if (!opt_omitTag) { + if (!this.serializeFieldTag_(field)) { + return false; + } + } + + switch (field.getFieldType()) { + default: + throw new Error('Unknown field type ' + field.getFieldType()); + case goog.proto2.Message.FieldType.SINT32: + this.serializeVarint_(this.zigZagEncode(/** @type {number} */ (value))); + break; + case goog.proto2.Message.FieldType.SINT64: + this.serializeVarint64_(this.zigZagEncode64_( + goog.math.Long.fromString(/** @type {string} */(value)))); + break; + case goog.proto2.Message.FieldType.BOOL: + this.serializeVarint_(value ? 1 : 0); + break; + case goog.proto2.Message.FieldType.INT32: + var numericValue = /** @type {number} */ (value); + if (numericValue > 0) { + this.serializeVarint_(numericValue); + } else { + // Negative 32 bit quantities are always 10 bytes long. + this.serializeVarint64_(goog.math.Long.fromInt(numericValue)); + } + break; + case goog.proto2.Message.FieldType.INT64: + case goog.proto2.Message.FieldType.UINT64: + this.serializeVarint64_( + goog.math.Long.fromString(/** @type {string} */(value))); + break; + case goog.proto2.Message.FieldType.ENUM: + case goog.proto2.Message.FieldType.UINT32: + this.serializeVarint_(/** @type {number} */ (value)); + break; + case goog.proto2.Message.FieldType.FIXED64: + case goog.proto2.Message.FieldType.SFIXED64: + this.serializeFixed_( + goog.math.Long.fromString(/** @type {string} */ (value)), 8); + break; + case goog.proto2.Message.FieldType.DOUBLE: + this.serializeDouble_(/** @type {number} */ (value)); + break; + case goog.proto2.Message.FieldType.STRING: + this.serializeString(value); + break; + case goog.proto2.Message.FieldType.BYTES: + this.serializeBytes(value); + break; + case goog.proto2.Message.FieldType.GROUP: + var serialized = new net.proto2.contrib.WireSerializer().serialize( + /** @type {goog.proto2.Message} */ (value)); + goog.array.extend(this.buffer_, serialized); + this.serializeVarint_((field.getTag() << 3) | 4); + break; + case goog.proto2.Message.FieldType.MESSAGE: + var serialized = new net.proto2.contrib.WireSerializer().serialize( + /** @type {goog.proto2.Message} */ (value)); + this.serializeVarint_(serialized.length); + goog.array.extend(this.buffer_, serialized); + break; + case goog.proto2.Message.FieldType.FIXED32: + this.serializeFixed_( + goog.math.Long.fromNumber(/** @type {number} */ (value)), 4); + break; + case goog.proto2.Message.FieldType.SFIXED32: + this.serializeFixed_( + goog.math.Long.fromInt(/** @type {number} */ (value)), 4); + break; + case goog.proto2.Message.FieldType.FLOAT: + this.serializeFloat_(/** @type {number} */ (value)); + break; + } + // To avoid allocations, this method serializes into a pre-existing buffer, + // rather than serializing into a new value object. + return null; +}; + + +/** @override */ +net.proto2.contrib.WireSerializer.prototype.deserializeTo = + function(message, buffer) { + if (buffer == null) { + // Since value double-equals null, it may be either null or undefined. + // Ensure we return the same one, since they have different meanings. + return buffer; + } + + if (buffer instanceof ArrayBuffer) { + buffer = new Uint8Array(buffer); + } + + var descriptor = message.getDescriptor(); + var offset = 0; + var size = buffer.length; + var view = function() { + return buffer.subarray(offset); + }; + // Because subarray is broken on ie10, we can't simply advance our view of the + // buffer. Instead, we keep track of an offset. + while (offset < buffer.length) { + var tag = this.parseUnsignedVarInt_(view()); + var tagValue = tag.value; + var tagLength = tag.length; + var index = tagValue >> 3; + var wireType = tagValue & 0x7; // Last 3 bits. + + // Advance. + offset += tagLength; + + var field = descriptor.findFieldByTag(index); + if (!field) { + // Unknown field; skip it. + offset += this.lengthForWireType_(wireType, view()); + continue; + } else if (field.isPacked()) { // Packed repeated. + // Read byte length. + var v = this.parseUnsignedVarInt_(view()); + var remaining = v.value; + offset += v.length; + while (remaining > 0 && offset < buffer.length) { + var packedValue = + this.getDeserializedValue(field, view()); + if (!packedValue) { + throw new Error('Expected ' + field.getFieldType()); + } + message.add(field, packedValue.value); + offset += packedValue.length; + remaining -= packedValue.length; + } + } else { + var value = this.getDeserializedValue(field, view()); + if (!value) { + throw new Error('Expected ' + field.getFieldType()); + } + offset += value.length; + if (field.isRepeated()) { + message.add(field, value.value); + } else { + message.set(field, value.value); + } + } + } +}; + + +/** + * @param {number} wireType + * @param {*} buffer The data of the message. + * @return {number} Default length to use for a given fieldType. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.lengthForWireType_ = function( + wireType, buffer) { + var length = 0; + switch (wireType) { + case 0: // int32, int64, uint32, uint64, sint32, sint64, bool, enum. + length = this.parseVarInt64_(buffer).length; + break; + case 1: // fixed64, sfixed64, double. + length = 8; + break; + case 2: // Length-delimited: string, bytes, messages, repeated fields. + var bufferLength = this.parseVarInt64_(buffer); + length = bufferLength.length + bufferLength.value.toInt(); + break; + case 3: // "Start group". Not supported. + case 4: // "End group". Not supported. + goog.asserts.fail('Error deserializing group'); + break; + case 5: // fixed32, sfixed32, float. + length = 4; + break; + } + return length; +}; + + +/** + * Deserializes a message from the expected format and places the + * data in the message. The message must correspond to a group. Moreover + * the buffer must be positioned after the initial START_GROUP tag for the + * group. The message will be terminated by the first END_GROUP tag at the + * same nesting level. It is the responsibility of the caller to validate that + * its field index matches the one in the opening START_GROUP tag. Since groups + * are not length-delimited, this method returns the length of the parsed + * data excluding the END_GROUP tag. + * + * @param {goog.proto2.Message} message The message in which to + * place the information. + * @param {*} buffer The data of the message. + * @return {number} the length of the parsed message, excluding the closing tag. + * @protected + */ + net.proto2.contrib.WireSerializer.prototype.deserializeGroupTo = + function(message, buffer) { + var descriptor = message.getDescriptor(); + var parsedLength = 0; + + while (true) { + var tag = this.parseUnsignedVarInt_(buffer); + var tagValue = tag.value; + var tagLength = tag.length; + var index = tagValue >> 3; + var wiretype = tagValue & 7; + if (wiretype == 4) { + // Got an end group. + break; + } + parsedLength += tagLength; + var value = {value: undefined, length: 0}; + var field = descriptor.findFieldByTag(index); + if (field) { + value = this.getDeserializedValue(field, buffer.subarray(tagLength)); + if (value && value.value !== null) { + if (field.isRepeated()) { + message.add(field, value.value); + } else { + message.set(field, value.value); + } + } + } + parsedLength += value.length; + if (buffer.length < tagLength + value.length) { + break; + } + buffer = buffer.subarray(tagLength + value.length); + } + return parsedLength; +}; + + +/** + * @override + */ +net.proto2.contrib.WireSerializer.prototype.getDeserializedValue = + function(field, buffer) { + var value = null; + var t = field.getFieldType(); + var varInt = this.parseVarInt64_(buffer); + var length = varInt.length; + switch (t) { + case goog.proto2.Message.FieldType.SINT32: + value = this.zigZagDecode_(varInt.value.toInt()); + break; + case goog.proto2.Message.FieldType.SINT64: + value = this.zigZagDecode64_(varInt.value).toString(); + break; + case goog.proto2.Message.FieldType.BOOL: + value = varInt.value.equals(goog.math.Long.getOne()); + break; + case goog.proto2.Message.FieldType.INT64: + case goog.proto2.Message.FieldType.UINT64: + value = varInt.value.toString(); + break; + case goog.proto2.Message.FieldType.INT32: + value = varInt.value.toInt(); + break; + case goog.proto2.Message.FieldType.ENUM: + case goog.proto2.Message.FieldType.UINT32: + value = varInt.value.getLowBitsUnsigned(); + break; + case goog.proto2.Message.FieldType.FIXED64: + case goog.proto2.Message.FieldType.SFIXED64: + value = this.parseFixed64_(buffer.subarray(0, 8)).toString(); + length = 8; + break; + case goog.proto2.Message.FieldType.DOUBLE: + value = this.parseDouble_(buffer.subarray(0, 8)); + length = 8; + break; + case goog.proto2.Message.FieldType.STRING: + var strBuffer = + buffer.subarray(varInt.length, varInt.length + varInt.value.toInt()); + value = this.arrayBufferToUtf8String_(strBuffer); + length = varInt.length + varInt.value.toInt(); + break; + case goog.proto2.Message.FieldType.BYTES: + var strBuffer = + buffer.subarray(varInt.length, varInt.length + varInt.value.toInt()); + // Store the bytes using a String. + value = this.arrayBufferToString_(strBuffer); + length = varInt.length + varInt.value.toInt(); + break; + case goog.proto2.Message.FieldType.GROUP: + value = field.getFieldMessageType().createMessageInstance(); + var groupLength = this.deserializeGroupTo(value, buffer); + var next = buffer.subarray(groupLength); + var closingTag = this.parseVarInt64_(next); + var expected = (field.getTag() << 3) | 4; + goog.asserts.assert(closingTag.value.toInt() == expected, + 'Error deserializing group'); + length = groupLength + closingTag.length; + break; + case goog.proto2.Message.FieldType.MESSAGE: + length = varInt.length + varInt.value.toInt(); + var data = buffer.subarray(varInt.length, length); + value = field.getFieldMessageType().createMessageInstance(); + this.deserializeTo(value, data); + break; + case goog.proto2.Message.FieldType.FIXED32: + case goog.proto2.Message.FieldType.SFIXED32: + value = this.parseFixed32_( + buffer.subarray(0, 4), t == goog.proto2.Message.FieldType.SFIXED32); + length = 4; + break; + case goog.proto2.Message.FieldType.FLOAT: + value = this.parseFloat_(buffer.subarray(0, 4)); + length = 4; + break; + } + return {value: value, length: length}; +}; + + +/** + * @param {*} value Binary string that needs to be converted to bytes. + */ +net.proto2.contrib.WireSerializer.prototype.serializeBytes = function(value) { + if (goog.isDefAndNotNull(value)) { + var valueStr = /** @type {string} */ (value); + // Serialize the number of bytes, per spec of the wire format. + this.serializeVarint_(valueStr.length); + for (var i = 0; i < valueStr.length; i++) { + this.buffer_.push(valueStr.charCodeAt(i)); + } + } +}; + + +/** + * @param {*} value String (possibly utf-8) that needs to be converted to bytes. + */ +net.proto2.contrib.WireSerializer.prototype.serializeString = function(value) { + if (goog.isDefAndNotNull(value)) { + var valueStr = /** @type {string} */ (value); + // Inspired by: + // http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html + var utf8 = unescape(encodeURIComponent(valueStr)); + // Serialize the length of the encoded string: what we want is the number + // of bytes, not the number of characters, per spec of the wire format. + this.serializeVarint_(utf8.length); + for (var i = 0; i < utf8.length; i++) { + this.buffer_.push(utf8.charCodeAt(i)); + } + } +}; + + +/** + * @param {*} buffer to parse as String. + * @return {{value: string, length: number}} + */ +net.proto2.contrib.WireSerializer.prototype.parseString = function(buffer) { + var length = this.parseUnsignedVarInt_(buffer); + var strBuffer = buffer.subarray(length.length, length.length + length.value); + return { + value: this.arrayBufferToUtf8String_(strBuffer), + length: length.length + length.value + }; +}; + + +/** + * @param {number} number signed number that needs to be converted to unsigned. + * @return {number} + */ +net.proto2.contrib.WireSerializer.prototype.zigZagEncode = + function(number) { + var sign = number >>> 31; + return (number << 1) ^ -sign; +}; + + +/** + * @param {number} number Unsigned number in zigzag format that needs + to be converted to signed. + * @return {number} signed. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.zigZagDecode_ = + function(number) { + return (number >>> 1) ^ -(number & 1); +}; + + +/** + * @param {!goog.math.Long} number signed number that needs to be converted to + * unsigned. + * @return {!goog.math.Long} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.zigZagEncode64_ = + function(number) { + var sign = number.shiftRightUnsigned(63); + return number.shiftLeft(1).xor(sign.negate()); +}; + + +/** + * @param {!goog.math.Long} number Unsigned number in zigzag format that needs + to be converted to signed. + * @return {!goog.math.Long} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.zigZagDecode64_ = + function(number) { + return number.shiftRightUnsigned(1).xor( + number.and(goog.math.Long.getOne()).negate()); +}; + + +/** + * Serialize the given number as a varint into our buffer. + * @param {number} number that needs to be converted to varint. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeVarint_ = + function(number) { + do { + var chunk = number & 0x7F; + number = number >>> 7; + if (number > 0) { + chunk = chunk | 0x80; + } + this.buffer_.push(chunk); + } while (number > 0); +}; + + +/** + * Serialize the given 64-bit number as a varint into our buffer. + * @param {!goog.math.Long} number that needs to be encoded as varint. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeVarint64_ = + function(number) { + var mask = goog.math.Long.fromInt(0x7F); + do { + var chunk = number.and(mask).toInt(); + number = number.shiftRightUnsigned(7); + if (number.greaterThan(goog.math.Long.getZero())) { + chunk = chunk | 0x80; + } + this.buffer_.push(chunk); + } while (number.greaterThan(goog.math.Long.getZero())); +}; + + +/** + * @param {*} buffer from which field number and type needs to be extracted. + * @return {{value: !goog.math.Long, length: number}} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseVarInt64_ = function(buffer) { + var valueInfo = this.scratchTag64_; + var number = goog.math.Long.fromNumber(0); + var i = 0; + for (; i < buffer.length; i++) { + var bits = goog.math.Long.fromInt(buffer[i] & 0x7F).shiftLeft(i * 7); + number = number.or(bits); + if ((buffer[i] & 0x80) == 0) { + break; + } + } + valueInfo.value = number; + valueInfo.length = i + 1; + return valueInfo; +}; + + +/** + * A special case parser for unsigned 32-bit varints, which can fit comfortably + * in 32 bits during decoding. + * @param {*} buffer from which field number and type needs to be extracted. + * @return {{value: number, length: number}} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseUnsignedVarInt_ = + function(buffer) { + var valueInfo = this.scratchTag32_; + var result = 0; + var i = 0; + for (; i < buffer.length; i++) { + result = result | ((buffer[i] & 0x7F) << (i * 7)); + if ((buffer[i] & 0x80) == 0) { + break; + } + } + valueInfo.value = result; + valueInfo.length = i + 1; + return valueInfo; +}; + + +/** + * @param {goog.math.Long} number that needs to be converted to little endian + * order. + * @param {number} size of the result array (4 = 32bit, 8 = 64bit). + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeFixed_ = + function(number, size) { + var mask = goog.math.Long.fromInt(0xFF); + for (var i = 0; i < size; i++) { + var chunk = number.and(mask).toInt(); + this.buffer_.push(chunk); + number = number.shiftRightUnsigned(8); + } +}; + + +/** + * @param {*} buffer from which the fixed32 value needs to be extracted. + * @param {boolean} signed if the fixed32 value represents a signed value + * (i.e. sfixed32). + * @return {number} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseFixed32_ = function( + buffer, signed) { + var number = 0; + for (var i = 0; i < buffer.length; i++) { + number = number | (buffer[i] << (i * 8)); + } + if (!signed) { + // The bitwise operations above treat numbers as signed int32 values. + // Correct for this in the unsigned case by using >>> to coerce to unsigned. + number = number >>> 0; + } + return number; +}; + + +/** + * @param {*} buffer from which the fixed64 value needs to be extracted. + * @return {!goog.math.Long} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseFixed64_ = function(buffer) { + // Javascript numbers are only accurate up to 51 bits as they are stored as + // 64-bit floating points. We store the result in a goog.math.Long object to + // preserve full precision. + return new goog.math.Long( + this.parseFixed32_(buffer.subarray(0, 4), true), + this.parseFixed32_(buffer.subarray(4, 8), true)); +}; + + +/** + * @param {*} buffer from which double needs to be extracted. + * @return {number} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseDouble_ = function(buffer) { + for (var i = 0; i < 8; i++) { + this.dataView_.setUint8(i, buffer[i]); + } + return this.dataView_.getFloat64(0, true); // little-endian +}; + + +/** + * @param {*} buffer from which float needs to be extracted. + * @return {number} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.parseFloat_ = function(buffer) { + for (var i = 0; i < 4; i++) { + this.dataView_.setUint8(i, buffer[i]); + } + return this.dataView_.getFloat32(0, true); // little-endian +}; + + +/** + * @param {number} number to be serialized to 8 bytes. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeDouble_ = + function(number) { + this.dataView_.setFloat64(0, number, true); // little-endian + for (var i = 0; i < 8; i++) { + this.buffer_.push(this.dataView_.getUint8(i)); + } +}; + + +/** + * @param {number} number to be serialized to 4 bytes. + * @private + */ +net.proto2.contrib.WireSerializer.prototype.serializeFloat_ = function(number) { + this.dataView_.setFloat32(0, number, true); // little-endian + for (var i = 0; i < 4; i++) { + this.buffer_.push(this.dataView_.getUint8(i)); + } +}; + + +/** + * This method converts an ArrayBuffer into a string (with utf8 encoding). + * + * @param {ArrayBuffer} buffer The buffer to convert to a string + * @return {string} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.arrayBufferToUtf8String_ = function( + buffer) { + var str = this.arrayBufferToString_(buffer); + // Inspired by: + // http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html + return decodeURIComponent(escape(str)); +}; + + +/** + * This method converts an ArrayBuffer into a string (each index is 1 byte). + * + * The maximum stack size in chrome is ~125k. This means that using + * String.fromCharCode.apply will fail for strings larger than the maximum stack + * size. This method breaks up the calls to fromCharCode into ~64k chunks to + * work around this limitation. + * + * @param {ArrayBuffer} buffer The buffer to convert to a string + * @return {string} + * @private + */ +net.proto2.contrib.WireSerializer.prototype.arrayBufferToString_ = function( + buffer) { + var CHUNK_SIZE = 65536; + var str = ''; + var view = new Uint16Array(buffer); + for (var offset = 0; offset < view.length; offset += CHUNK_SIZE) { + var len = Math.min(CHUNK_SIZE, view.length - offset); + var subview = view.subarray(offset, offset + len); + str += String.fromCharCode.apply(null, subview); + } + return str; +};
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js index 1c4e80e..1c94df0 100644 --- a/ui/login/display_manager.js +++ b/ui/login/display_manager.js
@@ -33,6 +33,7 @@ /** @const */ var SCREEN_ARC_TERMS_OF_SERVICE = 'arc-tos'; /** @const */ var SCREEN_WRONG_HWID = 'wrong-hwid'; /** @const */ var SCREEN_DEVICE_DISABLED = 'device-disabled'; +/** @const */ var SCREEN_UPDATE_REQUIRED = 'update-required'; /** @const */ var SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR = 'unrecoverable-cryptohome-error'; /** @const */ var SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE = @@ -154,6 +155,7 @@ SCREEN_ARC_TERMS_OF_SERVICE, SCREEN_WRONG_HWID, SCREEN_CONFIRM_PASSWORD, + SCREEN_UPDATE_REQUIRED, SCREEN_FATAL_ERROR ];
diff --git a/ui/login/md_screen_container.css b/ui/login/md_screen_container.css index 708b08f..bdac3bb 100644 --- a/ui/login/md_screen_container.css +++ b/ui/login/md_screen_container.css
@@ -97,6 +97,7 @@ #oobe.terms-of-service #inner-container, #oobe.arc-tos #inner-container, #oobe.update #inner-container, +#oobe.update-required #inner-container, #oobe.user-image #inner-container, #oobe.wrong-hwid #inner-container, #oobe.unrecoverable-cryptohome-error #inner-container { @@ -186,6 +187,7 @@ #arc-tos-dot, #tpm-error-message-dot, #wrong-hwid-dot, +#update-required-dot, #unrecoverable-cryptohome-error-dot { display: none; }
diff --git a/ui/login/screen_container.css b/ui/login/screen_container.css index 4ee8855..41833ce 100644 --- a/ui/login/screen_container.css +++ b/ui/login/screen_container.css
@@ -88,6 +88,7 @@ #oobe.terms-of-service #inner-container, #oobe.arc-tos #inner-container, #oobe.update #inner-container, +#oobe.update-required #inner-container, #oobe.user-image #inner-container, #oobe.wrong-hwid #inner-container, #oobe.unrecoverable-cryptohome-error #inner-container { @@ -176,6 +177,7 @@ #terms-of-service-dot, #arc-tos-dot, #tpm-error-message-dot, +#update-required-dot, #wrong-hwid-dot, #unrecoverable-cryptohome-error-dot { display: none;