1/* 2 * Copyright (C) 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "NetscapePluginModule.h" 28 29#if ENABLE(NETSCAPE_PLUGIN_API) 30 31#import "PluginProcessProxy.h" 32#import "PluginSandboxProfile.h" 33#import <WebCore/WebCoreNSStringExtras.h> 34#import <wtf/HashSet.h> 35#import <wtf/MainThread.h> 36 37using namespace WebCore; 38 39namespace WebKit { 40 41static bool getPluginArchitecture(CFBundleRef bundle, PluginModuleInfo& plugin) 42{ 43 RetainPtr<CFArrayRef> pluginArchitecturesArray = adoptCF(CFBundleCopyExecutableArchitectures(bundle)); 44 if (!pluginArchitecturesArray) 45 return false; 46 47 // Turn the array into a set. 48 HashSet<unsigned> architectures; 49 for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) { 50 CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i)); 51 52 SInt32 architecture; 53 if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture)) 54 continue; 55 architectures.add(architecture); 56 } 57 58#ifdef __x86_64__ 59 // We only support 64-bit Intel plug-ins on 64-bit Intel. 60 if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) { 61 plugin.pluginArchitecture = CPU_TYPE_X86_64; 62 return true; 63 } 64 65 // We also support 32-bit Intel plug-ins on 64-bit Intel. 66 if (architectures.contains(kCFBundleExecutableArchitectureI386)) { 67 plugin.pluginArchitecture = CPU_TYPE_X86; 68 return true; 69 } 70#elif defined(__i386__) 71 // We only support 32-bit Intel plug-ins on 32-bit Intel. 72 if (architectures.contains(kCFBundleExecutableArchitectureI386)) { 73 plugin.pluginArchitecture = CPU_TYPE_X86; 74 return true; 75 } 76#elif defined(__ppc64__) 77 // We only support 64-bit PPC plug-ins on 64-bit PPC. 78 if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) { 79 plugin.pluginArchitecture = CPU_TYPE_POWERPC64; 80 return true; 81 } 82#elif defined(__ppc__) 83 // We only support 32-bit PPC plug-ins on 32-bit PPC. 84 if (architectures.contains(kCFBundleExecutableArchitecturePPC)) { 85 plugin.pluginArchitecture = CPU_TYPE_POWERPC; 86 return true; 87 } 88#else 89#error "Unhandled architecture" 90#endif 91 92 return false; 93} 94 95static RetainPtr<CFDictionaryRef> contentsOfPropertyListAtURL(CFURLRef propertyListURL) 96{ 97 RetainPtr<NSData> propertyListData = adoptNS([[NSData alloc] initWithContentsOfURL:(NSURL *)propertyListURL]); 98 if (!propertyListData) 99 return 0; 100 101 RetainPtr<CFPropertyListRef> propertyList = adoptCF(CFPropertyListCreateWithData(kCFAllocatorDefault, (CFDataRef)propertyListData.get(), kCFPropertyListImmutable, 0, 0)); 102 if (!propertyList) 103 return 0; 104 105 if (CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID()) 106 return 0; 107 108 return static_cast<CFDictionaryRef>(propertyList.get()); 109} 110 111static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle, const PluginModuleInfo& plugin) 112{ 113 CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename"))); 114 if (propertyListFilename) { 115 RetainPtr<CFStringRef> propertyListPath = adoptCF(CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename)); 116 RetainPtr<CFURLRef> propertyListURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE)); 117 118 RetainPtr<CFDictionaryRef> propertyList = contentsOfPropertyListAtURL(propertyListURL.get()); 119 120 if (!propertyList && PluginProcessProxy::createPropertyListFile(plugin)) 121 propertyList = contentsOfPropertyListAtURL(propertyListURL.get()); 122 123 if (!propertyList) 124 return 0; 125 126 return static_cast<CFDictionaryRef>(CFDictionaryGetValue(propertyList.get(), CFSTR("WebPluginMIMETypes"))); 127 } 128 129 return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes"))); 130} 131 132static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginModuleInfo& plugin) 133{ 134 RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle, plugin); 135 if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID()) 136 return false; 137 138 // Get the plug-in name. 139 CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName"))); 140 if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID()) 141 plugin.info.name = pluginName; 142 143 // Get the plug-in description. 144 CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription"))); 145 if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID()) 146 plugin.info.desc = pluginDescription; 147 148 // Get the MIME type mapping dictionary. 149 CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get()); 150 Vector<CFStringRef> mimeTypesVector(numMimeTypes); 151 Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes); 152 CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data())); 153 154 for (CFIndex i = 0; i < numMimeTypes; ++i) { 155 MimeClassInfo mimeClassInfo; 156 157 // If this MIME type is invalid, ignore it. 158 CFStringRef mimeType = mimeTypesVector[i]; 159 if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0) 160 continue; 161 162 // If this MIME type doesn't have a valid info dictionary, ignore it. 163 CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i]; 164 if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID()) 165 continue; 166 167 // FIXME: Consider storing disabled MIME types. 168 CFTypeRef isEnabled = CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeEnabled")); 169 if (isEnabled) { 170 if (CFGetTypeID(isEnabled) == CFNumberGetTypeID()) { 171 int value; 172 if (!CFNumberGetValue(static_cast<CFNumberRef>(isEnabled), kCFNumberIntType, &value) || !value) 173 continue; 174 } else if (CFGetTypeID(isEnabled) == CFBooleanGetTypeID()) { 175 if (!CFBooleanGetValue(static_cast<CFBooleanRef>(isEnabled))) 176 continue; 177 } else 178 continue; 179 } 180 181 // Get the MIME type description. 182 CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription"))); 183 if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID()) 184 mimeTypeDescription = 0; 185 186 mimeClassInfo.type = String(mimeType).lower(); 187 mimeClassInfo.desc = mimeTypeDescription; 188 189 // Now get the extensions for this MIME type. 190 CFIndex numExtensions = 0; 191 CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions"))); 192 if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID()) 193 numExtensions = CFArrayGetCount(extensionsArray); 194 195 for (CFIndex i = 0; i < numExtensions; ++i) { 196 CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i)); 197 if (!extension || CFGetTypeID(extension) != CFStringGetTypeID()) 198 continue; 199 200 // The DivX plug-in lists multiple extensions in a comma separated string instead of using 201 // multiple array elements in the property list. Work around this here by splitting the 202 // extension string into components. 203 Vector<String> extensionComponents; 204 String(extension).lower().split(',', extensionComponents); 205 206 for (size_t i = 0; i < extensionComponents.size(); ++i) 207 mimeClassInfo.extensions.append(extensionComponents[i]); 208 } 209 210 // Add this MIME type. 211 plugin.info.mimes.append(mimeClassInfo); 212 } 213 214 return true; 215} 216 217#pragma clang diagnostic push 218#pragma clang diagnostic ignored "-Wdeprecated-declarations" 219 220class ResourceMap { 221public: 222 explicit ResourceMap(CFBundleRef bundle) 223 : m_bundle(bundle) 224 , m_currentResourceFile(CurResFile()) 225 , m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle)) 226 { 227 UseResFile(m_bundleResourceMap); 228 } 229 230 ~ResourceMap() 231 { 232 // Close the resource map. 233 CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap); 234 235 // And restore the old resource. 236 UseResFile(m_currentResourceFile); 237 } 238 239 bool isValid() const { return m_bundleResourceMap != -1; } 240 241private: 242 CFBundleRef m_bundle; 243 ResFileRefNum m_currentResourceFile; 244 ResFileRefNum m_bundleResourceMap; 245}; 246 247static bool getStringListResource(ResID resourceID, Vector<String>& stringList) { 248 Handle stringListHandle = Get1Resource('STR#', resourceID); 249 if (!stringListHandle || !*stringListHandle) 250 return false; 251 252 // Get the string list size. 253 Size stringListSize = GetHandleSize(stringListHandle); 254 if (stringListSize < static_cast<Size>(sizeof(UInt16))) 255 return false; 256 257 CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle); 258 259 unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle); 260 unsigned char* end = ptr + stringListSize; 261 262 // Get the number of strings in the string list. 263 UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr); 264 ptr += sizeof(UInt16); 265 266 for (UInt16 i = 0; i < numStrings; ++i) { 267 // We're past the end of the string, bail. 268 if (ptr >= end) 269 return false; 270 271 // Get the string length. 272 unsigned char stringLength = *ptr++; 273 274 RetainPtr<CFStringRef> cfString = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull)); 275 if (!cfString.get()) 276 return false; 277 278 stringList.append(cfString.get()); 279 ptr += stringLength; 280 } 281 282 if (ptr != end) 283 return false; 284 285 return true; 286} 287 288#pragma clang diagnostic pop 289 290static const ResID PluginNameOrDescriptionStringNumber = 126; 291static const ResID MIMEDescriptionStringNumber = 127; 292static const ResID MIMEListStringStringNumber = 128; 293 294static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginModuleInfo& plugin) 295{ 296 ASSERT(RunLoop::isMain()); 297 298 ResourceMap resourceMap(bundle); 299 if (!resourceMap.isValid()) 300 return false; 301 302 // Get the description and name string list. 303 Vector<String> descriptionAndName; 304 if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName)) 305 return false; 306 307 // Get the MIME types and extensions string list. This list needs to be a multiple of two. 308 Vector<String> mimeTypesAndExtensions; 309 if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions)) 310 return false; 311 312 if (mimeTypesAndExtensions.size() % 2) 313 return false; 314 315 // Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types. 316 Vector<String> mimeTypeDescriptions; 317 if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions)) 318 return false; 319 320 // Add all MIME types. 321 for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) { 322 MimeClassInfo mimeClassInfo; 323 324 const String& mimeType = mimeTypesAndExtensions[i * 2]; 325 String description; 326 if (i < mimeTypeDescriptions.size()) 327 description = mimeTypeDescriptions[i]; 328 329 mimeClassInfo.type = mimeType.lower(); 330 mimeClassInfo.desc = description; 331 332 Vector<String> extensions; 333 mimeTypesAndExtensions[i * 2 + 1].split(',', extensions); 334 335 for (size_t i = 0; i < extensions.size(); ++i) 336 mimeClassInfo.extensions.append(extensions[i].lower()); 337 338 plugin.info.mimes.append(mimeClassInfo); 339 } 340 341 // Set the description and name if they exist. 342 if (descriptionAndName.size() > 0) 343 plugin.info.desc = descriptionAndName[0]; 344 if (descriptionAndName.size() > 1) 345 plugin.info.name = descriptionAndName[1]; 346 347 return true; 348} 349 350bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginModuleInfo& plugin) 351{ 352 RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false)); 353 354 // Try to initialize the bundle. 355 RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get())); 356 if (!bundle) 357 return false; 358 359 // Check if this bundle is an NPAPI plug-in. 360 UInt32 packageType = 0; 361 CFBundleGetPackageInfo(bundle.get(), &packageType, 0); 362 if (packageType != FOUR_CHAR_CODE('BRPL')) 363 return false; 364 365 // Check that the architecture is valid. 366 if (!getPluginArchitecture(bundle.get(), plugin)) 367 return false; 368 369 plugin.path = pluginPath; 370 plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get()); 371 if (CFTypeRef versionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), kCFBundleVersionKey)) { 372 if (CFGetTypeID(versionTypeRef) == CFStringGetTypeID()) 373 plugin.versionString = static_cast<CFStringRef>(versionTypeRef); 374 } 375 376 if (CFTypeRef shortVersionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("CFBundleShortVersionString"))) { 377 if (CFGetTypeID(shortVersionTypeRef) == CFStringGetTypeID()) 378 plugin.shortVersionString = static_cast<CFStringRef>(shortVersionTypeRef); 379 } 380 381 if (CFTypeRef preferencePathTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("WebPluginPreferencePanePath"))) { 382 if (CFGetTypeID(preferencePathTypeRef) == CFStringGetTypeID()) 383 plugin.preferencePanePath = static_cast<CFStringRef>(preferencePathTypeRef); 384 } 385 386 // Check that there's valid info for this plug-in. 387 if (!getPluginInfoFromPropertyLists(bundle.get(), plugin) && 388 !getPluginInfoFromCarbonResources(bundle.get(), plugin)) 389 return false; 390 391 plugin.hasSandboxProfile = pluginHasSandboxProfile(plugin.bundleIdentifier); 392 393 RetainPtr<CFStringRef> filename = adoptCF(CFURLCopyLastPathComponent(bundleURL.get())); 394 plugin.info.file = filename.get(); 395 396 if (plugin.info.name.isNull()) 397 plugin.info.name = plugin.info.file; 398 if (plugin.info.desc.isNull()) 399 plugin.info.desc = plugin.info.file; 400 401 plugin.info.isApplicationPlugin = false; 402 403 return true; 404} 405 406bool NetscapePluginModule::createPluginMIMETypesPreferences(const String& pluginPath) 407{ 408 RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false)); 409 410 RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get())); 411 if (!bundle) 412 return false; 413 414 if (!CFBundleLoadExecutable(bundle.get())) 415 return false; 416 417 void (*createPluginMIMETypesPreferences)(void) = reinterpret_cast<void (*)(void)>(CFBundleGetFunctionPointerForName(bundle.get(), CFSTR("BP_CreatePluginMIMETypesPreferences"))); 418 if (!createPluginMIMETypesPreferences) 419 return false; 420 421 createPluginMIMETypesPreferences(); 422 return true; 423} 424 425// FIXME: This doesn't need to be platform-specific. 426class PluginVersion { 427public: 428 static PluginVersion parse(const String& versionString); 429 430 bool isLessThan(unsigned componentA) const; 431 bool isValid() const { return !m_versionComponents.isEmpty(); } 432 433private: 434 PluginVersion() 435 { 436 } 437 438 Vector<unsigned, 4> m_versionComponents; 439}; 440 441PluginVersion PluginVersion::parse(const String& versionString) 442{ 443 PluginVersion version; 444 445 Vector<String> versionStringComponents; 446 versionString.split(".", versionStringComponents); 447 for (size_t i = 0; i < versionStringComponents.size(); ++i) { 448 bool successfullyParsed = false; 449 unsigned versionComponent = versionStringComponents[i].toUInt(&successfullyParsed); 450 if (!successfullyParsed) 451 return PluginVersion(); 452 453 version.m_versionComponents.append(versionComponent); 454 } 455 456 return version; 457} 458 459bool PluginVersion::isLessThan(unsigned componentA) const 460{ 461 ASSERT(isValid()); 462 463 return m_versionComponents[0] < componentA; 464} 465 466void NetscapePluginModule::determineQuirks() 467{ 468 PluginModuleInfo plugin; 469 if (!getPluginInfo(m_pluginPath, plugin)) 470 return; 471 472 if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") { 473 // Flash requires that the return value of getprogname() be "WebKitPluginHost". 474 m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost); 475 476 // Flash supports snapshotting. 477 m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting); 478 479 // Flash returns a retained Core Animation layer. 480 m_pluginQuirks.add(PluginQuirks::ReturnsRetainedCoreAnimationLayer); 481 482 // Flash has a bug where NSExceptions can be released too early. 483 m_pluginQuirks.add(PluginQuirks::LeakAllThrownNSExceptions); 484 } 485 486 if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") { 487 // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever 488 // there's a 'background' attribute that's set to a transparent color. 489 m_pluginQuirks.add(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists); 490 491 // Silverlight has a workaround for a leak in Safari 2. This workaround is 492 // applied when the user agent does not contain "Version/3" so we append it 493 // at the end of the user agent. 494 m_pluginQuirks.add(PluginQuirks::AppendVersion3UserAgent); 495 496 PluginVersion pluginVersion = PluginVersion::parse(plugin.versionString); 497 if (pluginVersion.isValid()) { 498 if (pluginVersion.isLessThan(4)) { 499 // Versions of Silverlight prior to 4 don't retain the scriptable NPObject. 500 m_pluginQuirks.add(PluginQuirks::ReturnsNonRetainedScriptableNPObject); 501 } 502 } 503 } 504 505 if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") { 506 // <rdar://problem/8440903>: AppleConnect has a bug where it does not 507 // understand the parameter names specified in the <object> element that 508 // embeds its plug-in. 509 m_pluginQuirks.add(PluginQuirks::WantsLowercaseParameterNames); 510 511#ifndef NP_NO_QUICKDRAW 512 // The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events 513 // so we'll allow it to be instantiated even though we don't support QuickDraw. 514 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport); 515#endif 516 } 517 518#ifndef NP_NO_QUICKDRAW 519 if (plugin.bundleIdentifier == "com.microsoft.sharepoint.browserplugin") { 520 // The Microsoft SharePoint plug-in uses QuickDraw but doesn't paint or receive events 521 // so we'll allow it to be instantiated even though we don't support QuickDraw. 522 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport); 523 } 524 525 if (plugin.bundleIdentifier == "com.jattesaker.macid2.NPPlugin") { 526 // The BankID plug-in uses QuickDraw but doesn't paint or receive events 527 // so we'll allow it to be instantiated even though we don't support QuickDraw. 528 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport); 529 } 530#endif 531 532 if (plugin.bundleIdentifier == "com.adobe.acrobat.pdfviewerNPAPI") { 533 // The Adobe Reader plug-in wants wheel events. 534 m_pluginQuirks.add(PluginQuirks::WantsWheelEvents); 535 } 536} 537 538} // namespace WebKit 539 540#endif // ENABLE(NETSCAPE_PLUGIN_API) 541