Commit e194fcc0 authored by Maxim Kolosovskiy's avatar Maxim Kolosovskiy Committed by Commit Bot

Revert "Move HTTP structured headers implementation to net/"

This reverts commit 14f8a1d5.

Reason for revert: caused complication error https://ci.chromium.org/p/chromium/builders/ci/ios-device-xcode-clang/122578



Original change's description:
> Move HTTP structured headers implementation to net/
> 
> Discussion: https://groups.google.com/a/chromium.org/forum/#!msg/loading-dev/dvEirmhddus/OFjNantxAwAJ
> 
> I made some non-trivial edits.
> 
>  - Changed the file name from structured_header.* to
>    structured_headers.* to be aligned with other classes in the
>    directory.
>  - Changed the namespace from structured_header to
>    structured_headers to match with the filename.
>  - Changed const base::StringPiece& params to base::StringPiece.
>  - Started using std::tie for equality operators.
> 
> This CL keeps blink/public/common/http/structured_header.h. I will
> change the existing call sites in a follow-up CL.
> 
> Bug: 1049936
> Change-Id: Iaa170e2a867470e4e523349c572e1ac4f1bedd17
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2041384
> Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
> Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
> Reviewed-by: Ian Clelland <iclelland@chromium.org>
> Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#740652}

TBR=rsleevi@chromium.org,kinuko@chromium.org,yhirano@chromium.org,iclelland@chromium.org

