OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/browser/gpu/gpu_blacklist.h" | |
6 | |
7 #include "base/command_line.h" | |
8 #include "base/json/json_reader.h" | |
9 #include "base/logging.h" | |
10 #include "base/string_number_conversions.h" | |
11 #include "base/string_split.h" | |
12 #include "base/string_util.h" | |
13 #include "base/sys_info.h" | |
14 #include "base/version.h" | |
15 #include "content/browser/gpu/gpu_util.h" | |
16 #include "content/public/common/content_switches.h" | |
17 #include "content/public/common/gpu_info.h" | |
18 | |
19 using content::GpuFeatureType; | |
20 | |
21 namespace { | |
22 | |
23 // Encode a date as Version, where [0] is year, [1] is month, and [2] is day. | |
24 void GetDateFromString(const std::string& date_string, Version* version) { | |
25 // TODO(zmo): verify if in Windows registry, driver dates are always in the | |
26 // format of "mm-dd-yyyy". | |
27 std::vector<std::string> pieces; | |
28 base::SplitString(date_string, '-', &pieces); | |
29 if (pieces.size() != 3) { | |
30 *version = Version(); | |
31 return; | |
32 } | |
33 std::string date_as_version_string = pieces[2]; | |
34 for (size_t i = 0; i < 2; ++i) { | |
35 date_as_version_string += "."; | |
36 date_as_version_string += pieces[i]; | |
37 } | |
38 *version = Version(date_as_version_string); | |
39 } | |
40 | |
41 // We assume the input format is major.minor, and we treat major version | |
42 // as numerical and minor as lexical. | |
43 // Otherwise we simply return the original string. | |
44 // For example, if input numerical is 8.103, returned lexical is 8.1.0.3. | |
45 std::string NumericalToLexical(const std::string& numerical) { | |
46 std::string lexical; | |
47 bool valid = true; | |
48 size_t pos = numerical.find_first_of('.'); | |
49 if (pos != std::string::npos && pos + 1 < numerical.length()) { | |
50 lexical = numerical.substr(0, pos); | |
51 for (size_t i = pos + 1; i < numerical.length(); ++i) { | |
52 if (!IsAsciiDigit(numerical[i])) { | |
53 valid = false; | |
54 break; | |
55 } | |
56 lexical += '.'; | |
57 lexical += numerical[i]; | |
58 } | |
59 } else { | |
60 valid = false; | |
61 } | |
62 if (valid) | |
63 return lexical; | |
64 return numerical; | |
65 } | |
66 | |
67 bool GpuUnmatched(uint32 vendor_id, const std::vector<uint32>& device_id_list, | |
68 const content::GPUInfo::GPUDevice& gpu) { | |
69 if (vendor_id == 0) | |
70 return false; | |
71 if (vendor_id != gpu.vendor_id) | |
72 return true; | |
73 bool device_specified = false; | |
74 for (size_t i = 0; i < device_id_list.size(); ++i) { | |
75 if (device_id_list[i] == 0) | |
76 continue; | |
77 if (device_id_list[i] == gpu.device_id) | |
78 return false; | |
79 device_specified = true; | |
80 } | |
81 return device_specified; | |
82 } | |
83 | |
84 const char kMultiGpuStyleStringAMDSwitchable[] = "amd_switchable"; | |
85 const char kMultiGpuStyleStringOptimus[] = "optimus"; | |
86 | |
87 const char kMultiGpuCategoryStringPrimary[] = "primary"; | |
88 const char kMultiGpuCategoryStringSecondary[] = "secondary"; | |
89 const char kMultiGpuCategoryStringAny[] = "any"; | |
90 | |
91 const char kVersionStyleStringNumerical[] = "numerical"; | |
92 const char kVersionStyleStringLexical[] = "lexical"; | |
93 | |
94 } // namespace anonymous | |
95 | |
96 GpuBlacklist::VersionInfo::VersionInfo( | |
97 const std::string& version_op, | |
98 const std::string& version_style, | |
99 const std::string& version_string, | |
100 const std::string& version_string2) | |
101 : version_style_(kVersionStyleNumerical) { | |
102 op_ = StringToNumericOp(version_op); | |
103 if (op_ == kUnknown || op_ == kAny) | |
104 return; | |
105 version_style_ = StringToVersionStyle(version_style); | |
106 std::string processed_version_string, processed_version_string2; | |
107 if (version_style_ == kVersionStyleLexical) { | |
108 processed_version_string = NumericalToLexical(version_string); | |
109 processed_version_string2 = NumericalToLexical(version_string2); | |
110 } else { | |
111 processed_version_string = version_string; | |
112 processed_version_string2 = version_string2; | |
113 } | |
114 version_.reset(new Version(processed_version_string)); | |
115 if (!version_->IsValid()) { | |
116 op_ = kUnknown; | |
117 return; | |
118 } | |
119 if (op_ == kBetween) { | |
120 version2_.reset(new Version(processed_version_string2)); | |
121 if (!version2_->IsValid()) | |
122 op_ = kUnknown; | |
123 } | |
124 } | |
125 | |
126 GpuBlacklist::VersionInfo::~VersionInfo() { | |
127 } | |
128 | |
129 bool GpuBlacklist::VersionInfo::Contains(const Version& version) const { | |
130 if (op_ == kUnknown) | |
131 return false; | |
132 if (op_ == kAny) | |
133 return true; | |
134 if (op_ == kEQ) { | |
135 // Handles cases where 10.6 is considered as containing 10.6.*. | |
136 const std::vector<uint16>& components_reference = version_->components(); | |
137 const std::vector<uint16>& components = version.components(); | |
138 for (size_t i = 0; i < components_reference.size(); ++i) { | |
139 if (i >= components.size() && components_reference[i] != 0) | |
140 return false; | |
141 if (components[i] != components_reference[i]) | |
142 return false; | |
143 } | |
144 return true; | |
145 } | |
146 int relation = version.CompareTo(*version_); | |
147 if (op_ == kEQ) | |
148 return (relation == 0); | |
149 else if (op_ == kLT) | |
150 return (relation < 0); | |
151 else if (op_ == kLE) | |
152 return (relation <= 0); | |
153 else if (op_ == kGT) | |
154 return (relation > 0); | |
155 else if (op_ == kGE) | |
156 return (relation >= 0); | |
157 // op_ == kBetween | |
158 if (relation < 0) | |
159 return false; | |
160 return version.CompareTo(*version2_) <= 0; | |
161 } | |
162 | |
163 bool GpuBlacklist::VersionInfo::IsValid() const { | |
164 return (op_ != kUnknown && version_style_ != kVersionStyleUnknown); | |
165 } | |
166 | |
167 bool GpuBlacklist::VersionInfo::IsLexical() const { | |
168 return version_style_ == kVersionStyleLexical; | |
169 } | |
170 | |
171 // static | |
172 GpuBlacklist::VersionInfo::VersionStyle | |
173 GpuBlacklist::VersionInfo::StringToVersionStyle( | |
174 const std::string& version_style) { | |
175 if (version_style.empty() || version_style == kVersionStyleStringNumerical) | |
176 return kVersionStyleNumerical; | |
177 if (version_style == kVersionStyleStringLexical) | |
178 return kVersionStyleLexical; | |
179 return kVersionStyleUnknown; | |
180 } | |
181 | |
182 GpuBlacklist::OsInfo::OsInfo(const std::string& os, | |
183 const std::string& version_op, | |
184 const std::string& version_string, | |
185 const std::string& version_string2) { | |
186 type_ = StringToOsType(os); | |
187 if (type_ != kOsUnknown) { | |
188 version_info_.reset( | |
189 new VersionInfo(version_op, "", version_string, version_string2)); | |
190 } | |
191 } | |
192 | |
193 GpuBlacklist::OsInfo::~OsInfo() {} | |
194 | |
195 bool GpuBlacklist::OsInfo::Contains(OsType type, | |
196 const Version& version) const { | |
197 if (!IsValid()) | |
198 return false; | |
199 if (type_ != type && type_ != kOsAny) | |
200 return false; | |
201 return version_info_->Contains(version); | |
202 } | |
203 | |
204 bool GpuBlacklist::OsInfo::IsValid() const { | |
205 return type_ != kOsUnknown && version_info_->IsValid(); | |
206 } | |
207 | |
208 GpuBlacklist::OsType GpuBlacklist::OsInfo::type() const { | |
209 return type_; | |
210 } | |
211 | |
212 GpuBlacklist::OsType GpuBlacklist::OsInfo::StringToOsType( | |
213 const std::string& os) { | |
214 if (os == "win") | |
215 return kOsWin; | |
216 else if (os == "macosx") | |
217 return kOsMacosx; | |
218 else if (os == "linux") | |
219 return kOsLinux; | |
220 else if (os == "chromeos") | |
221 return kOsChromeOS; | |
222 else if (os == "any") | |
223 return kOsAny; | |
224 return kOsUnknown; | |
225 } | |
226 | |
227 GpuBlacklist::StringInfo::StringInfo(const std::string& string_op, | |
228 const std::string& string_value) { | |
229 op_ = StringToOp(string_op); | |
230 value_ = StringToLowerASCII(string_value); | |
231 } | |
232 | |
233 bool GpuBlacklist::StringInfo::Contains(const std::string& value) const { | |
234 std::string my_value = StringToLowerASCII(value); | |
235 switch (op_) { | |
236 case kContains: | |
237 return strstr(my_value.c_str(), value_.c_str()) != NULL; | |
238 case kBeginWith: | |
239 return StartsWithASCII(my_value, value_, false); | |
240 case kEndWith: | |
241 return EndsWith(my_value, value_, false); | |
242 case kEQ: | |
243 return value_ == my_value; | |
244 default: | |
245 return false; | |
246 } | |
247 } | |
248 | |
249 bool GpuBlacklist::StringInfo::IsValid() const { | |
250 return op_ != kUnknown; | |
251 } | |
252 | |
253 GpuBlacklist::StringInfo::Op GpuBlacklist::StringInfo::StringToOp( | |
254 const std::string& string_op) { | |
255 if (string_op == "=") | |
256 return kEQ; | |
257 else if (string_op == "contains") | |
258 return kContains; | |
259 else if (string_op == "beginwith") | |
260 return kBeginWith; | |
261 else if (string_op == "endwith") | |
262 return kEndWith; | |
263 return kUnknown; | |
264 } | |
265 | |
266 GpuBlacklist::FloatInfo::FloatInfo(const std::string& float_op, | |
267 const std::string& float_value, | |
268 const std::string& float_value2) | |
269 : op_(kUnknown), | |
270 value_(0.f), | |
271 value2_(0.f) { | |
272 double dvalue = 0; | |
273 if (!base::StringToDouble(float_value, &dvalue)) { | |
274 op_ = kUnknown; | |
275 return; | |
276 } | |
277 value_ = static_cast<float>(dvalue); | |
278 op_ = StringToNumericOp(float_op); | |
279 if (op_ == kBetween) { | |
280 if (!base::StringToDouble(float_value2, &dvalue)) { | |
281 op_ = kUnknown; | |
282 return; | |
283 } | |
284 value2_ = static_cast<float>(dvalue); | |
285 } | |
286 } | |
287 | |
288 bool GpuBlacklist::FloatInfo::Contains(float value) const { | |
289 if (op_ == kUnknown) | |
290 return false; | |
291 if (op_ == kAny) | |
292 return true; | |
293 if (op_ == kEQ) | |
294 return (value == value_); | |
295 if (op_ == kLT) | |
296 return (value < value_); | |
297 if (op_ == kLE) | |
298 return (value <= value_); | |
299 if (op_ == kGT) | |
300 return (value > value_); | |
301 if (op_ == kGE) | |
302 return (value >= value_); | |
303 DCHECK(op_ == kBetween); | |
304 return ((value_ <= value && value <= value2_) || | |
305 (value2_ <= value && value <= value_)); | |
306 } | |
307 | |
308 bool GpuBlacklist::FloatInfo::IsValid() const { | |
309 return op_ != kUnknown; | |
310 } | |
311 | |
312 // static | |
313 GpuBlacklist::ScopedGpuBlacklistEntry | |
314 GpuBlacklist::GpuBlacklistEntry::GetGpuBlacklistEntryFromValue( | |
315 const DictionaryValue* value, bool top_level) { | |
316 DCHECK(value); | |
317 ScopedGpuBlacklistEntry entry(new GpuBlacklistEntry()); | |
318 | |
319 size_t dictionary_entry_count = 0; | |
320 | |
321 if (top_level) { | |
322 uint32 id; | |
323 if (!value->GetInteger("id", reinterpret_cast<int*>(&id)) || | |
324 !entry->SetId(id)) { | |
325 LOG(WARNING) << "Malformed id entry " << entry->id(); | |
326 return NULL; | |
327 } | |
328 dictionary_entry_count++; | |
329 | |
330 bool disabled; | |
331 if (value->GetBoolean("disabled", &disabled)) { | |
332 entry->SetDisabled(disabled); | |
333 dictionary_entry_count++; | |
334 } | |
335 } | |
336 | |
337 std::string description; | |
338 if (value->GetString("description", &description)) { | |
339 entry->description_ = description; | |
340 dictionary_entry_count++; | |
341 } else { | |
342 entry->description_ = "The GPU is unavailable for an unexplained reason."; | |
343 } | |
344 | |
345 const ListValue* cr_bugs; | |
346 if (value->GetList("cr_bugs", &cr_bugs)) { | |
347 for (size_t i = 0; i < cr_bugs->GetSize(); ++i) { | |
348 int bug_id; | |
349 if (cr_bugs->GetInteger(i, &bug_id)) { | |
350 entry->cr_bugs_.push_back(bug_id); | |
351 } else { | |
352 LOG(WARNING) << "Malformed cr_bugs entry " << entry->id(); | |
353 return NULL; | |
354 } | |
355 } | |
356 dictionary_entry_count++; | |
357 } | |
358 | |
359 const ListValue* webkit_bugs; | |
360 if (value->GetList("webkit_bugs", &webkit_bugs)) { | |
361 for (size_t i = 0; i < webkit_bugs->GetSize(); ++i) { | |
362 int bug_id; | |
363 if (webkit_bugs->GetInteger(i, &bug_id)) { | |
364 entry->webkit_bugs_.push_back(bug_id); | |
365 } else { | |
366 LOG(WARNING) << "Malformed webkit_bugs entry " << entry->id(); | |
367 return NULL; | |
368 } | |
369 } | |
370 dictionary_entry_count++; | |
371 } | |
372 | |
373 const DictionaryValue* os_value = NULL; | |
374 if (value->GetDictionary("os", &os_value)) { | |
375 std::string os_type; | |
376 std::string os_version_op = "any"; | |
377 std::string os_version_string; | |
378 std::string os_version_string2; | |
379 os_value->GetString("type", &os_type); | |
380 const DictionaryValue* os_version_value = NULL; | |
381 if (os_value->GetDictionary("version", &os_version_value)) { | |
382 os_version_value->GetString("op", &os_version_op); | |
383 os_version_value->GetString("number", &os_version_string); | |
384 os_version_value->GetString("number2", &os_version_string2); | |
385 } | |
386 if (!entry->SetOsInfo(os_type, os_version_op, os_version_string, | |
387 os_version_string2)) { | |
388 LOG(WARNING) << "Malformed os entry " << entry->id(); | |
389 return NULL; | |
390 } | |
391 dictionary_entry_count++; | |
392 } | |
393 | |
394 std::string vendor_id; | |
395 if (value->GetString("vendor_id", &vendor_id)) { | |
396 if (!entry->SetVendorId(vendor_id)) { | |
397 LOG(WARNING) << "Malformed vendor_id entry " << entry->id(); | |
398 return NULL; | |
399 } | |
400 dictionary_entry_count++; | |
401 } | |
402 | |
403 const ListValue* device_id_list; | |
404 if (value->GetList("device_id", &device_id_list)) { | |
405 for (size_t i = 0; i < device_id_list->GetSize(); ++i) { | |
406 std::string device_id; | |
407 if (!device_id_list->GetString(i, &device_id) || | |
408 !entry->AddDeviceId(device_id)) { | |
409 LOG(WARNING) << "Malformed device_id entry " << entry->id(); | |
410 return NULL; | |
411 } | |
412 } | |
413 dictionary_entry_count++; | |
414 } | |
415 | |
416 std::string multi_gpu_style; | |
417 if (value->GetString("multi_gpu_style", &multi_gpu_style)) { | |
418 if (!entry->SetMultiGpuStyle(multi_gpu_style)) { | |
419 LOG(WARNING) << "Malformed multi_gpu_style entry " << entry->id(); | |
420 return NULL; | |
421 } | |
422 dictionary_entry_count++; | |
423 } | |
424 | |
425 std::string multi_gpu_category; | |
426 if (value->GetString("multi_gpu_category", &multi_gpu_category)) { | |
427 if (!entry->SetMultiGpuCategory(multi_gpu_category)) { | |
428 LOG(WARNING) << "Malformed multi_gpu_category entry " << entry->id(); | |
429 return NULL; | |
430 } | |
431 dictionary_entry_count++; | |
432 } | |
433 | |
434 const DictionaryValue* driver_vendor_value = NULL; | |
435 if (value->GetDictionary("driver_vendor", &driver_vendor_value)) { | |
436 std::string vendor_op; | |
437 std::string vendor_value; | |
438 driver_vendor_value->GetString("op", &vendor_op); | |
439 driver_vendor_value->GetString("value", &vendor_value); | |
440 if (!entry->SetDriverVendorInfo(vendor_op, vendor_value)) { | |
441 LOG(WARNING) << "Malformed driver_vendor entry " << entry->id(); | |
442 return NULL; | |
443 } | |
444 dictionary_entry_count++; | |
445 } | |
446 | |
447 const DictionaryValue* driver_version_value = NULL; | |
448 if (value->GetDictionary("driver_version", &driver_version_value)) { | |
449 std::string driver_version_op = "any"; | |
450 std::string driver_version_style; | |
451 std::string driver_version_string; | |
452 std::string driver_version_string2; | |
453 driver_version_value->GetString("op", &driver_version_op); | |
454 driver_version_value->GetString("style", &driver_version_style); | |
455 driver_version_value->GetString("number", &driver_version_string); | |
456 driver_version_value->GetString("number2", &driver_version_string2); | |
457 if (!entry->SetDriverVersionInfo(driver_version_op, | |
458 driver_version_style, | |
459 driver_version_string, | |
460 driver_version_string2)) { | |
461 LOG(WARNING) << "Malformed driver_version entry " << entry->id(); | |
462 return NULL; | |
463 } | |
464 dictionary_entry_count++; | |
465 } | |
466 | |
467 const DictionaryValue* driver_date_value = NULL; | |
468 if (value->GetDictionary("driver_date", &driver_date_value)) { | |
469 std::string driver_date_op = "any"; | |
470 std::string driver_date_string; | |
471 std::string driver_date_string2; | |
472 driver_date_value->GetString("op", &driver_date_op); | |
473 driver_date_value->GetString("number", &driver_date_string); | |
474 driver_date_value->GetString("number2", &driver_date_string2); | |
475 if (!entry->SetDriverDateInfo(driver_date_op, driver_date_string, | |
476 driver_date_string2)) { | |
477 LOG(WARNING) << "Malformed driver_date entry " << entry->id(); | |
478 return NULL; | |
479 } | |
480 dictionary_entry_count++; | |
481 } | |
482 | |
483 const DictionaryValue* gl_vendor_value = NULL; | |
484 if (value->GetDictionary("gl_vendor", &gl_vendor_value)) { | |
485 std::string vendor_op; | |
486 std::string vendor_value; | |
487 gl_vendor_value->GetString("op", &vendor_op); | |
488 gl_vendor_value->GetString("value", &vendor_value); | |
489 if (!entry->SetGLVendorInfo(vendor_op, vendor_value)) { | |
490 LOG(WARNING) << "Malformed gl_vendor entry " << entry->id(); | |
491 return NULL; | |
492 } | |
493 dictionary_entry_count++; | |
494 } | |
495 | |
496 const DictionaryValue* gl_renderer_value = NULL; | |
497 if (value->GetDictionary("gl_renderer", &gl_renderer_value)) { | |
498 std::string renderer_op; | |
499 std::string renderer_value; | |
500 gl_renderer_value->GetString("op", &renderer_op); | |
501 gl_renderer_value->GetString("value", &renderer_value); | |
502 if (!entry->SetGLRendererInfo(renderer_op, renderer_value)) { | |
503 LOG(WARNING) << "Malformed gl_renderer entry " << entry->id(); | |
504 return NULL; | |
505 } | |
506 dictionary_entry_count++; | |
507 } | |
508 | |
509 const DictionaryValue* perf_graphics_value = NULL; | |
510 if (value->GetDictionary("perf_graphics", &perf_graphics_value)) { | |
511 std::string op; | |
512 std::string float_value; | |
513 std::string float_value2; | |
514 perf_graphics_value->GetString("op", &op); | |
515 perf_graphics_value->GetString("value", &float_value); | |
516 perf_graphics_value->GetString("value2", &float_value2); | |
517 if (!entry->SetPerfGraphicsInfo(op, float_value, float_value2)) { | |
518 LOG(WARNING) << "Malformed perf_graphics entry " << entry->id(); | |
519 return NULL; | |
520 } | |
521 dictionary_entry_count++; | |
522 } | |
523 | |
524 const DictionaryValue* perf_gaming_value = NULL; | |
525 if (value->GetDictionary("perf_gaming", &perf_gaming_value)) { | |
526 std::string op; | |
527 std::string float_value; | |
528 std::string float_value2; | |
529 perf_gaming_value->GetString("op", &op); | |
530 perf_gaming_value->GetString("value", &float_value); | |
531 perf_gaming_value->GetString("value2", &float_value2); | |
532 if (!entry->SetPerfGamingInfo(op, float_value, float_value2)) { | |
533 LOG(WARNING) << "Malformed perf_gaming entry " << entry->id(); | |
534 return NULL; | |
535 } | |
536 dictionary_entry_count++; | |
537 } | |
538 | |
539 const DictionaryValue* perf_overall_value = NULL; | |
540 if (value->GetDictionary("perf_overall", &perf_overall_value)) { | |
541 std::string op; | |
542 std::string float_value; | |
543 std::string float_value2; | |
544 perf_overall_value->GetString("op", &op); | |
545 perf_overall_value->GetString("value", &float_value); | |
546 perf_overall_value->GetString("value2", &float_value2); | |
547 if (!entry->SetPerfOverallInfo(op, float_value, float_value2)) { | |
548 LOG(WARNING) << "Malformed perf_overall entry " << entry->id(); | |
549 return NULL; | |
550 } | |
551 dictionary_entry_count++; | |
552 } | |
553 | |
554 if (top_level) { | |
555 const ListValue* blacklist_value = NULL; | |
556 if (!value->GetList("blacklist", &blacklist_value)) { | |
557 LOG(WARNING) << "Malformed blacklist entry " << entry->id(); | |
558 return NULL; | |
559 } | |
560 std::vector<std::string> blacklist; | |
561 for (size_t i = 0; i < blacklist_value->GetSize(); ++i) { | |
562 std::string feature; | |
563 if (blacklist_value->GetString(i, &feature)) { | |
564 blacklist.push_back(feature); | |
565 } else { | |
566 LOG(WARNING) << "Malformed blacklist entry " << entry->id(); | |
567 return NULL; | |
568 } | |
569 } | |
570 if (!entry->SetBlacklistedFeatures(blacklist)) { | |
571 LOG(WARNING) << "Malformed blacklist entry " << entry->id(); | |
572 return NULL; | |
573 } | |
574 dictionary_entry_count++; | |
575 } | |
576 | |
577 if (top_level) { | |
578 const ListValue* exception_list_value = NULL; | |
579 if (value->GetList("exceptions", &exception_list_value)) { | |
580 for (size_t i = 0; i < exception_list_value->GetSize(); ++i) { | |
581 const DictionaryValue* exception_value = NULL; | |
582 if (!exception_list_value->GetDictionary(i, &exception_value)) { | |
583 LOG(WARNING) << "Malformed exceptions entry " << entry->id(); | |
584 return NULL; | |
585 } | |
586 ScopedGpuBlacklistEntry exception( | |
587 GetGpuBlacklistEntryFromValue(exception_value, false)); | |
588 if (exception == NULL) { | |
589 LOG(WARNING) << "Malformed exceptions entry " << entry->id(); | |
590 return NULL; | |
591 } | |
592 if (exception->contains_unknown_fields_) { | |
593 LOG(WARNING) << "Exception with unknown fields " << entry->id(); | |
594 entry->contains_unknown_fields_ = true; | |
595 } else { | |
596 entry->AddException(exception); | |
597 } | |
598 } | |
599 dictionary_entry_count++; | |
600 } | |
601 | |
602 const DictionaryValue* browser_version_value = NULL; | |
603 // browser_version is processed in LoadGpuBlacklist(). | |
604 if (value->GetDictionary("browser_version", &browser_version_value)) | |
605 dictionary_entry_count++; | |
606 } | |
607 | |
608 if (value->size() != dictionary_entry_count) { | |
609 LOG(WARNING) << "Entry with unknown fields " << entry->id(); | |
610 entry->contains_unknown_fields_ = true; | |
611 } | |
612 return entry; | |
613 } | |
614 | |
615 GpuBlacklist::GpuBlacklistEntry::GpuBlacklistEntry() | |
616 : id_(0), | |
617 disabled_(false), | |
618 vendor_id_(0), | |
619 multi_gpu_style_(kMultiGpuStyleNone), | |
620 multi_gpu_category_(kMultiGpuCategoryPrimary), | |
621 feature_type_(content::GPU_FEATURE_TYPE_UNKNOWN), | |
622 contains_unknown_fields_(false), | |
623 contains_unknown_features_(false) { | |
624 } | |
625 | |
626 GpuBlacklist::GpuBlacklistEntry::~GpuBlacklistEntry() { } | |
627 | |
628 bool GpuBlacklist::GpuBlacklistEntry::SetId(uint32 id) { | |
629 if (id != 0) { | |
630 id_ = id; | |
631 return true; | |
632 } | |
633 return false; | |
634 } | |
635 | |
636 void GpuBlacklist::GpuBlacklistEntry::SetDisabled(bool disabled) { | |
637 disabled_ = disabled; | |
638 } | |
639 | |
640 bool GpuBlacklist::GpuBlacklistEntry::SetOsInfo( | |
641 const std::string& os, | |
642 const std::string& version_op, | |
643 const std::string& version_string, | |
644 const std::string& version_string2) { | |
645 os_info_.reset(new OsInfo(os, version_op, version_string, version_string2)); | |
646 return os_info_->IsValid(); | |
647 } | |
648 | |
649 bool GpuBlacklist::GpuBlacklistEntry::SetVendorId( | |
650 const std::string& vendor_id_string) { | |
651 vendor_id_ = 0; | |
652 return base::HexStringToInt(vendor_id_string, | |
653 reinterpret_cast<int*>(&vendor_id_)); | |
654 } | |
655 | |
656 bool GpuBlacklist::GpuBlacklistEntry::AddDeviceId( | |
657 const std::string& device_id_string) { | |
658 uint32 device_id = 0; | |
659 if (base::HexStringToInt(device_id_string, | |
660 reinterpret_cast<int*>(&device_id))) { | |
661 device_id_list_.push_back(device_id); | |
662 return true; | |
663 } | |
664 return false; | |
665 } | |
666 | |
667 bool GpuBlacklist::GpuBlacklistEntry::SetMultiGpuStyle( | |
668 const std::string& multi_gpu_style_string) { | |
669 MultiGpuStyle style = StringToMultiGpuStyle(multi_gpu_style_string); | |
670 if (style == kMultiGpuStyleNone) | |
671 return false; | |
672 multi_gpu_style_ = style; | |
673 return true; | |
674 } | |
675 | |
676 bool GpuBlacklist::GpuBlacklistEntry::SetMultiGpuCategory( | |
677 const std::string& multi_gpu_category_string) { | |
678 MultiGpuCategory category = | |
679 StringToMultiGpuCategory(multi_gpu_category_string); | |
680 if (category == kMultiGpuCategoryNone) | |
681 return false; | |
682 multi_gpu_category_ = category; | |
683 return true; | |
684 } | |
685 | |
686 bool GpuBlacklist::GpuBlacklistEntry::SetDriverVendorInfo( | |
687 const std::string& vendor_op, | |
688 const std::string& vendor_value) { | |
689 driver_vendor_info_.reset( | |
690 new StringInfo(vendor_op, vendor_value)); | |
691 return driver_vendor_info_->IsValid(); | |
692 } | |
693 | |
694 bool GpuBlacklist::GpuBlacklistEntry::SetDriverVersionInfo( | |
695 const std::string& version_op, | |
696 const std::string& version_style, | |
697 const std::string& version_string, | |
698 const std::string& version_string2) { | |
699 driver_version_info_.reset(new VersionInfo( | |
700 version_op, version_style, version_string, version_string2)); | |
701 return driver_version_info_->IsValid(); | |
702 } | |
703 | |
704 bool GpuBlacklist::GpuBlacklistEntry::SetDriverDateInfo( | |
705 const std::string& date_op, | |
706 const std::string& date_string, | |
707 const std::string& date_string2) { | |
708 driver_date_info_.reset( | |
709 new VersionInfo(date_op, "", date_string, date_string2)); | |
710 return driver_date_info_->IsValid(); | |
711 } | |
712 | |
713 bool GpuBlacklist::GpuBlacklistEntry::SetGLVendorInfo( | |
714 const std::string& vendor_op, | |
715 const std::string& vendor_value) { | |
716 gl_vendor_info_.reset( | |
717 new StringInfo(vendor_op, vendor_value)); | |
718 return gl_vendor_info_->IsValid(); | |
719 } | |
720 | |
721 bool GpuBlacklist::GpuBlacklistEntry::SetGLRendererInfo( | |
722 const std::string& renderer_op, | |
723 const std::string& renderer_value) { | |
724 gl_renderer_info_.reset( | |
725 new StringInfo(renderer_op, renderer_value)); | |
726 return gl_renderer_info_->IsValid(); | |
727 } | |
728 | |
729 bool GpuBlacklist::GpuBlacklistEntry::SetPerfGraphicsInfo( | |
730 const std::string& op, | |
731 const std::string& float_string, | |
732 const std::string& float_string2) { | |
733 perf_graphics_info_.reset( | |
734 new FloatInfo(op, float_string, float_string2)); | |
735 return perf_graphics_info_->IsValid(); | |
736 } | |
737 | |
738 bool GpuBlacklist::GpuBlacklistEntry::SetPerfGamingInfo( | |
739 const std::string& op, | |
740 const std::string& float_string, | |
741 const std::string& float_string2) { | |
742 perf_gaming_info_.reset( | |
743 new FloatInfo(op, float_string, float_string2)); | |
744 return perf_gaming_info_->IsValid(); | |
745 } | |
746 | |
747 bool GpuBlacklist::GpuBlacklistEntry::SetPerfOverallInfo( | |
748 const std::string& op, | |
749 const std::string& float_string, | |
750 const std::string& float_string2) { | |
751 perf_overall_info_.reset( | |
752 new FloatInfo(op, float_string, float_string2)); | |
753 return perf_overall_info_->IsValid(); | |
754 } | |
755 | |
756 bool GpuBlacklist::GpuBlacklistEntry::SetBlacklistedFeatures( | |
757 const std::vector<std::string>& blacklisted_features) { | |
758 size_t size = blacklisted_features.size(); | |
759 if (size == 0) | |
760 return false; | |
761 int feature_type = content::GPU_FEATURE_TYPE_UNKNOWN; | |
762 for (size_t i = 0; i < size; ++i) { | |
763 GpuFeatureType type = | |
764 gpu_util::StringToGpuFeatureType(blacklisted_features[i]); | |
765 switch (type) { | |
766 case content::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS: | |
767 case content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING: | |
768 case content::GPU_FEATURE_TYPE_WEBGL: | |
769 case content::GPU_FEATURE_TYPE_MULTISAMPLING: | |
770 case content::GPU_FEATURE_TYPE_FLASH3D: | |
771 case content::GPU_FEATURE_TYPE_FLASH_STAGE3D: | |
772 case content::GPU_FEATURE_TYPE_TEXTURE_SHARING: | |
773 case content::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE: | |
774 case content::GPU_FEATURE_TYPE_ALL: | |
775 feature_type |= type; | |
776 break; | |
777 case content::GPU_FEATURE_TYPE_UNKNOWN: | |
778 contains_unknown_features_ = true; | |
779 break; | |
780 } | |
781 } | |
782 feature_type_ = static_cast<GpuFeatureType>(feature_type); | |
783 return true; | |
784 } | |
785 | |
786 void GpuBlacklist::GpuBlacklistEntry::AddException( | |
787 ScopedGpuBlacklistEntry exception) { | |
788 exceptions_.push_back(exception); | |
789 } | |
790 | |
791 // static | |
792 GpuBlacklist::GpuBlacklistEntry::MultiGpuStyle | |
793 GpuBlacklist::GpuBlacklistEntry::StringToMultiGpuStyle( | |
794 const std::string& style) { | |
795 if (style == kMultiGpuStyleStringOptimus) | |
796 return kMultiGpuStyleOptimus; | |
797 if (style == kMultiGpuStyleStringAMDSwitchable) | |
798 return kMultiGpuStyleAMDSwitchable; | |
799 return kMultiGpuStyleNone; | |
800 } | |
801 | |
802 // static | |
803 GpuBlacklist::GpuBlacklistEntry::MultiGpuCategory | |
804 GpuBlacklist::GpuBlacklistEntry::StringToMultiGpuCategory( | |
805 const std::string& category) { | |
806 if (category == kMultiGpuCategoryStringPrimary) | |
807 return kMultiGpuCategoryPrimary; | |
808 if (category == kMultiGpuCategoryStringSecondary) | |
809 return kMultiGpuCategorySecondary; | |
810 if (category == kMultiGpuCategoryStringAny) | |
811 return kMultiGpuCategoryAny; | |
812 return kMultiGpuCategoryNone; | |
813 } | |
814 | |
815 bool GpuBlacklist::GpuBlacklistEntry::Contains( | |
816 OsType os_type, const Version& os_version, | |
817 const content::GPUInfo& gpu_info) const { | |
818 DCHECK(os_type != kOsAny); | |
819 if (os_info_.get() != NULL && !os_info_->Contains(os_type, os_version)) | |
820 return false; | |
821 bool is_not_primary_gpu = | |
822 GpuUnmatched(vendor_id_, device_id_list_, gpu_info.gpu); | |
823 bool is_not_secondary_gpu = true; | |
824 for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) { | |
825 is_not_secondary_gpu = is_not_secondary_gpu && | |
826 GpuUnmatched(vendor_id_, device_id_list_, gpu_info.secondary_gpus[i]); | |
827 } | |
828 switch (multi_gpu_category_) { | |
829 case kMultiGpuCategoryPrimary: | |
830 if (is_not_primary_gpu) | |
831 return false; | |
832 break; | |
833 case kMultiGpuCategorySecondary: | |
834 if (is_not_secondary_gpu) | |
835 return false; | |
836 break; | |
837 case kMultiGpuCategoryAny: | |
838 if (is_not_primary_gpu && is_not_secondary_gpu) | |
839 return false; | |
840 break; | |
841 default: | |
842 break; | |
843 } | |
844 switch (multi_gpu_style_) { | |
845 case kMultiGpuStyleOptimus: | |
846 if (!gpu_info.optimus) | |
847 return false; | |
848 break; | |
849 case kMultiGpuStyleAMDSwitchable: | |
850 if (!gpu_info.amd_switchable) | |
851 return false; | |
852 break; | |
853 default: | |
854 break; | |
855 } | |
856 if (driver_vendor_info_.get() != NULL && | |
857 !driver_vendor_info_->Contains(gpu_info.driver_vendor)) | |
858 return false; | |
859 if (driver_version_info_.get() != NULL) { | |
860 std::string processed_driver_version; | |
861 if (driver_version_info_->IsLexical()) | |
862 processed_driver_version = NumericalToLexical(gpu_info.driver_version); | |
863 else | |
864 processed_driver_version = gpu_info.driver_version; | |
865 Version driver_version(processed_driver_version); | |
866 if (!driver_version.IsValid() || | |
867 !driver_version_info_->Contains(driver_version)) | |
868 return false; | |
869 } | |
870 if (driver_date_info_.get() != NULL) { | |
871 Version driver_date; | |
872 GetDateFromString(gpu_info.driver_date, &driver_date); | |
873 if (!driver_date.IsValid() || !driver_date_info_->Contains(driver_date)) | |
874 return false; | |
875 } | |
876 if (gl_vendor_info_.get() != NULL && | |
877 !gl_vendor_info_->Contains(gpu_info.gl_vendor)) | |
878 return false; | |
879 if (gl_renderer_info_.get() != NULL && | |
880 !gl_renderer_info_->Contains(gpu_info.gl_renderer)) | |
881 return false; | |
882 if (perf_graphics_info_.get() != NULL && | |
883 (gpu_info.performance_stats.graphics == 0.0 || | |
884 !perf_graphics_info_->Contains(gpu_info.performance_stats.graphics))) | |
885 return false; | |
886 if (perf_gaming_info_.get() != NULL && | |
887 (gpu_info.performance_stats.gaming == 0.0 || | |
888 !perf_gaming_info_->Contains(gpu_info.performance_stats.gaming))) | |
889 return false; | |
890 if (perf_overall_info_.get() != NULL && | |
891 (gpu_info.performance_stats.overall == 0.0 || | |
892 !perf_overall_info_->Contains(gpu_info.performance_stats.overall))) | |
893 return false; | |
894 for (size_t i = 0; i < exceptions_.size(); ++i) { | |
895 if (exceptions_[i]->Contains(os_type, os_version, gpu_info)) | |
896 return false; | |
897 } | |
898 return true; | |
899 } | |
900 | |
901 GpuBlacklist::OsType GpuBlacklist::GpuBlacklistEntry::GetOsType() const { | |
902 if (os_info_.get() == NULL) | |
903 return kOsAny; | |
904 return os_info_->type(); | |
905 } | |
906 | |
907 uint32 GpuBlacklist::GpuBlacklistEntry::id() const { | |
908 return id_; | |
909 } | |
910 | |
911 bool GpuBlacklist::GpuBlacklistEntry::disabled() const { | |
912 return disabled_; | |
913 } | |
914 | |
915 GpuFeatureType GpuBlacklist::GpuBlacklistEntry::GetGpuFeatureType() const { | |
916 return feature_type_; | |
917 } | |
918 | |
919 GpuBlacklist::GpuBlacklist() | |
920 : max_entry_id_(0), | |
921 contains_unknown_fields_(false) { | |
922 } | |
923 | |
924 GpuBlacklist::~GpuBlacklist() { | |
925 Clear(); | |
926 } | |
927 | |
928 bool GpuBlacklist::LoadGpuBlacklist( | |
929 const std::string& json_context, GpuBlacklist::OsFilter os_filter) { | |
930 const std::string browser_version_string = "0"; | |
931 return LoadGpuBlacklist(browser_version_string, json_context, os_filter); | |
932 } | |
933 | |
934 bool GpuBlacklist::LoadGpuBlacklist( | |
935 const std::string& browser_version_string, | |
936 const std::string& json_context, | |
937 GpuBlacklist::OsFilter os_filter) { | |
938 browser_version_.reset(new Version(browser_version_string)); | |
939 DCHECK(browser_version_->IsValid()); | |
940 | |
941 scoped_ptr<Value> root; | |
942 root.reset(base::JSONReader::Read(json_context)); | |
943 if (root.get() == NULL || !root->IsType(Value::TYPE_DICTIONARY)) | |
944 return false; | |
945 | |
946 DictionaryValue* root_dictionary = static_cast<DictionaryValue*>(root.get()); | |
947 DCHECK(root_dictionary); | |
948 return LoadGpuBlacklist(*root_dictionary, os_filter); | |
949 } | |
950 | |
951 bool GpuBlacklist::LoadGpuBlacklist( | |
952 const DictionaryValue& parsed_json, GpuBlacklist::OsFilter os_filter) { | |
953 std::vector<ScopedGpuBlacklistEntry> entries; | |
954 | |
955 std::string version_string; | |
956 parsed_json.GetString("version", &version_string); | |
957 version_.reset(new Version(version_string)); | |
958 if (!version_->IsValid()) | |
959 return false; | |
960 | |
961 const ListValue* list = NULL; | |
962 if (!parsed_json.GetList("entries", &list)) | |
963 return false; | |
964 | |
965 uint32 max_entry_id = 0; | |
966 bool contains_unknown_fields = false; | |
967 for (size_t i = 0; i < list->GetSize(); ++i) { | |
968 const DictionaryValue* list_item = NULL; | |
969 bool valid = list->GetDictionary(i, &list_item); | |
970 if (!valid || list_item == NULL) | |
971 return false; | |
972 // Check browser version compatibility: if the entry is not for the | |
973 // current browser version, don't process it. | |
974 BrowserVersionSupport browser_version_support = | |
975 IsEntrySupportedByCurrentBrowserVersion(list_item); | |
976 if (browser_version_support == kMalformed) | |
977 return false; | |
978 if (browser_version_support == kUnsupported) | |
979 continue; | |
980 DCHECK(browser_version_support == kSupported); | |
981 ScopedGpuBlacklistEntry entry( | |
982 GpuBlacklistEntry::GetGpuBlacklistEntryFromValue(list_item, true)); | |
983 if (entry == NULL) | |
984 return false; | |
985 if (entry->id() > max_entry_id) | |
986 max_entry_id = entry->id(); | |
987 // If an unknown field is encountered, skip the entry; if an unknown | |
988 // feature is encountered, ignore the feature, but keep the entry. | |
989 if (entry->contains_unknown_fields()) { | |
990 contains_unknown_fields = true; | |
991 continue; | |
992 } | |
993 if (entry->contains_unknown_features()) | |
994 contains_unknown_fields = true; | |
995 entries.push_back(entry); | |
996 } | |
997 | |
998 Clear(); | |
999 OsType my_os = GetOsType(); | |
1000 for (size_t i = 0; i < entries.size(); ++i) { | |
1001 OsType entry_os = entries[i]->GetOsType(); | |
1002 if (os_filter == GpuBlacklist::kAllOs || | |
1003 entry_os == kOsAny || entry_os == my_os) | |
1004 blacklist_.push_back(entries[i]); | |
1005 } | |
1006 max_entry_id_ = max_entry_id; | |
1007 contains_unknown_fields_ = contains_unknown_fields; | |
1008 return true; | |
1009 } | |
1010 | |
1011 GpuFeatureType GpuBlacklist::DetermineGpuFeatureType( | |
1012 GpuBlacklist::OsType os, | |
1013 Version* os_version, | |
1014 const content::GPUInfo& gpu_info) { | |
1015 active_entries_.clear(); | |
1016 int type = 0; | |
1017 | |
1018 if (os == kOsAny) | |
1019 os = GetOsType(); | |
1020 scoped_ptr<Version> my_os_version; | |
1021 if (os_version == NULL) { | |
1022 std::string version_string = base::SysInfo::OperatingSystemVersion(); | |
1023 size_t pos = version_string.find_first_not_of("0123456789."); | |
1024 if (pos != std::string::npos) | |
1025 version_string = version_string.substr(0, pos); | |
1026 my_os_version.reset(new Version(version_string)); | |
1027 os_version = my_os_version.get(); | |
1028 } | |
1029 DCHECK(os_version != NULL); | |
1030 | |
1031 for (size_t i = 0; i < blacklist_.size(); ++i) { | |
1032 if (blacklist_[i]->Contains(os, *os_version, gpu_info)) { | |
1033 if (!blacklist_[i]->disabled()) | |
1034 type |= blacklist_[i]->GetGpuFeatureType(); | |
1035 active_entries_.push_back(blacklist_[i]); | |
1036 } | |
1037 } | |
1038 return static_cast<GpuFeatureType>(type); | |
1039 } | |
1040 | |
1041 void GpuBlacklist::GetGpuFeatureTypeEntries( | |
1042 content::GpuFeatureType feature, | |
1043 std::vector<uint32>& entry_ids, | |
1044 bool disabled) const { | |
1045 entry_ids.clear(); | |
1046 for (size_t i = 0; i < active_entries_.size(); ++i) { | |
1047 if (((feature & active_entries_[i]->GetGpuFeatureType()) != 0) && | |
1048 disabled == active_entries_[i]->disabled()) | |
1049 entry_ids.push_back(active_entries_[i]->id()); | |
1050 } | |
1051 } | |
1052 | |
1053 void GpuBlacklist::GetBlacklistReasons(ListValue* problem_list) const { | |
1054 DCHECK(problem_list); | |
1055 for (size_t i = 0; i < active_entries_.size(); ++i) { | |
1056 GpuBlacklistEntry* entry = active_entries_[i]; | |
1057 if (entry->disabled()) | |
1058 continue; | |
1059 DictionaryValue* problem = new DictionaryValue(); | |
1060 | |
1061 problem->SetString("description", entry->description()); | |
1062 | |
1063 ListValue* cr_bugs = new ListValue(); | |
1064 for (size_t j = 0; j < entry->cr_bugs().size(); ++j) | |
1065 cr_bugs->Append(Value::CreateIntegerValue(entry->cr_bugs()[j])); | |
1066 problem->Set("crBugs", cr_bugs); | |
1067 | |
1068 ListValue* webkit_bugs = new ListValue(); | |
1069 for (size_t j = 0; j < entry->webkit_bugs().size(); ++j) { | |
1070 webkit_bugs->Append(Value::CreateIntegerValue( | |
1071 entry->webkit_bugs()[j])); | |
1072 } | |
1073 problem->Set("webkitBugs", webkit_bugs); | |
1074 | |
1075 problem_list->Append(problem); | |
1076 } | |
1077 } | |
1078 | |
1079 size_t GpuBlacklist::num_entries() const { | |
1080 return blacklist_.size(); | |
1081 } | |
1082 | |
1083 uint32 GpuBlacklist::max_entry_id() const { | |
1084 return max_entry_id_; | |
1085 } | |
1086 | |
1087 std::string GpuBlacklist::GetVersion() const { | |
1088 if (version_.get() == NULL) | |
1089 return std::string(); | |
1090 const std::vector<uint16>& components_reference = version_->components(); | |
1091 if (components_reference.size() != 2) | |
1092 return std::string(); | |
1093 | |
1094 std::string version_string = | |
1095 base::UintToString(static_cast<unsigned>(components_reference[0])) + | |
1096 "." + | |
1097 base::UintToString(static_cast<unsigned>(components_reference[1])); | |
1098 return version_string; | |
1099 } | |
1100 | |
1101 GpuBlacklist::OsType GpuBlacklist::GetOsType() { | |
1102 #if defined(OS_CHROMEOS) | |
1103 return kOsChromeOS; | |
1104 #elif defined(OS_WIN) | |
1105 return kOsWin; | |
1106 #elif defined(OS_LINUX) || defined(OS_OPENBSD) | |
1107 return kOsLinux; | |
1108 #elif defined(OS_MACOSX) | |
1109 return kOsMacosx; | |
1110 #else | |
1111 return kOsUnknown; | |
1112 #endif | |
1113 } | |
1114 | |
1115 void GpuBlacklist::Clear() { | |
1116 blacklist_.clear(); | |
1117 active_entries_.clear(); | |
1118 max_entry_id_ = 0; | |
1119 contains_unknown_fields_ = false; | |
1120 } | |
1121 | |
1122 GpuBlacklist::BrowserVersionSupport | |
1123 GpuBlacklist::IsEntrySupportedByCurrentBrowserVersion( | |
1124 const DictionaryValue* value) { | |
1125 DCHECK(value); | |
1126 const DictionaryValue* browser_version_value = NULL; | |
1127 if (value->GetDictionary("browser_version", &browser_version_value)) { | |
1128 std::string version_op = "any"; | |
1129 std::string version_string; | |
1130 std::string version_string2; | |
1131 browser_version_value->GetString("op", &version_op); | |
1132 browser_version_value->GetString("number", &version_string); | |
1133 browser_version_value->GetString("number2", &version_string2); | |
1134 scoped_ptr<VersionInfo> browser_version_info; | |
1135 browser_version_info.reset( | |
1136 new VersionInfo(version_op, "", version_string, version_string2)); | |
1137 if (!browser_version_info->IsValid()) | |
1138 return kMalformed; | |
1139 if (browser_version_info->Contains(*browser_version_)) | |
1140 return kSupported; | |
1141 return kUnsupported; | |
1142 } | |
1143 return kSupported; | |
1144 } | |
1145 | |
1146 // static | |
1147 GpuBlacklist::NumericOp GpuBlacklist::StringToNumericOp( | |
1148 const std::string& op) { | |
1149 if (op == "=") | |
1150 return kEQ; | |
1151 if (op == "<") | |
1152 return kLT; | |
1153 if (op == "<=") | |
1154 return kLE; | |
1155 if (op == ">") | |
1156 return kGT; | |
1157 if (op == ">=") | |
1158 return kGE; | |
1159 if (op == "any") | |
1160 return kAny; | |
1161 if (op == "between") | |
1162 return kBetween; | |
1163 return kUnknown; | |
1164 } | |
OLD | NEW |