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