1/*
2 * Copyright (C) 2008 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
27
28#import "NetscapePluginHostManager.h"
29
30#import "NetscapePluginHostProxy.h"
31#import "NetscapePluginInstanceProxy.h"
32#import "WebLocalizableStringsInternal.h"
33#import "WebKitSystemInterface.h"
34#import "WebNetscapePluginPackage.h"
35#import <mach/mach_port.h>
36#import <servers/bootstrap.h>
37#import <spawn.h>
38#import <wtf/Assertions.h>
39#import <wtf/RetainPtr.h>
40#import <wtf/StdLibExtras.h>
41
42extern "C" {
43#import "WebKitPluginAgent.h"
44#import "WebKitPluginHost.h"
45}
46
47using namespace WebCore;
48
49namespace WebKit {
50
51NetscapePluginHostManager& NetscapePluginHostManager::shared()
52{
53    DEPRECATED_DEFINE_STATIC_LOCAL(NetscapePluginHostManager, pluginHostManager, ());
54
55    return pluginHostManager;
56}
57
58static NSString * const pluginHostAppName = @"WebKitPluginHost.app";
59
60NetscapePluginHostManager::NetscapePluginHostManager()
61    : m_pluginVendorPort(MACH_PORT_NULL)
62{
63}
64
65NetscapePluginHostManager::~NetscapePluginHostManager()
66{
67}
68
69NetscapePluginHostProxy* NetscapePluginHostManager::hostForPlugin(const WTF::String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier)
70{
71    PluginHostMap::AddResult result = m_pluginHosts.add(pluginPath, nullptr);
72
73    // The package was already in the map, just return it.
74    if (!result.isNewEntry)
75        return result.iterator->value;
76
77    mach_port_t clientPort;
78    if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &clientPort) != KERN_SUCCESS) {
79        m_pluginHosts.remove(result.iterator);
80        return 0;
81    }
82
83    mach_port_t pluginHostPort;
84    ProcessSerialNumber pluginHostPSN;
85    if (!spawnPluginHost(pluginPath, pluginArchitecture, clientPort, pluginHostPort, pluginHostPSN)) {
86        mach_port_destroy(mach_task_self(), clientPort);
87        m_pluginHosts.remove(result.iterator);
88        return 0;
89    }
90
91    // Since Flash NPObjects add methods dynamically, we don't want to cache when a property/method doesn't exist
92    // on an object because it could be added later.
93    bool shouldCacheMissingPropertiesAndMethods = bundleIdentifier != "com.macromedia.Flash Player.plugin";
94
95    NetscapePluginHostProxy* hostProxy = new NetscapePluginHostProxy(clientPort, pluginHostPort, pluginHostPSN, shouldCacheMissingPropertiesAndMethods);
96
97    result.iterator->value = hostProxy;
98
99    return hostProxy;
100}
101
102bool NetscapePluginHostManager::spawnPluginHost(const String& pluginPath, cpu_type_t pluginArchitecture, mach_port_t clientPort, mach_port_t& pluginHostPort, ProcessSerialNumber& pluginHostPSN)
103{
104    if (m_pluginVendorPort == MACH_PORT_NULL) {
105        if (!initializeVendorPort())
106            return false;
107    }
108
109    mach_port_t renderServerPort = WKInitializeRenderServer();
110    if (renderServerPort == MACH_PORT_NULL)
111        return false;
112
113    NSString *pluginHostAppPath = [[NSBundle bundleForClass:[WebNetscapePluginPackage class]] pathForAuxiliaryExecutable:pluginHostAppName];
114    NSString *pluginHostAppExecutablePath = [[NSBundle bundleWithPath:pluginHostAppPath] executablePath];
115
116    RetainPtr<CFStringRef> localization = adoptCF(WKCopyCFLocalizationPreferredName(NULL));
117
118    NSDictionary *launchProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
119                                      pluginHostAppExecutablePath, @"pluginHostPath",
120                                      [NSNumber numberWithInt:pluginArchitecture], @"cpuType",
121                                      (NSString *)localization.get(), @"localization",
122                                      nil];
123
124    NSData *data = [NSPropertyListSerialization dataWithPropertyList:launchProperties format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
125    ASSERT(data);
126
127    [launchProperties release];
128
129    kern_return_t kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
130
131    if (kr == MACH_SEND_INVALID_DEST) {
132        // The plug-in vendor port has gone away for some reason. Try to reinitialize it.
133        m_pluginVendorPort = MACH_PORT_NULL;
134        if (!initializeVendorPort())
135            return false;
136
137        // And spawn the plug-in host again.
138        kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
139    }
140
141    if (kr != KERN_SUCCESS) {
142        // FIXME: Check for invalid dest and try to re-spawn the plug-in agent.
143        LOG_ERROR("Failed to spawn plug-in host, error %x", kr);
144        return false;
145    }
146
147    NSString *visibleName = [NSString stringWithFormat:UI_STRING_INTERNAL("%@ (%@ Internet plug-in)",
148                                                                 "visible name of the plug-in host process. The first argument is the plug-in name "
149                                                                 "and the second argument is the application name."),
150                             [[(NSString*)pluginPath lastPathComponent] stringByDeletingPathExtension], [[NSProcessInfo processInfo] processName]];
151
152    NSDictionary *hostProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
153                                    visibleName, @"visibleName",
154                                    (NSString *)pluginPath, @"bundlePath",
155                                    nil];
156
157    data = [NSPropertyListSerialization dataWithPropertyList:hostProperties format:NSPropertyListBinaryFormat_v1_0 options:0 error:NULL];
158    ASSERT(data);
159
160    [hostProperties release];
161
162    ProcessSerialNumber psn;
163
164#pragma clang diagnostic push
165#pragma clang diagnostic ignored "-Wdeprecated-declarations"
166    GetCurrentProcess(&psn);
167#pragma clang diagnostic pop
168
169    kr = _WKPHCheckInWithPluginHost(pluginHostPort, (uint8_t*)[data bytes], [data length], clientPort, psn.highLongOfPSN, psn.lowLongOfPSN, renderServerPort,
170                                    &pluginHostPSN.highLongOfPSN, &pluginHostPSN.lowLongOfPSN);
171
172    if (kr != KERN_SUCCESS) {
173        mach_port_deallocate(mach_task_self(), pluginHostPort);
174        LOG_ERROR("Failed to check in with plug-in host, error %x", kr);
175
176        return false;
177    }
178
179    return true;
180}
181
182bool NetscapePluginHostManager::initializeVendorPort()
183{
184    ASSERT(m_pluginVendorPort == MACH_PORT_NULL);
185
186    // Get the plug-in agent port.
187    mach_port_t pluginAgentPort;
188    if (bootstrap_look_up(bootstrap_port, "com.apple.WebKit.PluginAgent", &pluginAgentPort) != KERN_SUCCESS) {
189        LOG_ERROR("Failed to look up the plug-in agent port");
190        return false;
191    }
192
193    NSData *appNameData = [[[NSProcessInfo processInfo] processName] dataUsingEncoding:NSUTF8StringEncoding];
194
195    // Tell the plug-in agent that we exist.
196    if (_WKPACheckInApplication(pluginAgentPort, (uint8_t*)[appNameData bytes], [appNameData length], &m_pluginVendorPort) != KERN_SUCCESS)
197        return false;
198
199    // FIXME: Should we add a notification for when the vendor port dies?
200
201    return true;
202}
203
204void NetscapePluginHostManager::pluginHostDied(NetscapePluginHostProxy* pluginHost)
205{
206    PluginHostMap::iterator end = m_pluginHosts.end();
207
208    // This has O(n) complexity but the number of active plug-in hosts is very small so it shouldn't matter.
209    for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
210        if (it->value == pluginHost) {
211            m_pluginHosts.remove(it);
212            return;
213        }
214    }
215}
216
217PassRefPtr<NetscapePluginInstanceProxy> NetscapePluginHostManager::instantiatePlugin(const String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier, WebHostedNetscapePluginView *pluginView, NSString *mimeType, NSArray *attributeKeys, NSArray *attributeValues, NSString *userAgent, NSURL *sourceURL, bool fullFrame, bool isPrivateBrowsingEnabled, bool isAcceleratedCompositingEnabled, bool hostLayersInWindowServer)
218{
219    NetscapePluginHostProxy* hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
220    if (!hostProxy)
221        return 0;
222
223    RetainPtr<NSMutableDictionary> properties = adoptNS([[NSMutableDictionary alloc] init]);
224
225    if (mimeType)
226        [properties.get() setObject:mimeType forKey:@"mimeType"];
227
228    ASSERT_ARG(userAgent, userAgent);
229    [properties.get() setObject:userAgent forKey:@"userAgent"];
230
231    ASSERT_ARG(attributeKeys, attributeKeys);
232    [properties.get() setObject:attributeKeys forKey:@"attributeKeys"];
233
234    ASSERT_ARG(attributeValues, attributeValues);
235    [properties.get() setObject:attributeValues forKey:@"attributeValues"];
236
237    if (sourceURL)
238        [properties.get() setObject:[sourceURL absoluteString] forKey:@"sourceURL"];
239
240    [properties.get() setObject:[NSNumber numberWithBool:fullFrame] forKey:@"fullFrame"];
241    [properties.get() setObject:[NSNumber numberWithBool:isPrivateBrowsingEnabled] forKey:@"privateBrowsingEnabled"];
242    [properties.get() setObject:[NSNumber numberWithBool:isAcceleratedCompositingEnabled] forKey:@"acceleratedCompositingEnabled"];
243    [properties.get() setObject:[NSNumber numberWithBool:hostLayersInWindowServer] forKey:@"hostLayersInWindowServer"];
244
245    NSData *data = [NSPropertyListSerialization dataWithPropertyList:properties.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
246    ASSERT(data);
247
248    RefPtr<NetscapePluginInstanceProxy> instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
249    uint32_t requestID = instance->nextRequestID();
250    kern_return_t kr = _WKPHInstantiatePlugin(hostProxy->port(), requestID, (uint8_t*)[data bytes], [data length], instance->pluginID());
251    if (kr == MACH_SEND_INVALID_DEST) {
252        // Invalidate the instance.
253        instance->invalidate();
254
255        // The plug-in host must have died, but we haven't received the death notification yet.
256        pluginHostDied(hostProxy);
257
258        // Try to spawn it again.
259        hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
260
261        // Create a new instance.
262        instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
263        requestID = instance->nextRequestID();
264        _WKPHInstantiatePlugin(hostProxy->port(), requestID, (uint8_t*)[data bytes], [data length], instance->pluginID());
265    }
266
267    auto reply = instance->waitForReply<NetscapePluginInstanceProxy::InstantiatePluginReply>(requestID);
268    if (!reply || reply->m_resultCode != KERN_SUCCESS) {
269        instance->cleanup();
270        return nullptr;
271    }
272
273    instance->setRenderContextID(reply->m_renderContextID);
274    instance->setRendererType(reply->m_rendererType);
275
276    return instance.release();
277}
278
279void NetscapePluginHostManager::createPropertyListFile(const String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier)
280{
281    NetscapePluginHostProxy* hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
282    if (!hostProxy)
283        return;
284
285    _WKPHCreatePluginMIMETypesPreferences(hostProxy->port());
286}
287
288void NetscapePluginHostManager::didCreateWindow()
289{
290    // See if any of our hosts are in full-screen mode.
291    PluginHostMap::iterator end = m_pluginHosts.end();
292    for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
293        NetscapePluginHostProxy* hostProxy = it->value;
294
295        if (!hostProxy->isMenuBarVisible()) {
296            // Make ourselves the front process.
297            ProcessSerialNumber psn;
298#pragma clang diagnostic push
299#pragma clang diagnostic ignored "-Wdeprecated-declarations"
300            GetCurrentProcess(&psn);
301            SetFrontProcess(&psn);
302#pragma clang diagnostic pop
303            return;
304        }
305    }
306}
307
308} // namespace WebKit
309
310#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
311