Commit e8a7d2db authored by Ilya Sherman's avatar Ilya Sherman Committed by Commit Bot

[Reland] Generate a small JS binary for parsing the X-Client-Data header

This header contains serialized and base64-encoded protocol buffers.
This utility simply base64-decodes the serialized data, and then
parses the proto via compiled JSPB parsing.

THis is a reland of
https://chromium-review.googlesource.com/c/chromium/src/+/2301121, restricting
the new build target to only build on Linux.

R=asvitkine, dpranke@google.com
CC=mathias@chromium.org, yangguo@chromium.org

Bug: 1103854
Change-Id: I30bd4d02f89b250c53c06068ab22e97d7fc279ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2316448
Commit-Queue: Ilya Sherman <isherman@chromium.org>
Reviewed-by: default avatarDirk Pranke <dpranke@google.com>
Reviewed-by: default avatarMathias Bynens <mathias@chromium.org>
Auto-Submit: Ilya Sherman <isherman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791341}
parent 316ed75c
......@@ -10,6 +10,9 @@ for more details about the presubmit API built into depot_tools.
_EXCLUDED_PATHS = (
# Generated file.
(r"^components[\\/]variations[\\/]proto[\\/]devtools[\\/]"
r"client_variations_parser.js"),
r"^native_client_sdk[\\/]src[\\/]build_tools[\\/]make_rules.py",
r"^native_client_sdk[\\/]src[\\/]build_tools[\\/]make_simple.py",
r"^native_client_sdk[\\/]src[\\/]tools[\\/].*.mk",
......
......@@ -23,4 +23,18 @@ proto_library("proto") {
"study.proto",
"variations_seed.proto",
]
# The generated JavaScript bindings are used to build a small, self-contained
# parser for ClientVariations in Dev Tools. This parser is used to decode the
# X-Client-Data header.
generate_javascript = true
}
# Some of the Windows builders do not support Java, which is a required
# dependency for this build target. This target is only built manually, so it's
# fine to limit it to Linux.
if (is_linux || is_mac) {
group("devtools") {
deps = [ "devtools" ]
}
}
# Copyright 2020 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.
"""
See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""
def CheckChange(input_api, output_api):
"""Checks that changes to client_variations.proto are mirrored."""
has_proto_update = False
has_parser_update = False
cwd = input_api.PresubmitLocalPath()
for path in input_api.AbsoluteLocalPaths():
if not path.startswith(cwd):
continue
name = input_api.os_path.relpath(path, cwd)
if name == 'client_variations.proto':
has_proto_update = True
elif name == 'devtools/client_variations_parser.js':
has_parser_update = True
results = []
if has_proto_update and not has_parser_update:
results.append(output_api.PresubmitPromptWarning(
'client_variations.proto was changed. Does the JS parser at '
'devtools/client_variations_parser.js need to be updated as well?'))
return results
def CheckChangeOnUpload(input_api, output_api):
return CheckChange(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return []
......@@ -11,6 +11,8 @@ option java_package = "org.chromium.components.variations";
package variations;
// NOTE: If you update this proto, you'll also need to rebuild the JS parser for
// devtools. See //components/variations/proto/devtools/BUILD.gn for details.
message ClientVariations {
// A list of client experiment variation IDs that are active.
repeated int32 variation_id = 1;
......
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/closure_compiler/closure_args.gni")
import("//third_party/closure_compiler/compile_js.gni")
# Some of the Windows builders do not support Java, which is a required
# dependency for these build targets. These targets are only built manually, so
# it's fine to limit them to Linux.
if (is_linux || is_mac) {
# Build this target to generate a small, self-contained utility for parsing a
# serialized ClientVariations proto from the X-Client-Data header.
#
# Run components/variations/proto/devtools/update_client_variations_parser.py
# to rebuild the library that is synced into the Dev Tools repository.
group("devtools") {
deps = [ ":client_variations_parser_uncompiled" ]
}
# Note that the name passed to the `js_binary` rule implicitly defines a
# dependency on the file that matches the build rule's name.
js_binary("client_variations_parser_uncompiled") {
outputs = [ "$target_gen_dir/client_variations_parser_gen.js" ]
deps = [ "..:proto_js" ]
closure_flags = strict_error_checking_closure_args + [
"compilation_level=ADVANCED_OPTIMIZATIONS",
"browser_featureset_year=2020",
"generate_exports",
"export_local_property_definitions",
"isolation_mode=IIFE",
"jscomp_warning=checkTypes",
"jscomp_warning=deprecatedAnnotations",
"jscomp_warning=deprecated",
]
}
}
/* eslint-disable */
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Note: This is a generated file. Do not edit by hand. Instead, run
// components/variations/proto/devtools/update_client_variations_parser.py to
// update.
const gen = {};
// clang-format off
(function(){var f=this||self;
function g(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==
b&&"undefined"==typeof a.call)return"object";return b}function l(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.base=function(e,d,h){for(var m=Array(arguments.length-2),k=2;k<arguments.length;k++)m[k-2]=arguments[k];return b.prototype[d].apply(e,m)}};function n(a){if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{const b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))}l(n,Error);n.prototype.name="CustomError";function p(a,b){a=a.split("%s");for(var c="",e=a.length-1,d=0;d<e;d++)c+=a[d]+(d<b.length?b[d]:"%s");n.call(this,c+a[e])}l(p,n);p.prototype.name="AssertionError";function q(a,b){throw new p("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1));};var r=null;function t(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var e=new Uint8Array(c),d=0;u(a,function(h){e[d++]=h});return e.subarray(0,d)}
function u(a,b){function c(L){for(;e<a.length;){var y=a.charAt(e++),z=r[y];if(null!=z)return z;if(!/^[\s\xa0]*$/.test(y))throw Error("Unknown base64 encoding at char: "+y);}return L}v();for(var e=0;;){var d=c(-1),h=c(0),m=c(64),k=c(64);if(64===k&&-1===d)break;b(d<<2|h>>4);64!=m&&(b(h<<4&240|m>>2),64!=k&&b(m<<6&192|k))}}
function v(){if(!r){r={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++)for(var e=a.concat(b[c].split("")),d=0;d<e.length;d++){var h=e[d];void 0===r[h]&&(r[h]=d)}}};function w(a){if(a.constructor===Uint8Array)return a;if(a.constructor===ArrayBuffer)return new Uint8Array(a);if("undefined"!=typeof Buffer&&a.constructor===Buffer)return new Uint8Array(a);if(a.constructor===Array)return new Uint8Array(a);if(a.constructor===String)return t(a);q("Type not convertible to Uint8Array.");return new Uint8Array(0)};function x(a){this.b=null;this.a=this.c=this.g=0;a&&A(this,a)}var B=[];function A(a,b){a.b=w(b);a.g=0;a.c=a.b.length;a.a=a.g}x.prototype.reset=function(){this.a=this.g};
x.prototype.f=function(){var a=this.b;var b=a[this.a];var c=b&127;if(128>b)return this.a+=1,c;b=a[this.a+1];c|=(b&127)<<7;if(128>b)return this.a+=2,c;b=a[this.a+2];c|=(b&127)<<14;if(128>b)return this.a+=3,c;b=a[this.a+3];c|=(b&127)<<21;if(128>b)return this.a+=4,c;b=a[this.a+4];c|=(b&15)<<28;if(128>b)return this.a+=5,c>>>0;this.a+=5;128<=a[this.a++]&&128<=a[this.a++]&&128<=a[this.a++]&&128<=a[this.a++]&&this.a++;return c};x.prototype.h=x.prototype.f;function C(a){if(B.length){var b=B.pop();a&&A(b,a);a=b}else a=new x(a);this.a=a;this.g=this.a.a;this.b=this.c=-1;this.f=!1}C.prototype.reset=function(){this.a.reset();this.b=this.c=-1};function D(a){var b=a.a;if(b.a==b.c)return!1;(b=a.f)||(b=a.a,b=0>b.a||b.a>b.c);if(b)return q("Decoder hit an error"),!1;a.g=a.a.a;var c=a.a.f();b=c>>>3;c&=7;if(0!=c&&5!=c&&1!=c&&2!=c&&3!=c&&4!=c)return q("Invalid wire type: %s (at position %s)",c,a.g),a.f=!0,!1;a.c=b;a.b=c;return!0}
function E(a){switch(a.b){case 0:if(0!=a.b)q("Invalid wire type for skipVarintField"),E(a);else{for(a=a.a;a.b[a.a]&128;)a.a++;a.a++}break;case 1:1!=a.b?(q("Invalid wire type for skipFixed64Field"),E(a)):(a=a.a,a.a+=8);break;case 2:if(2!=a.b)q("Invalid wire type for skipDelimitedField"),E(a);else{var b=a.a.f();a=a.a;a.a+=b}break;case 5:5!=a.b?(q("Invalid wire type for skipFixed32Field"),E(a)):(a=a.a,a.a+=4);break;case 3:b=a.c;do{if(!D(a)){q("Unmatched start-group tag: stream EOF");a.f=!0;break}if(4==
a.b){a.c!=b&&(q("Unmatched end-group tag"),a.f=!0);break}E(a)}while(1);break;default:q("Invalid wire encoding for field.")}};function F(){}var G="function"==typeof Uint8Array,H=Object.freeze?Object.freeze([]):[];function I(a,b){if(b<a.c){b+=a.f;var c=a.a[b];return c===H?a.a[b]=[]:c}if(a.b)return c=a.b[b],c===H?a.b[b]=[]:c}F.prototype.toString=function(){return this.a.toString()};function J(a){var b=a;a=K;b||(b=[]);this.f=-1;this.a=b;a:{if(b=this.a.length){--b;var c=this.a[b];if(!(null===c||"object"!=typeof c||"array"==g(c)||G&&c instanceof Uint8Array)){this.c=b- -1;this.b=c;break a}}this.c=Number.MAX_VALUE}if(a)for(b=0;b<a.length;b++)if(c=a[b],c<this.c)c+=-1,this.a[c]=this.a[c]||H;else{var e=this.c+-1;this.a[e]||(this.b=this.a[e]={});this.b[c]=this.b[c]||H}}l(J,F);var K=[1,3];function M(a){var b="";try{b=atob(a)}catch(e){}a=[];for(var c=0;c<b.length;c++)a.push(b.charCodeAt(c));b=new C(a);for(a=new J;D(b)&&4!=b.b;)switch(b.c){case 1:c=b.a.h();I(a,1).push(c);break;case 3:c=b.a.h();I(a,3).push(c);break;default:E(b)}return{variationIds:I(a,1),triggerVariationIds:I(a,3)}}var N=["parseClientVariations"],O=f;N[0]in O||"undefined"==typeof O.execScript||O.execScript("var "+N[0]);
for(var P;N.length&&(P=N.shift());)N.length||void 0===M?O[P]&&O[P]!==Object.prototype[P]?O=O[P]:O=O[P]={}:O[P]=M;}).call(gen);
// clang-format on
export function parseClientVariations(data) {
return gen.parseClientVariations(data);
}
// Copyright 2020 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.
const ClientVariations = goog.require('proto.variations.ClientVariations');
/**
* Parses a serialized ClientVariations proto into a human-readable format.
* @param {string} data The serialized ClientVariations proto contents, e.g.
* taken from the X-Client-Data header.
* @return {{
* variationIds: !Array<number>,
* triggerVariationIds: !Array<number>,
* }}
*/
export function parse(data) {
let decoded = '';
try {
decoded = atob(data);
} catch (e) {
// Nothing to do here -- it's fine to leave `decoded` empty if base64
// decoding fails.
}
const bytes = [];
for (let i = 0; i < decoded.length; i++) {
bytes.push(decoded.charCodeAt(i));
}
const parsed = ClientVariations.deserializeBinary(bytes);
return {
'variationIds': parsed.getVariationIdList(),
'triggerVariationIds': parsed.getTriggerVariationIdList(),
};
}
goog.exportSymbol('parseClientVariations', parse);
# Copyright 2020 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.
"""Updates the generated ClientVariations proto parser.
If this script happens not to run correctly in your environment, it should be
easy to perform the steps manually. This script simply builds a generated file,
and then copies it into the Chromium checkout, making some simple modifications.
"""
import argparse
import os
OUTPUT_TEMPLATE = """\
/* eslint-disable */
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Note: This is a generated file. Do not edit by hand. Instead, run
// components/variations/proto/devtools/update_client_variations_parser.py to
// update.
const gen = {};
// clang-format off
%s
// clang-format on
export function parseClientVariations(data) {
return gen.parseClientVariations(data);
}
"""
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'-t', '--target', default='Default',
help='the target build subdirectory under src/out/')
args = parser.parse_args()
cwd = os.path.dirname(__file__)
root = os.path.join(cwd, '..', '..', '..', '..')
build_dir = os.path.abspath(os.path.join(root, 'out', args.target))
cmd = 'autoninja -C %s components/variations/proto/devtools' % build_dir
os.system(cmd)
script_file = os.path.join(
build_dir, 'gen', 'components', 'variations', 'proto', 'devtools',
'client_variations_parser_gen.js')
with open(script_file, 'r') as f:
script = f.read().strip()
script = script.replace('call(this)', 'call(gen)')
output_file = os.path.abspath(
os.path.join(cwd, 'client_variations_parser.js'))
with open(output_file, 'w') as f:
f.write(OUTPUT_TEMPLATE % script)
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment