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

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

Issue 19761007: Move NPAPI implementation out of webkit/plugins/npapi and into content. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: fix mac Created 7 years, 5 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "webkit/plugins/npapi/plugin_list.h"
6
7 #include <algorithm>
8 #include <dlfcn.h>
9 #if defined(OS_OPENBSD)
10 #include <sys/exec_elf.h>
11 #else
12 #include <elf.h>
13 #endif
14 #include <fcntl.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <unistd.h>
18
19 #include "base/cpu.h"
20 #include "base/file_util.h"
21 #include "base/files/file_enumerator.h"
22 #include "base/native_library.h"
23 #include "base/path_service.h"
24 #include "base/posix/eintr_wrapper.h"
25 #include "base/sha1.h"
26 #include "base/strings/string_split.h"
27 #include "base/strings/string_util.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/strings/sys_string_conversions.h"
30 #include "base/strings/utf_string_conversions.h"
31 #include "build/build_config.h"
32 #include "third_party/npapi/bindings/nphostapi.h"
33
34 // These headers must be included in this order to make the declaration gods
35 // happy.
36 #include "base/third_party/nspr/prcpucfg_linux.h"
37
38 namespace webkit {
39 namespace npapi {
40
41 namespace {
42
43 // We build up a list of files and mtimes so we can sort them.
44 typedef std::pair<base::FilePath, base::Time> FileAndTime;
45 typedef std::vector<FileAndTime> FileTimeList;
46
47 enum PluginQuirk {
48 // No quirks - plugin is outright banned.
49 PLUGIN_QUIRK_NONE = 0,
50 // Plugin is using SSE2 instructions without checking for SSE2 instruction
51 // support. Ban the plugin if the system has no SSE2 support.
52 PLUGIN_QUIRK_MISSING_SSE2_CHECK = 1 << 0,
53 };
54
55 // Copied from nsplugindefs.h instead of including the file since it has a bunch
56 // of dependencies.
57 enum nsPluginVariable {
58 nsPluginVariable_NameString = 1,
59 nsPluginVariable_DescriptionString = 2
60 };
61
62 // Comparator used to sort by descending mtime then ascending filename.
63 bool CompareTime(const FileAndTime& a, const FileAndTime& b) {
64 if (a.second == b.second) {
65 // Fall back on filename sorting, just to make the predicate valid.
66 return a.first < b.first;
67 }
68
69 // Sort by mtime, descending.
70 return a.second > b.second;
71 }
72
73 // Checks to see if the current environment meets any of the condtions set in
74 // |quirks|. Returns true if any of the conditions are met, or if |quirks| is
75 // PLUGIN_QUIRK_NONE.
76 bool CheckQuirks(PluginQuirk quirks) {
77 if (quirks == PLUGIN_QUIRK_NONE)
78 return true;
79
80 if ((quirks & PLUGIN_QUIRK_MISSING_SSE2_CHECK) != 0) {
81 base::CPU cpu;
82 if (!cpu.has_sse2())
83 return true;
84 }
85
86 return false;
87 }
88
89 // Return true if |path| matches a known (file size, sha1sum) pair.
90 // Also check against any PluginQuirks the bad plugin may have.
91 // The use of the file size is an optimization so we don't have to read in
92 // the entire file unless we have to.
93 bool IsBlacklistedBySha1sumAndQuirks(const base::FilePath& path) {
94 const struct BadEntry {
95 int64 size;
96 std::string sha1;
97 PluginQuirk quirks;
98 } bad_entries[] = {
99 // Flash 9 r31 - http://crbug.com/29237
100 { 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb", PLUGIN_QUIRK_NONE },
101 // Flash 9 r48 - http://crbug.com/29237
102 { 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b", PLUGIN_QUIRK_NONE },
103 // Flash 11.2.202.236, 32-bit - http://crbug.com/140086
104 { 17406436, "1e07eac912faf9426c52a288c76c3b6238f90b6b",
105 PLUGIN_QUIRK_MISSING_SSE2_CHECK },
106 // Flash 11.2.202.238, 32-bit - http://crbug.com/140086
107 { 17410532, "e9401097e97c8443a7d9156be62184ffe1addd5c",
108 PLUGIN_QUIRK_MISSING_SSE2_CHECK },
109 };
110
111 int64 size;
112 if (!file_util::GetFileSize(path, &size))
113 return false;
114 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) {
115 if (bad_entries[i].size != size)
116 continue;
117
118 std::string file_content;
119 if (!file_util::ReadFileToString(path, &file_content))
120 continue;
121 std::string sha1 = base::SHA1HashString(file_content);
122 std::string sha1_readable;
123 for (size_t j = 0; j < sha1.size(); j++)
124 base::StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF);
125 if (bad_entries[i].sha1 == sha1_readable)
126 return CheckQuirks(bad_entries[i].quirks);
127 }
128 return false;
129 }
130
131 // Some plugins are shells around other plugins; we prefer to use the
132 // real plugin directly, if it's available. This function returns
133 // true if we should prefer other plugins over this one. We'll still
134 // use a "undesirable" plugin if no other option is available.
135 bool IsUndesirablePlugin(const WebPluginInfo& info) {
136 std::string filename = info.path.BaseName().value();
137 const char* kUndesiredPlugins[] = {
138 "npcxoffice", // Crossover
139 "npwrapper", // nspluginwrapper
140 };
141 for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) {
142 if (filename.find(kUndesiredPlugins[i]) != std::string::npos) {
143 return true;
144 }
145 }
146 return false;
147 }
148
149 // Return true if we shouldn't load a plugin at all.
150 // This is an ugly hack to blacklist Adobe Acrobat due to not supporting
151 // its Xt-based mainloop.
152 // http://code.google.com/p/chromium/issues/detail?id=38229
153 bool IsBlacklistedPlugin(const base::FilePath& path) {
154 const char* kBlackListedPlugins[] = {
155 "nppdf.so", // Adobe PDF
156 };
157 std::string filename = path.BaseName().value();
158 for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) {
159 if (filename.find(kBlackListedPlugins[i]) != std::string::npos) {
160 return true;
161 }
162 }
163 return IsBlacklistedBySha1sumAndQuirks(path);
164 }
165
166 // Read the ELF header and return true if it is usable on
167 // the current architecture (e.g. 32-bit ELF on 32-bit build).
168 // Returns false on other errors as well.
169 bool ELFMatchesCurrentArchitecture(const base::FilePath& filename) {
170 // First make sure we can open the file and it is in fact, a regular file.
171 struct stat stat_buf;
172 // Open with O_NONBLOCK so we don't block on pipes.
173 int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK);
174 if (fd < 0)
175 return false;
176 bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode));
177 if (HANDLE_EINTR(close(fd)) < 0)
178 return false;
179 if (!ret)
180 return false;
181
182 const size_t kELFBufferSize = 5;
183 char buffer[kELFBufferSize];
184 if (!file_util::ReadFile(filename, buffer, kELFBufferSize))
185 return false;
186
187 if (buffer[0] != ELFMAG0 ||
188 buffer[1] != ELFMAG1 ||
189 buffer[2] != ELFMAG2 ||
190 buffer[3] != ELFMAG3) {
191 // Not an ELF file, perhaps?
192 return false;
193 }
194
195 int elf_class = buffer[EI_CLASS];
196 #if defined(ARCH_CPU_32_BITS)
197 if (elf_class == ELFCLASS32)
198 return true;
199 #elif defined(ARCH_CPU_64_BITS)
200 if (elf_class == ELFCLASS64)
201 return true;
202 #endif
203
204 return false;
205 }
206
207 // This structure matches enough of nspluginwrapper's NPW_PluginInfo
208 // for us to extract the real plugin path.
209 struct __attribute__((packed)) NSPluginWrapperInfo {
210 char ident[32]; // NSPluginWrapper magic identifier (includes version).
211 char path[PATH_MAX]; // Path to wrapped plugin.
212 };
213
214 // Test a plugin for whether it's been wrapped by NSPluginWrapper, and
215 // if so attempt to unwrap it. Pass in an opened plugin handle; on
216 // success, |dl| and |unwrapped_path| will be filled in with the newly
217 // opened plugin. On failure, params are left unmodified.
218 void UnwrapNSPluginWrapper(void **dl, base::FilePath* unwrapped_path) {
219 NSPluginWrapperInfo* info =
220 reinterpret_cast<NSPluginWrapperInfo*>(dlsym(*dl, "NPW_Plugin"));
221 if (!info)
222 return; // Not a NSPW plugin.
223
224 // Here we could check the NSPW ident field for the versioning
225 // information, but the path field is available in all versions
226 // anyway.
227
228 // Grab the path to the wrapped plugin. Just in case the structure
229 // format changes, protect against the path not being null-terminated.
230 char* path_end = static_cast<char*>(memchr(info->path, '\0',
231 sizeof(info->path)));
232 if (!path_end)
233 path_end = info->path + sizeof(info->path);
234 base::FilePath path = base::FilePath(
235 std::string(info->path, path_end - info->path));
236
237 if (!ELFMatchesCurrentArchitecture(path)) {
238 LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a "
239 << "plugin for a different architecture; it will "
240 << "work better if you instead use a native plugin.";
241 return;
242 }
243
244 std::string error;
245 void* newdl = base::LoadNativeLibrary(path, &error);
246 if (!newdl) {
247 // We couldn't load the unwrapped plugin for some reason, despite
248 // being able to load the wrapped one. Just use the wrapped one.
249 LOG_IF(ERROR, PluginList::DebugPluginLoading())
250 << "Could not use unwrapped nspluginwrapper plugin "
251 << unwrapped_path->value() << " (" << error << "), "
252 << "using the wrapped one.";
253 return;
254 }
255
256 // Unload the wrapped plugin, and use the wrapped plugin instead.
257 LOG_IF(ERROR, PluginList::DebugPluginLoading())
258 << "Using unwrapped version " << unwrapped_path->value()
259 << " of nspluginwrapper-wrapped plugin.";
260 base::UnloadNativeLibrary(*dl);
261 *dl = newdl;
262 *unwrapped_path = path;
263 }
264
265 } // namespace
266
267 bool PluginList::ReadWebPluginInfo(const base::FilePath& filename,
268 WebPluginInfo* info) {
269 // The file to reference is:
270 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirU nix.cpp
271
272 // Skip files that aren't appropriate for our architecture.
273 if (!ELFMatchesCurrentArchitecture(filename)) {
274 LOG_IF(ERROR, PluginList::DebugPluginLoading())
275 << "Skipping plugin " << filename.value()
276 << " because it doesn't match the current architecture.";
277 return false;
278 }
279
280 std::string error;
281 void* dl = base::LoadNativeLibrary(filename, &error);
282 if (!dl) {
283 LOG_IF(ERROR, PluginList::DebugPluginLoading())
284 << "While reading plugin info, unable to load library "
285 << filename.value() << " (" << error << "), skipping.";
286 return false;
287 }
288
289 info->path = filename;
290
291 // Attempt to swap in the wrapped plugin if this is nspluginwrapper.
292 UnwrapNSPluginWrapper(&dl, &info->path);
293
294 // See comments in plugin_lib_mac regarding this symbol.
295 typedef const char* (*NP_GetMimeDescriptionType)();
296 NP_GetMimeDescriptionType NP_GetMIMEDescription =
297 reinterpret_cast<NP_GetMimeDescriptionType>(
298 dlsym(dl, "NP_GetMIMEDescription"));
299 const char* mime_description = NULL;
300 if (!NP_GetMIMEDescription) {
301 LOG_IF(ERROR, PluginList::DebugPluginLoading())
302 << "Plugin " << filename.value() << " doesn't have a "
303 << "NP_GetMIMEDescription symbol";
304 return false;
305 }
306 mime_description = NP_GetMIMEDescription();
307
308 if (!mime_description) {
309 LOG_IF(ERROR, PluginList::DebugPluginLoading())
310 << "MIME description for " << filename.value() << " is empty";
311 return false;
312 }
313 ParseMIMEDescription(mime_description, &info->mime_types);
314
315 // The plugin name and description live behind NP_GetValue calls.
316 typedef NPError (*NP_GetValueType)(void* unused,
317 nsPluginVariable variable,
318 void* value_out);
319 NP_GetValueType NP_GetValue =
320 reinterpret_cast<NP_GetValueType>(dlsym(dl, "NP_GetValue"));
321 if (NP_GetValue) {
322 const char* name = NULL;
323 NP_GetValue(NULL, nsPluginVariable_NameString, &name);
324 if (name) {
325 info->name = UTF8ToUTF16(name);
326 ExtractVersionString(name, info);
327 }
328
329 const char* description = NULL;
330 NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description);
331 if (description) {
332 info->desc = UTF8ToUTF16(description);
333 if (info->version.empty())
334 ExtractVersionString(description, info);
335 }
336
337 LOG_IF(ERROR, PluginList::DebugPluginLoading())
338 << "Got info for plugin " << filename.value()
339 << " Name = \"" << UTF16ToUTF8(info->name)
340 << "\", Description = \"" << UTF16ToUTF8(info->desc)
341 << "\", Version = \"" << UTF16ToUTF8(info->version)
342 << "\".";
343 } else {
344 LOG_IF(ERROR, PluginList::DebugPluginLoading())
345 << "Plugin " << filename.value()
346 << " has no GetValue() and probably won't work.";
347 }
348
349 // Intentionally not unloading the plugin here, it can lead to crashes.
350
351 return true;
352 }
353
354 // static
355 void PluginList::ParseMIMEDescription(
356 const std::string& description,
357 std::vector<WebPluginMimeType>* mime_types) {
358 // We parse the description here into WebPluginMimeType structures.
359 // Naively from the NPAPI docs you'd think you could use
360 // string-splitting, but the Firefox parser turns out to do something
361 // different: find the first colon, then the second, then a semi.
362 //
363 // See ParsePluginMimeDescription near
364 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirU tils.h#53
365
366 std::string::size_type ofs = 0;
367 for (;;) {
368 WebPluginMimeType mime_type;
369
370 std::string::size_type end = description.find(':', ofs);
371 if (end == std::string::npos)
372 break;
373 mime_type.mime_type = description.substr(ofs, end - ofs);
374 ofs = end + 1;
375
376 end = description.find(':', ofs);
377 if (end == std::string::npos)
378 break;
379 const std::string extensions = description.substr(ofs, end - ofs);
380 base::SplitString(extensions, ',', &mime_type.file_extensions);
381 ofs = end + 1;
382
383 end = description.find(';', ofs);
384 // It's ok for end to run off the string here. If there's no
385 // trailing semicolon we consume the remainder of the string.
386 if (end != std::string::npos) {
387 mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs));
388 } else {
389 mime_type.description = UTF8ToUTF16(description.substr(ofs));
390 }
391 mime_types->push_back(mime_type);
392 if (end == std::string::npos)
393 break;
394 ofs = end + 1;
395 }
396 }
397
398 // static
399 void PluginList::ExtractVersionString(const std::string& desc,
400 WebPluginInfo* info) {
401 // This matching works by extracting a version substring, along the lines of:
402 // No postfix: second match in .*<prefix>.*$
403 // With postfix: second match .*<prefix>.*<postfix>
404 static const struct {
405 const char* kPrefix;
406 const char* kPostfix;
407 } kPrePostFixes[] = {
408 { "Shockwave Flash ", 0 },
409 { "Java(TM) Plug-in ", 0 },
410 { "(using IcedTea-Web ", " " },
411 { 0, 0 }
412 };
413 std::string version;
414 for (size_t i = 0; kPrePostFixes[i].kPrefix; ++i) {
415 size_t pos;
416 if ((pos = desc.find(kPrePostFixes[i].kPrefix)) != std::string::npos) {
417 version = desc.substr(pos + strlen(kPrePostFixes[i].kPrefix));
418 pos = std::string::npos;
419 if (kPrePostFixes[i].kPostfix)
420 pos = version.find(kPrePostFixes[i].kPostfix);
421 if (pos != std::string::npos)
422 version = version.substr(0, pos);
423 break;
424 }
425 }
426 if (!version.empty()) {
427 info->version = UTF8ToUTF16(version);
428 }
429 }
430
431 void PluginList::GetPluginDirectories(std::vector<base::FilePath>* plugin_dirs) {
432 // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5 fcbac786a9
433 // for discussion.
434 // We first consult Chrome-specific dirs, then fall back on the logic
435 // Mozilla uses.
436
437 if (PluginList::plugins_discovery_disabled_)
438 return;
439
440 // Note: "extra" plugin dirs and paths are examined before these.
441 // "Extra" are those added by PluginList::AddExtraPluginDir() and
442 // PluginList::AddExtraPluginPath().
443
444 // The Chrome binary dir + "plugins/".
445 base::FilePath dir;
446 PathService::Get(base::DIR_EXE, &dir);
447 plugin_dirs->push_back(dir.Append("plugins"));
448
449 // Chrome OS only loads plugins from /opt/google/chrome/plugins.
450 #if !defined(OS_CHROMEOS)
451 // Mozilla code to reference:
452 // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST
453 // and tens of accompanying files (mxr is very helpful).
454 // This code carefully matches their behavior for compat reasons.
455
456 // 1) MOZ_PLUGIN_PATH env variable.
457 const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH");
458 if (moz_plugin_path) {
459 std::vector<std::string> paths;
460 base::SplitString(moz_plugin_path, ':', &paths);
461 for (size_t i = 0; i < paths.size(); ++i)
462 plugin_dirs->push_back(base::FilePath(paths[i]));
463 }
464
465 // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins.
466 // This is a de-facto standard, so even though we're not Mozilla, let's
467 // look in there too.
468 base::FilePath home = file_util::GetHomeDir();
469 if (!home.empty())
470 plugin_dirs->push_back(home.Append(".mozilla/plugins"));
471
472 // 3) NS_SYSTEM_PLUGINS_DIR:
473 // This varies across different browsers and versions, so check 'em all.
474 plugin_dirs->push_back(base::FilePath("/usr/lib/browser-plugins"));
475 plugin_dirs->push_back(base::FilePath("/usr/lib/mozilla/plugins"));
476 plugin_dirs->push_back(base::FilePath("/usr/lib/firefox/plugins"));
477 plugin_dirs->push_back(base::FilePath("/usr/lib/xulrunner-addons/plugins"));
478
479 #if defined(ARCH_CPU_64_BITS)
480 // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib.
481 // But a user reported on their Fedora system they are separate.
482 plugin_dirs->push_back(base::FilePath("/usr/lib64/browser-plugins"));
483 plugin_dirs->push_back(base::FilePath("/usr/lib64/mozilla/plugins"));
484 plugin_dirs->push_back(base::FilePath("/usr/lib64/firefox/plugins"));
485 plugin_dirs->push_back(base::FilePath("/usr/lib64/xulrunner-addons/plugins"));
486 #endif // defined(ARCH_CPU_64_BITS)
487 #endif // !defined(OS_CHROMEOS)
488 }
489
490 void PluginList::GetPluginsInDir(
491 const base::FilePath& dir_path, std::vector<base::FilePath>* plugins) {
492 // See ScanPluginsDirectory near
493 // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostI mpl.cpp#5052
494
495 // Construct and stat a list of all filenames under consideration, for
496 // later sorting by mtime.
497 FileTimeList files;
498 base::FileEnumerator enumerator(dir_path,
499 false, // not recursive
500 base::FileEnumerator::FILES);
501 for (base::FilePath path = enumerator.Next(); !path.value().empty();
502 path = enumerator.Next()) {
503 // Skip over Mozilla .xpt files.
504 if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt")))
505 continue;
506
507 // Java doesn't like being loaded through a symlink, since it uses
508 // its path to find dependent data files.
509 // MakeAbsoluteFilePath calls through to realpath(), which resolves
510 // symlinks.
511 base::FilePath orig_path = path;
512 path = base::MakeAbsoluteFilePath(path);
513 if (path.empty())
514 path = orig_path;
515 LOG_IF(ERROR, PluginList::DebugPluginLoading())
516 << "Resolved " << orig_path.value() << " -> " << path.value();
517
518 if (std::find(plugins->begin(), plugins->end(), path) != plugins->end()) {
519 LOG_IF(ERROR, PluginList::DebugPluginLoading())
520 << "Skipping duplicate instance of " << path.value();
521 continue;
522 }
523
524 if (IsBlacklistedPlugin(path)) {
525 LOG_IF(ERROR, PluginList::DebugPluginLoading())
526 << "Skipping blacklisted plugin " << path.value();
527 continue;
528 }
529
530 // Flash stops working if the containing directory involves 'netscape'.
531 // No joke. So use the other path if it's better.
532 static const char kFlashPlayerFilename[] = "libflashplayer.so";
533 static const char kNetscapeInPath[] = "/netscape/";
534 if (path.BaseName().value() == kFlashPlayerFilename &&
535 path.value().find(kNetscapeInPath) != std::string::npos) {
536 if (orig_path.value().find(kNetscapeInPath) == std::string::npos) {
537 // Go back to the old path.
538 path = orig_path;
539 } else {
540 LOG_IF(ERROR, PluginList::DebugPluginLoading())
541 << "Flash misbehaves when used from a directory containing "
542 << kNetscapeInPath << ", so skipping " << orig_path.value();
543 continue;
544 }
545 }
546
547 // Get mtime.
548 base::PlatformFileInfo info;
549 if (!file_util::GetFileInfo(path, &info))
550 continue;
551
552 files.push_back(std::make_pair(path, info.last_modified));
553 }
554
555 // Sort the file list by time (and filename).
556 std::sort(files.begin(), files.end(), CompareTime);
557
558 // Load the files in order.
559 for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) {
560 plugins->push_back(i->first);
561 }
562 }
563
564 bool PluginList::ShouldLoadPluginUsingPluginList(
565 const WebPluginInfo& info, std::vector<webkit::WebPluginInfo>* plugins) {
566 LOG_IF(ERROR, PluginList::DebugPluginLoading())
567 << "Considering " << info.path.value() << " (" << info.name << ")";
568
569 if (IsUndesirablePlugin(info)) {
570 LOG_IF(ERROR, PluginList::DebugPluginLoading())
571 << info.path.value() << " is undesirable.";
572
573 // See if we have a better version of this plugin.
574 for (size_t j = 0; j < plugins->size(); ++j) {
575 if ((*plugins)[j].name == info.name &&
576 !IsUndesirablePlugin((*plugins)[j])) {
577 // Skip the current undesirable one so we can use the better one
578 // we just found.
579 LOG_IF(ERROR, PluginList::DebugPluginLoading())
580 << "Skipping " << info.path.value() << ", preferring "
581 << (*plugins)[j].path.value();
582 return false;
583 }
584 }
585 }
586
587 // TODO(evanm): prefer the newest version of flash, etc. here?
588
589 VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value();
590
591 return true;
592 }
593
594 } // namespace npapi
595 } // namespace webkit
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698