Change-Id: I96632bac2aa48eb89feea80445e35a28ec8121ed
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 1049936
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2051272Reviewed-by: default avatarMaxim Kolosovskiy <kolos@chromium.org>
Commit-Queue: Maxim Kolosovskiy <kolos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#740653}
parent 14f8a1d5
......@@ -304,8 +304,6 @@ component("net") {
"http/http_util.h",
"http/http_vary_data.cc",
"http/http_vary_data.h",
"http/structured_headers.cc",
"http/structured_headers.h",
"http/transport_security_state.h",
"http/transport_security_state_source.cc",
"http/transport_security_state_source.h",
......@@ -4282,7 +4280,6 @@ test("net_unittests") {
"http/http_vary_data_unittest.cc",
"http/mock_allow_http_auth_preferences.cc",
"http/mock_allow_http_auth_preferences.h",
"http/structured_headers_unittest.cc",
"http/transport_security_persister_unittest.cc",
"http/transport_security_state_unittest.cc",
"http/url_security_manager_unittest.cc",
......@@ -5341,17 +5338,6 @@ fuzzer_test("net_http_proxy_client_socket_fuzzer") {
dict = "data/fuzzer_dictionaries/net_http_proxy_client_socket_fuzzer.dict"
}
fuzzer_test("net_structured_headers_fuzzer") {
sources = [ "http/structured_headers_fuzzer.cc" ]
deps = [
":net_fuzzer_test_support",
":test_support",
"//base",
"//net",
]
seed_corpus = "data/fuzzer_data/structured_headers_corpus"
}
fuzzer_test("net_parse_url_hostname_to_address_fuzzer") {
sources = [ "base/parse_url_hostname_to_address_fuzzer.cc" ]
deps = [
......
// Copyright 2019 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 NET_HTTP_STRUCTURED_HEADERS_H_
#define NET_HTTP_STRUCTURED_HEADERS_H_
#include <algorithm>
#include <map>
#include <string>
#include <tuple>
#include <vector>
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "net/base/net_export.h"
namespace net {
namespace structured_headers {
// This file implements parsing of HTTP structured headers, as defined in
// https://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html.
//
// Both drafts 9 and 13 are currently supported. The major difference
// between the two drafts is in the various list formats: Draft 9 describes
// Parameterised lists and lists-of-lists, while draft 13 uses a single List
// syntax, whose members may be inner lists. There should be no ambiguity,
// however, as the code which calls this parser should be expecting only a
// single type for a given header.
//
// Currently supported data types are:
// Item:
// integer: 123
// string: "abc"
// token: abc
// byte sequence: *YWJj*
// Parameterised list: abc_123;a=1;b=2; cdef_456, ghi;q="9";r="w"
// List-of-lists: "foo";"bar", "baz", "bat"; "one"
// List: "foo", "bar", "It was the best of times."
// ("foo" "bar"), ("baz"), ("bat" "one"), ()
// abc;a=1;b=2; cde_456, (ghi jkl);q="9";r=w
//
// Functions are provided to parse each of these, which are intended to be
// called with the complete value of an HTTP header (that is, any
// sub-structure will be handled internally by the parser; the exported
// functions are not intended to be called on partial header strings.) Input
// values should be ASCII byte strings (non-ASCII characters should not be
// present in Structured Header values, and will cause the entire header to fail
// to parse.)
class NET_EXPORT Item {
public:
enum ItemType {
kNullType,
kIntegerType,
kFloatType,
kStringType,
kTokenType,
kByteSequenceType,
kBooleanType
};
Item();
explicit Item(int64_t value);
explicit Item(double value);
explicit Item(bool value);
// Constructors for string-like items: Strings, Tokens and Byte Sequences.
Item(const char* value, Item::ItemType type = kStringType);
// Item(StringPiece value, Item::ItemType type = kStringType);
Item(const std::string& value, Item::ItemType type = kStringType);
Item(std::string&& value, Item::ItemType type = kStringType);
NET_EXPORT friend bool operator==(const Item& lhs, const Item& rhs);
inline friend bool operator!=(const Item& lhs, const Item& rhs) {
return !(lhs == rhs);
}
bool is_null() const { return type_ == kNullType; }
bool is_integer() const { return type_ == kIntegerType; }
bool is_float() const { return type_ == kFloatType; }
bool is_string() const { return type_ == kStringType; }
bool is_token() const { return type_ == kTokenType; }
bool is_byte_sequence() const { return type_ == kByteSequenceType; }
bool is_boolean() const { return type_ == kBooleanType; }
int64_t GetInteger() const {
DCHECK_EQ(type_, kIntegerType);
return integer_value_;
}
double GetFloat() const {
DCHECK_EQ(type_, kFloatType);
return float_value_;
}
bool GetBoolean() const {
DCHECK_EQ(type_, kBooleanType);
return boolean_value_;
}
// TODO(iclelland): Split up accessors for String, Token and Byte Sequence.
const std::string& GetString() const {
DCHECK(type_ == kStringType || type_ == kTokenType ||
type_ == kByteSequenceType);
return string_value_;
}
ItemType Type() const { return type_; }
private:
ItemType type_ = kNullType;
// TODO(iclelland): Make this class more memory-efficient, replacing the
// values here with a union or std::variant (when available).
int64_t integer_value_ = 0;
std::string string_value_;
double float_value_;
bool boolean_value_;
};
// Holds a ParameterizedIdentifier (draft 9 only). The contained Item must be a
// Token, and there may be any number of parameters. Parameter ordering is not
// significant.
struct NET_EXPORT ParameterisedIdentifier {
using Parameters = std::map<std::string, Item>;
Item identifier;
Parameters params;
ParameterisedIdentifier(const ParameterisedIdentifier&);
ParameterisedIdentifier& operator=(const ParameterisedIdentifier&);
ParameterisedIdentifier(Item, const Parameters&);
~ParameterisedIdentifier();
};
inline bool operator==(const ParameterisedIdentifier& lhs,
const ParameterisedIdentifier& rhs) {
return std::tie(lhs.identifier, lhs.params) ==
std::tie(rhs.identifier, rhs.params);
}
// Holds a ParameterizedMember, which may be either an Inner List, or a single
// Item, with any number of parameters. Parameter ordering is significant.
struct NET_EXPORT ParameterizedMember {
using Parameters = std::vector<std::pair<std::string, Item>>;
std::vector<Item> member;
// If false, then |member| should only hold one Item.
bool member_is_inner_list;
Parameters params;
ParameterizedMember(const ParameterizedMember&);
ParameterizedMember& operator=(const ParameterizedMember&);
ParameterizedMember(std::vector<Item>, bool, const Parameters&);
// Shorthand constructor for a member which is an inner list.
ParameterizedMember(std::vector<Item>, const Parameters&);
// Shorthand constructor for a member which is a single Item.
ParameterizedMember(Item, const Parameters&);
~ParameterizedMember();
};
inline bool operator==(const ParameterizedMember& lhs,
const ParameterizedMember& rhs) {
return std::tie(lhs.member, lhs.member_is_inner_list, lhs.params) ==
std::tie(rhs.member, rhs.member_is_inner_list, rhs.params);
}
// Structured Headers Draft 09 Parameterised List.
using ParameterisedList = std::vector<ParameterisedIdentifier>;
// Structured Headers Draft 09 List of Lists.
using ListOfLists = std::vector<std::vector<Item>>;
// Structured Headers Draft 13 List.
using List = std::vector<ParameterizedMember>;
// Returns the result of parsing the header value as an Item, if it can be
// parsed as one, or nullopt if it cannot. Note that this uses the Draft 13
// parsing rules, and so applies tighter range limits to integers.
NET_EXPORT base::Optional<Item> ParseItem(base::StringPiece str);
// Returns the result of parsing the header value as a Parameterised List, if it
// can be parsed as one, or nullopt if it cannot. Note that parameter keys will
// be returned as strings, which are guaranteed to be ASCII-encoded. List items,
// as well as parameter values, will be returned as Items. This method uses the
// Draft 09 parsing rules for Items, so integers have the 64-bit int range.
// Structured-Headers Draft 09 only.
NET_EXPORT base::Optional<ParameterisedList> ParseParameterisedList(
base::StringPiece str);
// Returns the result of parsing the header value as a List of Lists, if it can
// be parsed as one, or nullopt if it cannot. Inner list items will be returned
// as Items. This method uses the Draft 09 parsing rules for Items, so integers
// have the 64-bit int range.
// Structured-Headers Draft 09 only.
NET_EXPORT base::Optional<ListOfLists> ParseListOfLists(base::StringPiece str);
// Returns the result of parsing the header value as a general List, if it can
// be parsed as one, or nullopt if it cannot.
// Structured-Headers Draft 13 only.
NET_EXPORT base::Optional<List> ParseList(base::StringPiece str);
// Serialization is implemented for Structured-Headers Draft 13 only.
NET_EXPORT base::Optional<std::string> SerializeItem(const Item& value);
NET_EXPORT base::Optional<std::string> SerializeList(const List& value);
} // namespace structured_headers
} // namespace net
#endif // NET_HTTP_STRUCTURED_HEADERS_H_
......@@ -63,6 +63,7 @@ jumbo_source_set("common") {
"frame/frame_policy.cc",
"frame/from_ad_state.cc",
"frame/user_activation_state.cc",
"http/structured_header.cc",
"indexeddb/indexed_db_default_mojom_traits.cc",
"indexeddb/indexeddb_key.cc",
"indexeddb/indexeddb_key_path.cc",
......@@ -157,6 +158,15 @@ jumbo_source_set("common") {
}
}
fuzzer_test("http_structured_header_fuzzer") {
sources = [ "http/structured_header_fuzzer.cc" ]
deps = [
":common",
"//third_party/blink/renderer/platform:blink_fuzzer_test_support",
]
seed_corpus = "http/structured_header_corpus"
}
test("blink_common_unittests") {
visibility = [ "*" ]
deps = [
......@@ -182,6 +192,7 @@ jumbo_source_set("common_unittests_sources") {
"feature_policy/feature_policy_unittest.cc",
"feature_policy/policy_value_unittest.cc",
"frame/user_activation_state_unittest.cc",
"http/structured_header_unittest.cc",
"indexeddb/indexeddb_key_unittest.cc",
"loader/mime_sniffing_throttle_unittest.cc",
"loader/throttling_url_loader_unittest.cc",
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/structured_headers.h"
#include "third_party/blink/public/common/http/structured_header.h"
#include <cmath>
#include <string>
......@@ -14,8 +14,8 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
namespace net {
namespace structured_headers {
namespace blink {
namespace http_structured_header {
namespace {
......@@ -60,7 +60,8 @@ class StructuredHeaderParser {
kDraft09,
kDraft13,
};
explicit StructuredHeaderParser(base::StringPiece str, DraftVersion version)
explicit StructuredHeaderParser(const base::StringPiece& str,
DraftVersion version)
: input_(str), version_(version) {
// [SH09] 4.2 Step 1.
// [SH13] 4.2 Step 2.
......@@ -696,7 +697,7 @@ ParameterisedIdentifier::ParameterisedIdentifier(Item id, const Parameters& ps)
: identifier(std::move(id)), params(ps) {}
ParameterisedIdentifier::~ParameterisedIdentifier() = default;
base::Optional<Item> ParseItem(base::StringPiece str) {
base::Optional<Item> ParseItem(const base::StringPiece& str) {
StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft13);
base::Optional<Item> item = parser.ReadItem();
if (item && parser.FinishParsing())
......@@ -705,7 +706,7 @@ base::Optional<Item> ParseItem(base::StringPiece str) {
}
base::Optional<ParameterisedList> ParseParameterisedList(
base::StringPiece str) {
const base::StringPiece& str) {
StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09);
base::Optional<ParameterisedList> param_list = parser.ReadParameterisedList();
if (param_list && parser.FinishParsing())
......@@ -713,7 +714,7 @@ base::Optional<ParameterisedList> ParseParameterisedList(
return base::nullopt;
}
base::Optional<ListOfLists> ParseListOfLists(base::StringPiece str) {
base::Optional<ListOfLists> ParseListOfLists(const base::StringPiece& str) {
StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09);
base::Optional<ListOfLists> list_of_lists = parser.ReadListOfLists();
if (list_of_lists && parser.FinishParsing())
......@@ -721,7 +722,7 @@ base::Optional<ListOfLists> ParseListOfLists(base::StringPiece str) {
return base::nullopt;
}
base::Optional<List> ParseList(base::StringPiece str) {
base::Optional<List> ParseList(const base::StringPiece& str) {
StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft13);
base::Optional<List> list = parser.ReadList();
if (list && parser.FinishParsing())
......@@ -743,5 +744,5 @@ base::Optional<std::string> SerializeList(const List& value) {
return base::nullopt;
}
} // namespace structured_headers
} // namespace net
} // namespace http_structured_header
} // namespace blink
......@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/structured_headers.h"
#include "third_party/blink/public/common/http/structured_header.h" // nogncheck
namespace net {
namespace structured_headers {
namespace blink {
namespace http_structured_header {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
base::StringPiece input(reinterpret_cast<const char*>(data), size);
......@@ -15,5 +15,5 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return 0;
}
} // namespace structured_headers
} // namespace net
} // namespace http_structured_header
} // namespace blink
......@@ -2,15 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/structured_headers.h"
#include "third_party/blink/public/common/http/structured_header.h"
#include <limits>
#include <string>
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace structured_headers {
namespace blink {
namespace http_structured_header {
namespace {
// Helpers to make test cases clearer
......@@ -996,5 +996,5 @@ TEST(StructuredHeaderTest, UnserializableLists) {
}
}
} // namespace structured_headers
} // namespace net
} // namespace http_structured_header
} // namespace blink
......@@ -5,24 +5,203 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_HTTP_STRUCTURED_HEADER_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_HTTP_STRUCTURED_HEADER_H_
#include "net/http/structured_headers.h"
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "third_party/blink/public/common/common_export.h"
namespace blink {
namespace http_structured_header {
using net::structured_headers::Item;
using net::structured_headers::List;
using net::structured_headers::ListOfLists;
using net::structured_headers::ParameterisedIdentifier;
using net::structured_headers::ParameterisedList;
using net::structured_headers::ParameterizedMember;
using net::structured_headers::ParseItem;
using net::structured_headers::ParseList;
using net::structured_headers::ParseListOfLists;
using net::structured_headers::ParseParameterisedList;
using net::structured_headers::SerializeItem;
using net::structured_headers::SerializeList;
// This file implements parsing of HTTP structured headers, as defined in
// https://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html.
//
// Both drafts 9 and 13 are currently supported. The major difference
// between the two drafts is in the various list formats: Draft 9 describes
// Parameterised lists and lists-of-lists, while draft 13 uses a single List
// syntax, whose members may be inner lists. There should be no ambiguity,
// however, as the code which calls this parser should be expecting only a
// single type for a given header.
//
// Currently supported data types are:
// Item:
// integer: 123
// string: "abc"
// token: abc
// byte sequence: *YWJj*
// Parameterised list: abc_123;a=1;b=2; cdef_456, ghi;q="9";r="w"
// List-of-lists: "foo";"bar", "baz", "bat"; "one"
// List: "foo", "bar", "It was the best of times."
// ("foo" "bar"), ("baz"), ("bat" "one"), ()
// abc;a=1;b=2; cde_456, (ghi jkl);q="9";r=w
//
// Functions are provided to parse each of these, which are intended to be
// called with the complete value of an HTTP header (that is, any
// sub-structure will be handled internally by the parser; the exported
// functions are not intended to be called on partial header strings.) Input
// values should be ASCII byte strings (non-ASCII characters should not be
// present in Structured Header values, and will cause the entire header to fail
// to parse.)
class BLINK_COMMON_EXPORT Item {
public:
enum ItemType {
kNullType,
kIntegerType,
kFloatType,
kStringType,
kTokenType,
kByteSequenceType,
kBooleanType
};
Item();
explicit Item(int64_t value);
explicit Item(double value);
explicit Item(bool value);
// Constructors for string-like items: Strings, Tokens and Byte Sequences.
Item(const char* value, Item::ItemType type = kStringType);
// Item(StringPiece value, Item::ItemType type = kStringType);
Item(const std::string& value, Item::ItemType type = kStringType);
Item(std::string&& value, Item::ItemType type = kStringType);
BLINK_COMMON_EXPORT friend bool operator==(const Item& lhs, const Item& rhs);
inline friend bool operator!=(const Item& lhs, const Item& rhs) {
return !(lhs == rhs);
}
bool is_null() const { return type_ == kNullType; }
bool is_integer() const { return type_ == kIntegerType; }
bool is_float() const { return type_ == kFloatType; }
bool is_string() const { return type_ == kStringType; }
bool is_token() const { return type_ == kTokenType; }
bool is_byte_sequence() const { return type_ == kByteSequenceType; }
bool is_boolean() const { return type_ == kBooleanType; }
int64_t GetInteger() const {
DCHECK_EQ(type_, kIntegerType);
return integer_value_;
}
double GetFloat() const {
DCHECK_EQ(type_, kFloatType);
return float_value_;
}
bool GetBoolean() const {
DCHECK_EQ(type_, kBooleanType);
return boolean_value_;
}
// TODO(iclelland): Split up accessors for String, Token and Byte Sequence.
const std::string& GetString() const {
DCHECK(type_ == kStringType || type_ == kTokenType ||
type_ == kByteSequenceType);
return string_value_;
}
ItemType Type() const { return type_; }
private:
ItemType type_ = kNullType;
// TODO(iclelland): Make this class more memory-efficient, replacing the
// values here with a union or std::variant (when available).
int64_t integer_value_ = 0;
std::string string_value_;
double float_value_;
bool boolean_value_;
};
// Holds a ParameterizedIdentifier (draft 9 only). The contained Item must be a
// Token, and there may be any number of parameters. Parameter ordering is not
// significant.
struct BLINK_COMMON_EXPORT ParameterisedIdentifier {
using Parameters = std::map<std::string, Item>;
Item identifier;
Parameters params;
ParameterisedIdentifier(const ParameterisedIdentifier&);
ParameterisedIdentifier& operator=(const ParameterisedIdentifier&);
ParameterisedIdentifier(Item, const Parameters&);
~ParameterisedIdentifier();
};
inline bool operator==(const ParameterisedIdentifier& lhs,
const ParameterisedIdentifier& rhs) {
return lhs.identifier == rhs.identifier && lhs.params == rhs.params;
}
// Holds a ParameterizedMember, which may be either an Inner List, or a single
// Item, with any number of parameters. Parameter ordering is significant.
struct BLINK_COMMON_EXPORT ParameterizedMember {
using Parameters = std::vector<std::pair<std::string, Item>>;
std::vector<Item> member;
// If false, then |member| should only hold one Item.
bool member_is_inner_list;
Parameters params;
ParameterizedMember(const ParameterizedMember&);
ParameterizedMember& operator=(const ParameterizedMember&);
ParameterizedMember(std::vector<Item>, bool, const Parameters&);
// Shorthand constructor for a member which is an inner list.
ParameterizedMember(std::vector<Item>, const Parameters&);
// Shorthand constructor for a member which is a single Item.
ParameterizedMember(Item, const Parameters&);
~ParameterizedMember();
};
inline bool operator==(const ParameterizedMember& lhs,
const ParameterizedMember& rhs) {
return lhs.member == rhs.member &&
lhs.member_is_inner_list == rhs.member_is_inner_list &&
lhs.params == rhs.params;
}
// Structured Headers Draft 09 Parameterised List.
using ParameterisedList = std::vector<ParameterisedIdentifier>;
// Structured Headers Draft 09 List of Lists.
using ListOfLists = std::vector<std::vector<Item>>;
// Structured Headers Draft 13 List.
using List = std::vector<ParameterizedMember>;
// Returns the result of parsing the header value as an Item, if it can be
// parsed as one, or nullopt if it cannot. Note that this uses the Draft 13
// parsing rules, and so applies tighter range limits to integers.
BLINK_COMMON_EXPORT base::Optional<Item> ParseItem(
const base::StringPiece& str);
// Returns the result of parsing the header value as a Parameterised List, if it
// can be parsed as one, or nullopt if it cannot. Note that parameter keys will
// be returned as strings, which are guaranteed to be ASCII-encoded. List items,
// as well as parameter values, will be returned as Items. This method uses the
// Draft 09 parsing rules for Items, so integers have the 64-bit int range.
// Structured-Headers Draft 09 only.
BLINK_COMMON_EXPORT base::Optional<ParameterisedList> ParseParameterisedList(
const base::StringPiece& str);
// Returns the result of parsing the header value as a List of Lists, if it can
// be parsed as one, or nullopt if it cannot. Inner list items will be returned
// as Items. This method uses the Draft 09 parsing rules for Items, so integers
// have the 64-bit int range.
// Structured-Headers Draft 09 only.
BLINK_COMMON_EXPORT base::Optional<ListOfLists> ParseListOfLists(
const base::StringPiece& str);
// Returns the result of parsing the header value as a general List, if it can
// be parsed as one, or nullopt if it cannot.
// Structured-Headers Draft 13 only.
BLINK_COMMON_EXPORT base::Optional<List> ParseList(
const base::StringPiece& str);
// Serialization is implemented for Structured-Headers Draft 13 only.
BLINK_COMMON_EXPORT base::Optional<std::string> SerializeItem(
const Item& value);
BLINK_COMMON_EXPORT base::Optional<std::string> SerializeList(
const List& value);
} // namespace http_structured_header
} // namespace blink
......
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