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 std;
48using namespace WebCore;
49
50namespace WebKit {
51
52NetscapePluginHostManager& NetscapePluginHostManager::shared()
53{
54    DEFINE_STATIC_LOCAL(NetscapePluginHostManager, pluginHostManager, ());
55
56    return pluginHostManager;
57}
58
59static NSString * const pluginHostAppName = @"WebKitPluginHost.app";
60
61NetscapePluginHostManager::NetscapePluginHostManager()
62    : m_pluginVendorPort(MACH_PORT_NULL)
63{
64}
65
66NetscapePluginHostManager::~NetscapePluginHostManager()
67{
68}
69
70NetscapePluginHostProxy* NetscapePluginHostManager::hostForPlugin(const WTF::String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier)
71{
72    pair<PluginHostMap::iterator, bool> result = m_pluginHosts.add(pluginPath, 0);
73
74    // The package was already in the map, just return it.
75    if (!result.second)
76        return result.first->second;
77
78    mach_port_t clientPort;
79    if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &clientPort) != KERN_SUCCESS) {
80        m_pluginHosts.remove(result.first);
81        return 0;
82    }
83
84    mach_port_t pluginHostPort;
85    ProcessSerialNumber pluginHostPSN;
86    if (!spawnPluginHost(pluginPath, pluginArchitecture, clientPort, pluginHostPort, pluginHostPSN)) {
87        mach_port_destroy(mach_task_self(), clientPort);
88        m_pluginHosts.remove(result.first);
89        return 0;
90    }
91
92    // Since Flash NPObjects add methods dynamically, we don't want to cache when a property/method doesn't exist
93    // on an object because it could be added later.
94    bool shouldCacheMissingPropertiesAndMethods = bundleIdentifier != "com.macromedia.Flash Player.plugin";
95
96    NetscapePluginHostProxy* hostProxy = new NetscapePluginHostProxy(clientPort, pluginHostPort, pluginHostPSN, shouldCacheMissingPropertiesAndMethods);
97
98    result.first->second = hostProxy;
99
100    return hostProxy;
101}
102
103bool NetscapePluginHostManager::spawnPluginHost(const String& pluginPath, cpu_type_t pluginArchitecture, mach_port_t clientPort, mach_port_t& pluginHostPort, ProcessSerialNumber& pluginHostPSN)
104{
105    if (m_pluginVendorPort == MACH_PORT_NULL) {
106        if (!initializeVendorPort())
107            return false;
108    }
109
110    mach_port_t renderServerPort = WKInitializeRenderServer();
111    if (renderServerPort == MACH_PORT_NULL)
112        return false;
113
114    NSString *pluginHostAppPath = [[NSBundle bundleWithIdentifier:@"com.apple.WebKit"] pathForAuxiliaryExecutable:pluginHostAppName];
115    NSString *pluginHostAppExecutablePath = [[NSBundle bundleWithPath:pluginHostAppPath] executablePath];
116
117    RetainPtr<CFStringRef> localization(AdoptCF, WKCopyCFLocalizationPreferredName(NULL));
118
119    NSDictionary *launchProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
120                                      pluginHostAppExecutablePath, @"pluginHostPath",
121                                      [NSNumber numberWithInt:pluginArchitecture], @"cpuType",
122                                      localization.get(), @"localization",
123                                      nil];
124
125    NSData *data = [NSPropertyListSerialization dataFromPropertyList:launchProperties format:NSPropertyListBinaryFormat_v1_0 errorDescription:0];
126    ASSERT(data);
127
128    [launchProperties release];
129
130    kern_return_t kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
131
132    if (kr == MACH_SEND_INVALID_DEST) {
133        // The plug-in vendor port has gone away for some reason. Try to reinitialize it.
134        m_pluginVendorPort = MACH_PORT_NULL;
135        if (!initializeVendorPort())
136            return false;
137
138        // And spawn the plug-in host again.
139        kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
140    }
141
142    if (kr != KERN_SUCCESS) {
143        // FIXME: Check for invalid dest and try to re-spawn the plug-in agent.
144        LOG_ERROR("Failed to spawn plug-in host, error %x", kr);
145        return false;
146    }
147
148    NSString *visibleName = [NSString stringWithFormat:UI_STRING_INTERNAL("%@ (%@ Internet plug-in)",
149                                                                 "visible name of the plug-in host process. The first argument is the plug-in name "
150                                                                 "and the second argument is the application name."),
151                             [[(NSString*)pluginPath lastPathComponent] stringByDeletingPathExtension], [[NSProcessInfo processInfo] processName]];
152
153    NSDictionary *hostProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
154                                    visibleName, @"visibleName",
155                                    (NSString *)pluginPath, @"bundlePath",
156                                    nil];
157
158    data = [NSPropertyListSerialization dataFromPropertyList:hostProperties format:NSPropertyListBinaryFormat_v1_0 errorDescription:nil];
159    ASSERT(data);
160
161    [hostProperties release];
162
163    ProcessSerialNumber psn;
164    GetCurrentProcess(&psn);
165
166    kr = _WKPHCheckInWithPluginHost(pluginHostPort, (uint8_t*)[data bytes], [data length], clientPort, psn.highLongOfPSN, psn.lowLongOfPSN, renderServerPort,
167                                    &pluginHostPSN.highLongOfPSN, &pluginHostPSN.lowLongOfPSN);
168
169    if (kr != KERN_SUCCESS) {
170        mach_port_deallocate(mach_task_self(), pluginHostPort);
171        LOG_ERROR("Failed to check in with plug-in host, error %x", kr);
172
173        return false;
174    }
175
176    return true;
177}
178
179bool NetscapePluginHostManager::initializeVendorPort()
180{
181    ASSERT(m_pluginVendorPort == MACH_PORT_NULL);
182
183    // Get the plug-in agent port.
184    mach_port_t pluginAgentPort;
185    if (bootstrap_look_up(bootstrap_port, "com.apple.WebKit.PluginAgent", &pluginAgentPort) != KERN_SUCCESS) {
186        LOG_ERROR("Failed to look up the plug-in agent port");
187        return false;
188    }
189
190    NSData *appNameData = [[[NSProcessInfo processInfo] processName] dataUsingEncoding:NSUTF8StringEncoding];
191
192    // Tell the plug-in agent that we exist.
193    if (_WKPACheckInApplication(pluginAgentPort, (uint8_t*)[appNameData bytes], [appNameData length], &m_pluginVendorPort) != KERN_SUCCESS)
194        return false;
195
196    // FIXME: Should we add a notification for when the vendor port dies?
197
198    return true;
199}
200
201void NetscapePluginHostManager::pluginHostDied(NetscapePluginHostProxy* pluginHost)
202{
203    PluginHostMap::iterator end = m_pluginHosts.end();
204
205    // This has O(n) complexity but the number of active plug-in hosts is very small so it shouldn't matter.
206    for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
207        if (it->second == pluginHost) {
208            m_pluginHosts.remove(it);
209            return;
210        }
211    }
212}
213
214PassRefPtr<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)
215{
216    NetscapePluginHostProxy* hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
217    if (!hostProxy)
218        return 0;
219
220    RetainPtr<NSMutableDictionary> properties(AdoptNS, [[NSMutableDictionary alloc] init]);
221
222    if (mimeType)
223        [properties.get() setObject:mimeType forKey:@"mimeType"];
224
225    ASSERT_ARG(userAgent, userAgent);
226    [properties.get() setObject:userAgent forKey:@"userAgent"];
227
228    ASSERT_ARG(attributeKeys, attributeKeys);
229    [properties.get() setObject:attributeKeys forKey:@"attributeKeys"];
230
231    ASSERT_ARG(attributeValues, attributeValues);
232    [properties.get() setObject:attributeValues forKey:@"attributeValues"];
233
234    if (sourceURL)
235        [properties.get() setObject:[sourceURL absoluteString] forKey:@"sourceURL"];
236
237    [properties.get() setObject:[NSNumber numberWithBool:fullFrame] forKey:@"fullFrame"];
238    [properties.get() setObject:[NSNumber numberWithBool:isPrivateBrowsingEnabled] forKey:@"privateBrowsingEnabled"];
239    [properties.get() setObject:[NSNumber numberWithBool:isAcceleratedCompositingEnabled] forKey:@"acceleratedCompositingEnabled"];
240
241    NSData *data = [NSPropertyListSerialization dataFromPropertyList:properties.get() format:NSPropertyListBinaryFormat_v1_0 errorDescription:nil];
242    ASSERT(data);
243
244    RefPtr<NetscapePluginInstanceProxy> instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
245    uint32_t requestID = instance->nextRequestID();
246    kern_return_t kr = _WKPHInstantiatePlugin(hostProxy->port(), requestID, (uint8_t*)[data bytes], [data length], instance->pluginID());
247    if (kr == MACH_SEND_INVALID_DEST) {
248        // Invalidate the instance.
249        instance->invalidate();
250
251        // The plug-in host must have died, but we haven't received the death notification yet.
252        pluginHostDied(hostProxy);
253
254        // Try to spawn it again.
255        hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
256
257        // Create a new instance.
258        instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
259        requestID = instance->nextRequestID();
260        kr = _WKPHInstantiatePlugin(hostProxy->port(), requestID, (uint8_t*)[data bytes], [data length], instance->pluginID());
261    }
262
263    auto_ptr<NetscapePluginInstanceProxy::InstantiatePluginReply> reply = instance->waitForReply<NetscapePluginInstanceProxy::InstantiatePluginReply>(requestID);
264    if (!reply.get() || reply->m_resultCode != KERN_SUCCESS) {
265        instance->cleanup();
266        return 0;
267    }
268
269    instance->setRenderContextID(reply->m_renderContextID);
270    instance->setRendererType(reply->m_rendererType);
271
272    return instance.release();
273}
274
275void NetscapePluginHostManager::createPropertyListFile(const String& pluginPath, cpu_type_t pluginArchitecture)
276{
277    NSString *pluginHostAppPath = [[NSBundle bundleWithIdentifier:@"com.apple.WebKit"] pathForAuxiliaryExecutable:pluginHostAppName];
278    NSString *pluginHostAppExecutablePath = [[NSBundle bundleWithPath:pluginHostAppPath] executablePath];
279    NSString *bundlePath = pluginPath;
280
281    pid_t pid;
282    posix_spawnattr_t attr;
283    posix_spawnattr_init(&attr);
284
285    // Set the architecture.
286    size_t ocount = 0;
287    int cpuTypes[] = { pluginArchitecture };
288    posix_spawnattr_setbinpref_np(&attr, 1, cpuTypes, &ocount);
289
290    // Spawn the plug-in host and tell it to call the registration function.
291    const char* args[] = { [pluginHostAppExecutablePath fileSystemRepresentation], "-createPluginMIMETypesPreferences", [bundlePath fileSystemRepresentation], 0 };
292
293    int result = posix_spawn(&pid, args[0], 0, &attr, const_cast<char* const*>(args), 0);
294    posix_spawnattr_destroy(&attr);
295
296    if (!result && pid > 0) {
297        // Wait for the process to finish.
298        while (waitpid(pid, 0,  0) == -1) { }
299    }
300}
301
302void NetscapePluginHostManager::didCreateWindow()
303{
304    // See if any of our hosts are in full-screen mode.
305    PluginHostMap::iterator end = m_pluginHosts.end();
306    for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
307        NetscapePluginHostProxy* hostProxy = it->second;
308
309        if (!hostProxy->isMenuBarVisible()) {
310            // Make ourselves the front process.
311            ProcessSerialNumber psn;
312            GetCurrentProcess(&psn);
313            SetFrontProcess(&psn);
314            return;
315        }
316    }
317}
318
319} // namespace WebKit
320
321#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
322