Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: webkit/plugins/npapi/plugin_list.cc

Issue 10918174: Remove PluginGroup (Closed) Base URL: http://git.chromium.org/chromium/src.git@remove_async_plugin_finder
Patch Set: custom traits Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "webkit/plugins/npapi/plugin_list.h" 5 #include "webkit/plugins/npapi/plugin_list.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/command_line.h" 9 #include "base/command_line.h"
10 #include "base/lazy_instance.h" 10 #include "base/lazy_instance.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/string_split.h" 12 #include "base/string_split.h"
13 #include "base/string_util.h" 13 #include "base/string_util.h"
14 #include "base/sys_string_conversions.h" 14 #include "base/sys_string_conversions.h"
15 #include "base/utf_string_conversions.h" 15 #include "base/utf_string_conversions.h"
16 #include "googleurl/src/gurl.h" 16 #include "googleurl/src/gurl.h"
17 #include "net/base/mime_util.h" 17 #include "net/base/mime_util.h"
18 #include "webkit/glue/webkit_glue.h" 18 #include "webkit/glue/webkit_glue.h"
19 #include "webkit/plugins/npapi/plugin_constants_win.h" 19 #include "webkit/plugins/npapi/plugin_constants_win.h"
20 #include "webkit/plugins/npapi/plugin_lib.h" 20 #include "webkit/plugins/npapi/plugin_lib.h"
21 #include "webkit/plugins/plugin_switches.h" 21 #include "webkit/plugins/plugin_switches.h"
22 22
23 namespace { 23 namespace {
24 24
25 using webkit::npapi::PluginList;
26 using webkit::npapi::CustomPluginListTraits;
27
25 const char kApplicationOctetStream[] = "application/octet-stream"; 28 const char kApplicationOctetStream[] = "application/octet-stream";
26 29
27 base::LazyInstance<webkit::npapi::PluginList> g_singleton = 30 base::LazyInstance<PluginList, CustomPluginListTraits> g_singleton =
28 LAZY_INSTANCE_INITIALIZER; 31 LAZY_INSTANCE_INITIALIZER;
29 32
30 bool AllowMimeTypeMismatch(const std::string& orig_mime_type, 33 bool AllowMimeTypeMismatch(const std::string& orig_mime_type,
31 const std::string& actual_mime_type) { 34 const std::string& actual_mime_type) {
32 if (orig_mime_type == actual_mime_type) { 35 if (orig_mime_type == actual_mime_type) {
33 NOTREACHED(); 36 NOTREACHED();
34 return true; 37 return true;
35 } 38 }
36 39
37 // We do not permit URL-sniff based plug-in MIME type overrides aside from 40 // We do not permit URL-sniff based plug-in MIME type overrides aside from
38 // the case where the "type" was initially missing or generic 41 // the case where the "type" was initially missing or generic
39 // (application/octet-stream). 42 // (application/octet-stream).
40 // We collected stats to determine this approach isn't a major compat issue, 43 // We collected stats to determine this approach isn't a major compat issue,
41 // and we defend against content confusion attacks in various cases, such 44 // and we defend against content confusion attacks in various cases, such
42 // as when the user doesn't have the Flash plug-in enabled. 45 // as when the user doesn't have the Flash plug-in enabled.
43 bool allow = orig_mime_type.empty() || 46 bool allow = orig_mime_type.empty() ||
44 orig_mime_type == kApplicationOctetStream; 47 orig_mime_type == kApplicationOctetStream;
45 LOG_IF(INFO, !allow) << "Ignoring plugin with unexpected MIME type " 48 LOG_IF(INFO, !allow) << "Ignoring plugin with unexpected MIME type "
46 << actual_mime_type << " (expected " << orig_mime_type 49 << actual_mime_type << " (expected " << orig_mime_type
47 << ")"; 50 << ")";
48 return allow; 51 return allow;
49 } 52 }
50 53
51 } 54 }
52 55
53 namespace webkit { 56 namespace webkit {
54 namespace npapi { 57 namespace npapi {
55 58
56 // TODO(ibraaaa): DELETE all hardcoded definitions. http://crbug.com/124396
57 // Note: If you change the plug-in definitions here, also update
58 // chrome/browser/resources/plugins_*.json correspondingly!
59 // In particular, the identifier needs to be kept in sync.
60
61 // Try and share the group definition for plug-ins that are
62 // very consistent across OS'es.
63 #define kFlashDefinition { \
64 "adobe-flash-player", "Adobe Flash Player", "Shockwave Flash" }
65
66 #define kShockwaveDefinition { \
67 "adobe-shockwave", PluginGroup::kShockwaveGroupName, \
68 "Shockwave for Director" }
69
70 #define kSilverlightDefinition { \
71 "silverlight", PluginGroup::kSilverlightGroupName, "Silverlight" }
72
73 #define kChromePdfDefinition { \
74 "google-chrome-pdf", "Chrome PDF Viewer", "Chrome PDF Viewer" }
75
76 #define kGoogleTalkDefinition { \
77 "google-talk", "Google Talk", "Google Talk" }
78
79 #if defined(OS_MACOSX)
80 // Plugin Groups for Mac.
81
82 static const PluginGroupDefinition kGroupDefinitions[] = {
83 kFlashDefinition,
84 { "apple-quicktime", PluginGroup::kQuickTimeGroupName, "QuickTime Plug-in" },
85 { "java-runtime-environment", PluginGroup::kJavaGroupName, "Java" },
86 kSilverlightDefinition,
87 { "flip4mac", "Flip4Mac", "Flip4Mac" },
88 { "divx-player", "DivX Web Player", "DivX Plus Web Player" },
89 { "realplayer", PluginGroup::kRealPlayerGroupName, "RealPlayer" },
90 kShockwaveDefinition,
91 kChromePdfDefinition,
92 kGoogleTalkDefinition,
93 };
94
95 #elif defined(OS_WIN)
96 // TODO(panayiotis): We should group "RealJukebox NS Plugin" with the rest of
97 // the RealPlayer files.
98
99 static const PluginGroupDefinition kGroupDefinitions[] = {
100 kFlashDefinition,
101 { "apple-quicktime", PluginGroup::kQuickTimeGroupName, "QuickTime Plug-in" },
102 { "java-runtime-environment", PluginGroup::kJavaGroupName, "Java" },
103 { "adobe-reader", PluginGroup::kAdobeReaderGroupName, "Adobe Acrobat" },
104 kSilverlightDefinition,
105 kShockwaveDefinition,
106 { "divx-player", "DivX Web Player", "DivX Web Player" },
107 { "realplayer", PluginGroup::kRealPlayerGroupName, "RealPlayer" },
108 { "windows-media-player", PluginGroup::kWindowsMediaPlayerGroupName,
109 "Windows Media Player" },
110 { "microsoft-office", "Microsoft Office", "Microsoft Office" },
111 { "nvidia-3d", "NVIDIA 3D", "NVIDIA 3D" },
112 kChromePdfDefinition,
113 kGoogleTalkDefinition,
114 };
115
116 #elif defined(OS_CHROMEOS)
117 // ChromeOS generally has (autoupdated) system plug-ins and no user-installable
118 // plug-ins, so we just use these definitions for grouping.
119 static const PluginGroupDefinition kGroupDefinitions[] = {
120 kFlashDefinition,
121 kChromePdfDefinition,
122 };
123
124 #else // Most importantly, covers desktop Linux.
125
126 static const PluginGroupDefinition kGroupDefinitions[] = {
127 // Flash on Linux is significant because there isn't yet a built-in Flash
128 // plug-in on the Linux 64-bit version of Chrome.
129 kFlashDefinition,
130 { "java-runtime-environment", PluginGroup::kJavaGroupName, "Java" },
131 { "redhat-icetea-java", "IcedTea", "IcedTea" },
132 kChromePdfDefinition,
133 kGoogleTalkDefinition,
134 };
135 #endif
136
137 // static 59 // static
138 PluginList* PluginList::Singleton() { 60 PluginList* PluginList::Singleton() {
139 return g_singleton.Pointer(); 61 return g_singleton.Pointer();
140 } 62 }
141 63
142 // static 64 // static
143 bool PluginList::DebugPluginLoading() { 65 bool PluginList::DebugPluginLoading() {
144 return CommandLine::ForCurrentProcess()->HasSwitch( 66 return CommandLine::ForCurrentProcess()->HasSwitch(
145 switches::kDebugPluginLoading); 67 switches::kDebugPluginLoading);
146 } 68 }
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
280 } 202 }
281 203
282 parsed_mime_types->push_back(mime_type); 204 parsed_mime_types->push_back(mime_type);
283 } 205 }
284 206
285 return true; 207 return true;
286 } 208 }
287 209
288 PluginList::PluginList() 210 PluginList::PluginList()
289 : loading_state_(LOADING_STATE_NEEDS_REFRESH) { 211 : loading_state_(LOADING_STATE_NEEDS_REFRESH) {
290 PlatformInit();
291 AddHardcodedPluginGroups(kGroupDefinitions,
292 ARRAYSIZE_UNSAFE(kGroupDefinitions));
293 }
294
295 // TODO(ibraaaa): DELETE and add a different one. http://crbug.com/124396
296 PluginList::PluginList(const PluginGroupDefinition* definitions,
297 size_t num_definitions)
298 :
299 #if defined(OS_WIN)
300 dont_load_new_wmp_(false),
301 #endif
302 loading_state_(LOADING_STATE_NEEDS_REFRESH) {
303 // Don't do platform-dependent initialization in unit tests.
304 AddHardcodedPluginGroups(definitions, num_definitions);
305 }
306
307 // TODO(ibraaaa): DELETE. http://crbug.com/124396
308 PluginGroup* PluginList::CreatePluginGroup(
309 const webkit::WebPluginInfo& web_plugin_info) const {
310 for (size_t i = 0; i < hardcoded_plugin_groups_.size(); ++i) {
311 const PluginGroup* group = hardcoded_plugin_groups_[i];
312 if (group->Match(web_plugin_info))
313 return new PluginGroup(*group);
314 }
315 return PluginGroup::FromWebPluginInfo(web_plugin_info);
316 }
317
318 // TODO(ibraaaa): DELETE. http://crbug.com/124396
319 void PluginList::LoadPluginsInternal(ScopedVector<PluginGroup>* plugin_groups) {
320 base::Closure will_load_callback;
321 {
322 base::AutoLock lock(lock_);
323 will_load_callback = will_load_plugins_callback_;
324 }
325 if (!will_load_callback.is_null())
326 will_load_callback.Run();
327
328 std::vector<FilePath> plugin_paths;
329 GetPluginPathsToLoad(&plugin_paths);
330
331 for (std::vector<FilePath>::const_iterator it = plugin_paths.begin();
332 it != plugin_paths.end();
333 ++it) {
334 WebPluginInfo plugin_info;
335 LoadPlugin(*it, plugin_groups, &plugin_info);
336 }
337 } 212 }
338 213
339 void PluginList::LoadPluginsIntoPluginListInternal( 214 void PluginList::LoadPluginsIntoPluginListInternal(
340 std::vector<webkit::WebPluginInfo>* plugins) { 215 std::vector<webkit::WebPluginInfo>* plugins) {
341 base::Closure will_load_callback; 216 base::Closure will_load_callback;
342 { 217 {
343 base::AutoLock lock(lock_); 218 base::AutoLock lock(lock_);
344 will_load_callback = will_load_plugins_callback_; 219 will_load_callback = will_load_plugins_callback_;
345 } 220 }
346 if (!will_load_callback.is_null()) 221 if (!will_load_callback.is_null())
(...skipping 12 matching lines...) Expand all
359 234
360 void PluginList::LoadPlugins() { 235 void PluginList::LoadPlugins() {
361 { 236 {
362 base::AutoLock lock(lock_); 237 base::AutoLock lock(lock_);
363 if (loading_state_ == LOADING_STATE_UP_TO_DATE) 238 if (loading_state_ == LOADING_STATE_UP_TO_DATE)
364 return; 239 return;
365 240
366 loading_state_ = LOADING_STATE_REFRESHING; 241 loading_state_ = LOADING_STATE_REFRESHING;
367 } 242 }
368 243
369 ScopedVector<PluginGroup> new_plugin_groups; // TODO(ibraaaa): DELETE
370 // Do the actual loading of the plugins.
371 LoadPluginsInternal(&new_plugin_groups); // TODO(ibraaaa): DELETE
372
373 std::vector<webkit::WebPluginInfo> new_plugins; 244 std::vector<webkit::WebPluginInfo> new_plugins;
374 // Do the actual loading of the plugins. 245 // Do the actual loading of the plugins.
375 LoadPluginsIntoPluginListInternal(&new_plugins); 246 LoadPluginsIntoPluginListInternal(&new_plugins);
376 247
377 base::AutoLock lock(lock_); 248 base::AutoLock lock(lock_);
378 plugin_groups_.swap(new_plugin_groups); // TODO(ibraaaa): DELETE
379 plugins_list_.swap(new_plugins); 249 plugins_list_.swap(new_plugins);
380 250
381 // If we haven't been invalidated in the mean time, mark the plug-in list as 251 // If we haven't been invalidated in the mean time, mark the plug-in list as
382 // up-to-date. 252 // up-to-date.
383 if (loading_state_ != LOADING_STATE_NEEDS_REFRESH) 253 if (loading_state_ != LOADING_STATE_NEEDS_REFRESH)
384 loading_state_ = LOADING_STATE_UP_TO_DATE; 254 loading_state_ = LOADING_STATE_UP_TO_DATE;
385 } 255 }
386 256
387 // TODO(ibraaaa): DELETE. http://crbug.com/124396
388 bool PluginList::LoadPlugin(const FilePath& path,
389 ScopedVector<PluginGroup>* plugin_groups,
390 WebPluginInfo* plugin_info) {
391 LOG_IF(ERROR, PluginList::DebugPluginLoading())
392 << "Loading plugin " << path.value();
393 const PluginEntryPoints* entry_points;
394
395 if (!ReadPluginInfo(path, plugin_info, &entry_points))
396 return false;
397
398 if (!ShouldLoadPlugin(*plugin_info, plugin_groups))
399 return false;
400
401 #if defined(OS_WIN) && !defined(NDEBUG)
402 if (path.BaseName().value() != L"npspy.dll") // Make an exception for NPSPY
403 #endif
404 {
405 for (size_t i = 0; i < plugin_info->mime_types.size(); ++i) {
406 // TODO: don't load global handlers for now.
407 // WebKit hands to the Plugin before it tries
408 // to handle mimeTypes on its own.
409 const std::string &mime_type = plugin_info->mime_types[i].mime_type;
410 if (mime_type == "*")
411 return false;
412 }
413 }
414 AddToPluginGroups(*plugin_info, plugin_groups);
415 return true;
416 }
417
418 bool PluginList::LoadPluginIntoPluginList( 257 bool PluginList::LoadPluginIntoPluginList(
419 const FilePath& path, 258 const FilePath& path,
420 std::vector<webkit::WebPluginInfo>* plugins, 259 std::vector<webkit::WebPluginInfo>* plugins,
421 WebPluginInfo* plugin_info) { 260 WebPluginInfo* plugin_info) {
422 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 261 LOG_IF(ERROR, PluginList::DebugPluginLoading())
423 << "Loading plugin " << path.value(); 262 << "Loading plugin " << path.value();
424 const PluginEntryPoints* entry_points; 263 const PluginEntryPoints* entry_points;
425 264
426 if (!ReadPluginInfo(path, plugin_info, &entry_points)) 265 if (!ReadPluginInfo(path, plugin_info, &entry_points))
427 return false; 266 return false;
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
473 GetPluginsInDir(extra_plugin_dirs[i], plugin_paths); 312 GetPluginsInDir(extra_plugin_dirs[i], plugin_paths);
474 313
475 for (size_t i = 0; i < directories_to_scan.size(); ++i) 314 for (size_t i = 0; i < directories_to_scan.size(); ++i)
476 GetPluginsInDir(directories_to_scan[i], plugin_paths); 315 GetPluginsInDir(directories_to_scan[i], plugin_paths);
477 316
478 #if defined(OS_WIN) 317 #if defined(OS_WIN)
479 GetPluginPathsFromRegistry(plugin_paths); 318 GetPluginPathsFromRegistry(plugin_paths);
480 #endif 319 #endif
481 } 320 }
482 321
483 // TODO(ibraaaa): DELETE. http://crbug.com/124396
484 const std::vector<PluginGroup*>& PluginList::GetHardcodedPluginGroups() const {
485 return hardcoded_plugin_groups_.get();
486 }
487
488 void PluginList::SetPlugins(const std::vector<webkit::WebPluginInfo>& plugins) { 322 void PluginList::SetPlugins(const std::vector<webkit::WebPluginInfo>& plugins) {
489 base::AutoLock lock(lock_); 323 base::AutoLock lock(lock_);
490 324
491 DCHECK_NE(LOADING_STATE_REFRESHING, loading_state_); 325 DCHECK_NE(LOADING_STATE_REFRESHING, loading_state_);
492 loading_state_ = LOADING_STATE_UP_TO_DATE; 326 loading_state_ = LOADING_STATE_UP_TO_DATE;
493 327
494 // TODO(ibraaaa): DELETE
495 plugin_groups_.clear();
496 for (std::vector<webkit::WebPluginInfo>::const_iterator it = plugins.begin();
497 it != plugins.end();
498 ++it) {
499 AddToPluginGroups(*it, &plugin_groups_);
500 } // END OF DELETE
501
502 plugins_list_.clear(); 328 plugins_list_.clear();
503 plugins_list_.insert(plugins_list_.end(), plugins.begin(), plugins.end()); 329 plugins_list_.insert(plugins_list_.end(), plugins.begin(), plugins.end());
504 } 330 }
505 331
506 void PluginList::set_will_load_plugins_callback(const base::Closure& callback) { 332 void PluginList::set_will_load_plugins_callback(const base::Closure& callback) {
507 base::AutoLock lock(lock_); 333 base::AutoLock lock(lock_);
508 will_load_plugins_callback_ = callback; 334 will_load_plugins_callback_ = callback;
509 } 335 }
510 336
511 void PluginList::GetPlugins(std::vector<WebPluginInfo>* plugins) { 337 void PluginList::GetPlugins(std::vector<WebPluginInfo>* plugins) {
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
568 AllowMimeTypeMismatch(mime_type, actual_mime_type)) { 394 AllowMimeTypeMismatch(mime_type, actual_mime_type)) {
569 info->push_back(plugins_list_[i]); 395 info->push_back(plugins_list_[i]);
570 if (actual_mime_types) 396 if (actual_mime_types)
571 actual_mime_types->push_back(actual_mime_type); 397 actual_mime_types->push_back(actual_mime_type);
572 } 398 }
573 } 399 }
574 } 400 }
575 } 401 }
576 } 402 }
577 403
578 // TODO(ibraaaa): DELETE. http://crbug.com/124396
579 void PluginList::GetPluginGroups(
580 bool load_if_necessary,
581 std::vector<PluginGroup>* plugin_groups) {
582 if (load_if_necessary)
583 LoadPlugins();
584 base::AutoLock lock(lock_);
585 plugin_groups->clear();
586 for (size_t i = 0; i < plugin_groups_.size(); ++i) {
587 // In some unit tests we can get confronted with empty groups but in real
588 // world code this if should never be false here.
589 if (!plugin_groups_[i]->IsEmpty())
590 plugin_groups->push_back(*plugin_groups_[i]);
591 }
592 }
593
594 // TODO(ibraaaa): DELETE. http://crbug.com/124396
595 PluginGroup* PluginList::GetPluginGroup(
596 const webkit::WebPluginInfo& web_plugin_info) {
597 base::AutoLock lock(lock_);
598 for (size_t i = 0; i < plugin_groups_.size(); ++i) {
599 const std::vector<webkit::WebPluginInfo>& plugins =
600 plugin_groups_[i]->web_plugin_infos();
601 for (size_t j = 0; j < plugins.size(); ++j) {
602 if (plugins[j].path == web_plugin_info.path) {
603 return new PluginGroup(*plugin_groups_[i]);
604 }
605 }
606 }
607 PluginGroup* group = CreatePluginGroup(web_plugin_info);
608 group->AddPlugin(web_plugin_info);
609 return group;
610 }
611
612 // TODO(ibraaaa): DELETE. http://crbug.com/124396
613 string16 PluginList::GetPluginGroupName(const std::string& identifier) {
614 for (size_t i = 0; i < plugin_groups_.size(); ++i) {
615 if (plugin_groups_[i]->identifier() == identifier)
616 return plugin_groups_[i]->GetGroupName();
617 }
618 return string16();
619 }
620
621 // TODO(ibraaaa): DELETE. http://crbug.com/124396
622 void PluginList::AddHardcodedPluginGroups(
623 const PluginGroupDefinition* group_definitions,
624 size_t num_group_definitions) {
625 for (size_t i = 0; i < num_group_definitions; ++i) {
626 hardcoded_plugin_groups_.push_back(
627 PluginGroup::FromPluginGroupDefinition(group_definitions[i]));
628 }
629 }
630
631 // TODO(ibraaaa): DELETE. http://crbug.com/124396
632 PluginGroup* PluginList::AddToPluginGroups(
633 const webkit::WebPluginInfo& web_plugin_info,
634 ScopedVector<PluginGroup>* plugin_groups) {
635 PluginGroup* group = NULL;
636 for (size_t i = 0; i < plugin_groups->size(); ++i) {
637 if ((*plugin_groups)[i]->Match(web_plugin_info)) {
638 group = (*plugin_groups)[i];
639 break;
640 }
641 }
642 if (!group) {
643 group = CreatePluginGroup(web_plugin_info);
644 std::string identifier = group->identifier();
645 // If the identifier is not unique, use the full path. This means that we
646 // probably won't be able to search for this group by identifier, but at
647 // least it's going to be in the set of plugin groups, and if there
648 // is already a plug-in with the same filename, it's probably going to
649 // handle the same MIME types (and it has a higher priority), so this one
650 // is not going to run anyway.
651 for (size_t i = 0; i < plugin_groups->size(); ++i) {
652 if ((*plugin_groups)[i]->identifier() == identifier) {
653 group->set_identifier(PluginGroup::GetLongIdentifier(web_plugin_info));
654 break;
655 }
656 }
657 plugin_groups->push_back(group);
658 }
659 group->AddPlugin(web_plugin_info);
660 return group;
661 }
662
663 bool PluginList::SupportsType(const webkit::WebPluginInfo& plugin, 404 bool PluginList::SupportsType(const webkit::WebPluginInfo& plugin,
664 const std::string& mime_type, 405 const std::string& mime_type,
665 bool allow_wildcard) { 406 bool allow_wildcard) {
666 // Webkit will ask for a plugin to handle empty mime types. 407 // Webkit will ask for a plugin to handle empty mime types.
667 if (mime_type.empty()) 408 if (mime_type.empty())
668 return false; 409 return false;
669 410
670 for (size_t i = 0; i < plugin.mime_types.size(); ++i) { 411 for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
671 const webkit::WebPluginMimeType& mime_info = plugin.mime_types[i]; 412 const webkit::WebPluginMimeType& mime_info = plugin.mime_types[i];
672 if (net::MatchesMimeType(mime_info.mime_type, mime_type)) { 413 if (net::MatchesMimeType(mime_info.mime_type, mime_type)) {
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
708 } 449 }
709 return did_remove; 450 return did_remove;
710 } 451 }
711 452
712 PluginList::~PluginList() { 453 PluginList::~PluginList() {
713 } 454 }
714 455
715 456
716 } // namespace npapi 457 } // namespace npapi
717 } // namespace webkit 458 } // namespace webkit
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698