703c5462c8ef22a37b1dd86048e3ba161ab5f5b2
[WebKit.git] / Source / WebKitLegacy / mac / Plugins / Hosted / NetscapePluginHostManager.mm
1 /*
2  * Copyright (C) 2008-2018 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 "WebNetscapePluginPackage.h"
34 #import <mach/mach_port.h>
35 #import <pal/spi/cf/CFLocaleSPI.h>
36 #import <pal/spi/cocoa/QuartzCoreSPI.h>
37 #import <pal/spi/cocoa/ServersSPI.h>
38 #import <spawn.h>
39 #import <wtf/Assertions.h>
40 #import <wtf/MachSendRight.h>
41 #import <wtf/NeverDestroyed.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/StdLibExtras.h>
44
45 extern "C" {
46 #import "WebKitPluginAgent.h"
47 #import "WebKitPluginHost.h"
48 }
49
50 using namespace WebCore;
51
52 namespace WebKit {
53
54 NetscapePluginHostManager& NetscapePluginHostManager::singleton()
55 {
56     static NeverDestroyed<NetscapePluginHostManager> pluginHostManager;
57     return pluginHostManager;
58 }
59
60 static NSString * const pluginHostAppName = @"WebKitPluginHost.app";
61
62 NetscapePluginHostManager::NetscapePluginHostManager()
63     : m_pluginVendorPort(MACH_PORT_NULL)
64 {
65 }
66  
67 NetscapePluginHostManager::~NetscapePluginHostManager()
68 {
69 }
70
71 NetscapePluginHostProxy* NetscapePluginHostManager::hostForPlugin(const WTF::String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier)
72 {
73     PluginHostMap::AddResult result = m_pluginHosts.add(pluginPath, nullptr);
74     
75     // The package was already in the map, just return it.
76     if (!result.isNewEntry)
77         return result.iterator->value;
78         
79     mach_port_t clientPort = MACH_PORT_NULL;
80     if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &clientPort) != KERN_SUCCESS) {
81         m_pluginHosts.remove(result.iterator);
82         return nullptr;
83     }
84     
85     mach_port_t pluginHostPort = MACH_PORT_NULL;
86     ProcessSerialNumber pluginHostPSN;
87     if (!spawnPluginHost(pluginPath, pluginArchitecture, clientPort, pluginHostPort, pluginHostPSN)) {
88         mach_port_destroy(mach_task_self(), clientPort);
89         m_pluginHosts.remove(result.iterator);
90         return nullptr;
91     }
92     
93     // Since Flash NPObjects add methods dynamically, we don't want to cache when a property/method doesn't exist
94     // on an object because it could be added later.
95     bool shouldCacheMissingPropertiesAndMethods = bundleIdentifier != "com.macromedia.Flash Player.plugin";
96     
97     NetscapePluginHostProxy* hostProxy = new NetscapePluginHostProxy(clientPort, pluginHostPort, pluginHostPSN, shouldCacheMissingPropertiesAndMethods);
98     
99     result.iterator->value = hostProxy;
100     
101     return hostProxy;
102 }
103
104 static NSString *preferredBundleLocalizationName()
105 {
106     // FIXME: Any use of this function to pass localizations to another
107     // process is likely not completely right, since it only considers
108     // one localization.
109     NSArray *preferredLocalizations = [[NSBundle mainBundle] preferredLocalizations];
110     if (!preferredLocalizations || ![preferredLocalizations count])
111         return @"en_US";
112
113     NSString *language = [preferredLocalizations objectAtIndex:0];
114
115     // FIXME: <rdar://problem/18083880> Replace use of Script Manager
116     // to canonicalize locales with a custom Web-specific table
117     LangCode languageCode;
118     RegionCode regionCode;
119
120     Boolean success = CFLocaleGetLanguageRegionEncodingForLocaleIdentifier((CFStringRef)language, &languageCode, &regionCode, nullptr, nullptr);
121     if (!success)
122         return @"en_US";
123
124     return adoptCF(CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes(0, languageCode, regionCode)).bridgingAutorelease();
125 }
126
127 bool NetscapePluginHostManager::spawnPluginHost(const String& pluginPath, cpu_type_t pluginArchitecture, mach_port_t clientPort, mach_port_t& pluginHostPort, ProcessSerialNumber& pluginHostPSN)
128 {
129     if (m_pluginVendorPort == MACH_PORT_NULL) {
130         if (!initializeVendorPort())
131             return false;
132     }
133
134     if (!CARenderServerStart())
135         return MACH_PORT_NULL;
136
137     mach_port_t renderServerPort = CARenderServerGetPort();
138     if (renderServerPort == MACH_PORT_NULL)
139         return false;
140
141     NSString *pluginHostAppPath = [[NSBundle bundleForClass:[WebNetscapePluginPackage class]] pathForAuxiliaryExecutable:pluginHostAppName];
142     NSString *pluginHostAppExecutablePath = [[NSBundle bundleWithPath:pluginHostAppPath] executablePath];
143
144     NSDictionary *launchProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
145                                       pluginHostAppExecutablePath, @"pluginHostPath",
146                                       [NSNumber numberWithInt:pluginArchitecture], @"cpuType",
147                                       preferredBundleLocalizationName(), @"localization",
148                                       nil];
149
150     NSData *data = [NSPropertyListSerialization dataWithPropertyList:launchProperties format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
151     ASSERT(data);
152
153     [launchProperties release];
154
155     kern_return_t kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
156
157     if (kr == MACH_SEND_INVALID_DEST) {
158         // The plug-in vendor port has gone away for some reason. Try to reinitialize it.
159         m_pluginVendorPort = MACH_PORT_NULL;
160         if (!initializeVendorPort())
161             return false;
162         
163         // And spawn the plug-in host again.
164         kr = _WKPASpawnPluginHost(m_pluginVendorPort, reinterpret_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], &pluginHostPort);
165     }
166
167     if (kr != KERN_SUCCESS) {
168         // FIXME: Check for invalid dest and try to re-spawn the plug-in agent.
169         LOG_ERROR("Failed to spawn plug-in host, error %x", kr);
170         return false;
171     }
172     
173     NSString *visibleName = [NSString stringWithFormat:UI_STRING_INTERNAL("%@ (%@ Internet plug-in)",
174                                                                  "visible name of the plug-in host process. The first argument is the plug-in name "
175                                                                  "and the second argument is the application name."),
176                              [[(NSString*)pluginPath lastPathComponent] stringByDeletingPathExtension], [[NSProcessInfo processInfo] processName]];
177     
178     NSDictionary *hostProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
179                                     visibleName, @"visibleName",
180                                     (NSString *)pluginPath, @"bundlePath",
181                                     nil];
182     
183     data = [NSPropertyListSerialization dataWithPropertyList:hostProperties format:NSPropertyListBinaryFormat_v1_0 options:0 error:NULL];
184     ASSERT(data);
185
186     [hostProperties release];
187
188     ProcessSerialNumber psn;
189
190 #pragma clang diagnostic push
191 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
192     GetCurrentProcess(&psn);
193 #pragma clang diagnostic pop
194
195     ASSERT(MACH_PORT_VALID(clientPort));
196     kr = _WKPHCheckInWithPluginHost(pluginHostPort, static_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], clientPort, psn.highLongOfPSN, psn.lowLongOfPSN, renderServerPort,
197                                     &pluginHostPSN.highLongOfPSN, &pluginHostPSN.lowLongOfPSN);
198     
199     if (kr != KERN_SUCCESS) {
200         LOG_ERROR("Failed to check in with plug-in host, error %x", kr);
201         deallocateSendRightSafely(pluginHostPort);
202
203         return false;
204     }
205
206     return true;
207 }
208
209 bool NetscapePluginHostManager::initializeVendorPort()
210 {
211     ASSERT(m_pluginVendorPort == MACH_PORT_NULL);
212
213     // Get the plug-in agent port.
214     mach_port_t pluginAgentPort = MACH_PORT_NULL;
215     if (bootstrap_look_up(bootstrap_port, "com.apple.WebKit.PluginAgent", &pluginAgentPort) != KERN_SUCCESS) {
216         LOG_ERROR("Failed to look up the plug-in agent port");
217         return false;
218     }
219     
220     NSData *appNameData = [[[NSProcessInfo processInfo] processName] dataUsingEncoding:NSUTF8StringEncoding];
221     
222     // Tell the plug-in agent that we exist.
223     if (_WKPACheckInApplication(pluginAgentPort, static_cast<uint8_t*>(const_cast<void*>([appNameData bytes])), [appNameData length], &m_pluginVendorPort) != KERN_SUCCESS)
224         return false;
225
226     // FIXME: Should we add a notification for when the vendor port dies?
227     
228     return true;
229 }
230
231 void NetscapePluginHostManager::pluginHostDied(NetscapePluginHostProxy* pluginHost)
232 {
233     PluginHostMap::iterator end = m_pluginHosts.end();
234
235     // This has O(n) complexity but the number of active plug-in hosts is very small so it shouldn't matter.
236     for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
237         if (it->value == pluginHost) {
238             m_pluginHosts.remove(it);
239             return;
240         }
241     }
242 }
243
244 RefPtr<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)
245 {
246     NetscapePluginHostProxy* hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
247     if (!hostProxy)
248         return nullptr;
249
250     RetainPtr<NSMutableDictionary> properties = adoptNS([[NSMutableDictionary alloc] init]);
251     
252     if (mimeType)
253         [properties.get() setObject:mimeType forKey:@"mimeType"];
254
255     ASSERT_ARG(userAgent, userAgent);
256     [properties.get() setObject:userAgent forKey:@"userAgent"];
257     
258     ASSERT_ARG(attributeKeys, attributeKeys);
259     [properties.get() setObject:attributeKeys forKey:@"attributeKeys"];
260     
261     ASSERT_ARG(attributeValues, attributeValues);
262     [properties.get() setObject:attributeValues forKey:@"attributeValues"];
263
264     if (sourceURL)
265         [properties.get() setObject:[sourceURL absoluteString] forKey:@"sourceURL"];
266     
267     [properties.get() setObject:[NSNumber numberWithBool:fullFrame] forKey:@"fullFrame"];
268     [properties.get() setObject:[NSNumber numberWithBool:isPrivateBrowsingEnabled] forKey:@"privateBrowsingEnabled"];
269     [properties.get() setObject:[NSNumber numberWithBool:isAcceleratedCompositingEnabled] forKey:@"acceleratedCompositingEnabled"];
270     [properties.get() setObject:[NSNumber numberWithBool:hostLayersInWindowServer] forKey:@"hostLayersInWindowServer"];
271
272     NSData *data = [NSPropertyListSerialization dataWithPropertyList:properties.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
273     ASSERT(data);
274     
275     RefPtr<NetscapePluginInstanceProxy> instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
276     uint32_t requestID = instance->nextRequestID();
277     kern_return_t kr = _WKPHInstantiatePlugin(hostProxy->port(), requestID, static_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], instance->pluginID());
278     if (kr == MACH_SEND_INVALID_DEST) {
279         // Invalidate the instance.
280         instance->invalidate();
281         
282         // The plug-in host must have died, but we haven't received the death notification yet.
283         pluginHostDied(hostProxy);
284
285         // Try to spawn it again.
286         hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
287         
288         // Create a new instance.
289         instance = NetscapePluginInstanceProxy::create(hostProxy, pluginView, fullFrame);
290         requestID = instance->nextRequestID();
291         _WKPHInstantiatePlugin(hostProxy->port(), requestID, static_cast<uint8_t*>(const_cast<void*>([data bytes])), [data length], instance->pluginID());
292     }
293
294     auto reply = instance->waitForReply<NetscapePluginInstanceProxy::InstantiatePluginReply>(requestID);
295     if (!reply || reply->m_resultCode != KERN_SUCCESS) {
296         instance->cleanup();
297         return nullptr;
298     }
299     
300     instance->setRenderContextID(reply->m_renderContextID);
301     instance->setRendererType(reply->m_rendererType);
302
303     return instance;
304 }
305
306 void NetscapePluginHostManager::createPropertyListFile(const String& pluginPath, cpu_type_t pluginArchitecture, const String& bundleIdentifier)
307 {
308     NetscapePluginHostProxy* hostProxy = hostForPlugin(pluginPath, pluginArchitecture, bundleIdentifier);
309     if (!hostProxy)
310         return;
311
312     _WKPHCreatePluginMIMETypesPreferences(hostProxy->port());
313 }
314     
315 void NetscapePluginHostManager::didCreateWindow()
316 {
317     // See if any of our hosts are in full-screen mode.
318     PluginHostMap::iterator end = m_pluginHosts.end();
319     for (PluginHostMap::iterator it = m_pluginHosts.begin(); it != end; ++it) {
320         NetscapePluginHostProxy* hostProxy = it->value;
321         
322         if (!hostProxy->isMenuBarVisible()) {
323             // Make ourselves the front process.
324             ProcessSerialNumber psn;
325 #pragma clang diagnostic push
326 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
327             GetCurrentProcess(&psn);
328             SetFrontProcess(&psn);
329 #pragma clang diagnostic pop
330             return;
331         }
332     }
333 }
334
335 } // namespace WebKit
336
337 #endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)