blob: 3032afa280fa927d877320eb81cbd9071025597e [file] [log] [blame]
/*
* Copyright (C) 2008-2019 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
#import "NetscapePluginInstanceProxy.h"
#import "HostedNetscapePluginStream.h"
#import "NetscapePluginHostProxy.h"
#import "ProxyInstance.h"
#import "ProxyRuntimeObject.h"
#import "WebDataSourceInternal.h"
#import "WebFrameInternal.h"
#import "WebHostedNetscapePluginView.h"
#import "WebKitNSStringExtras.h"
#import "WebNSDataExtras.h"
#import "WebNSURLExtras.h"
#import "WebPluginRequest.h"
#import "WebUIDelegate.h"
#import "WebUIDelegatePrivate.h"
#import "WebViewInternal.h"
#import <JavaScriptCore/CatchScope.h>
#import <JavaScriptCore/Completion.h>
#import <JavaScriptCore/Error.h>
#import <JavaScriptCore/JSGlobalObjectInlines.h>
#import <JavaScriptCore/JSLock.h>
#import <JavaScriptCore/PropertyNameArray.h>
#import <JavaScriptCore/SourceCode.h>
#import <JavaScriptCore/StrongInlines.h>
#import <WebCore/CookieJar.h>
#import <WebCore/Document.h>
#import <WebCore/DocumentLoader.h>
#import <WebCore/Frame.h>
#import <WebCore/FrameLoader.h>
#import <WebCore/FrameTree.h>
#import <WebCore/Page.h>
#import <WebCore/PlatformEventFactoryMac.h>
#import <WebCore/ProxyServer.h>
#import <WebCore/ScriptController.h>
#import <WebCore/SecurityOrigin.h>
#import <WebCore/UserGestureIndicator.h>
#import <WebCore/npruntime_impl.h>
#import <WebCore/runtime_object.h>
#import <mach/mach.h>
#import <utility>
#import <wtf/NeverDestroyed.h>
#import <wtf/RefCountedLeakCounter.h>
#import <wtf/URL.h>
#import <wtf/text/CString.h>
extern "C" {
#import "WebKitPluginClientServer.h"
#import "WebKitPluginHost.h"
}
using namespace JSC;
using namespace JSC::Bindings;
using namespace WebCore;
namespace WebKit {
class NetscapePluginInstanceProxy::PluginRequest : public RefCounted<NetscapePluginInstanceProxy::PluginRequest> {
public:
static Ref<PluginRequest> create(uint32_t requestID, NSURLRequest* request, NSString* frameName, bool allowPopups)
{
return adoptRef(*new PluginRequest(requestID, request, frameName, allowPopups));
}
uint32_t requestID() const { return m_requestID; }
NSURLRequest* request() const { return m_request.get(); }
NSString* frameName() const { return m_frameName.get(); }
bool allowPopups() const { return m_allowPopups; }
private:
PluginRequest(uint32_t requestID, NSURLRequest* request, NSString* frameName, bool allowPopups)
: m_requestID(requestID)
, m_request(request)
, m_frameName(frameName)
, m_allowPopups(allowPopups)
{
}
uint32_t m_requestID;
RetainPtr<NSURLRequest> m_request;
RetainPtr<NSString> m_frameName;
bool m_allowPopups;
};
NetscapePluginInstanceProxy::LocalObjectMap::LocalObjectMap()
: m_objectIDCounter(0)
{
}
NetscapePluginInstanceProxy::LocalObjectMap::~LocalObjectMap()
{
}
inline bool NetscapePluginInstanceProxy::LocalObjectMap::contains(uint32_t objectID) const
{
return m_idToJSObjectMap.contains(objectID);
}
inline JSC::JSObject* NetscapePluginInstanceProxy::LocalObjectMap::get(uint32_t objectID) const
{
if (objectID == HashTraits<uint32_t>::emptyValue() || HashTraits<uint32_t>::isDeletedValue(objectID))
return nullptr;
return m_idToJSObjectMap.get(objectID).get();
}
uint32_t NetscapePluginInstanceProxy::LocalObjectMap::idForObject(VM& vm, JSObject* object)
{
// This method creates objects with refcount of 1, but doesn't increase refcount when returning
// found objects. This extra count accounts for the main "reference" kept by plugin process.
// To avoid excessive IPC, plugin process doesn't send each NPObject release/retain call to
// Safari. It only sends one when the last reference is removed, and it can destroy the proxy
// NPObject.
// However, the browser may be sending the same object out to plug-in as a function call
// argument at the same time - neither side can know what the other one is doing. So,
// is to make PCForgetBrowserObject call return a boolean result, making it possible for
// the browser to make plugin host keep the proxy with zero refcount for a little longer.
uint32_t objectID = 0;
HashMap<JSC::JSObject*, std::pair<uint32_t, uint32_t>>::iterator iter = m_jsObjectToIDMap.find(object);
if (iter != m_jsObjectToIDMap.end())
return iter->value.first;
do {
objectID = ++m_objectIDCounter;
} while (!m_objectIDCounter || m_objectIDCounter == static_cast<uint32_t>(-1) || m_idToJSObjectMap.contains(objectID));
m_idToJSObjectMap.set(objectID, Strong<JSObject>(vm, object));
m_jsObjectToIDMap.set(object, std::make_pair(objectID, 1));
return objectID;
}
void NetscapePluginInstanceProxy::LocalObjectMap::retain(JSC::JSObject* object)
{
HashMap<JSC::JSObject*, std::pair<uint32_t, uint32_t>>::iterator iter = m_jsObjectToIDMap.find(object);
ASSERT(iter != m_jsObjectToIDMap.end());
iter->value.second = iter->value.second + 1;
}
void NetscapePluginInstanceProxy::LocalObjectMap::release(JSC::JSObject* object)
{
HashMap<JSC::JSObject*, std::pair<uint32_t, uint32_t>>::iterator iter = m_jsObjectToIDMap.find(object);
ASSERT(iter != m_jsObjectToIDMap.end());
ASSERT(iter->value.second > 0);
iter->value.second = iter->value.second - 1;
if (!iter->value.second) {
m_idToJSObjectMap.remove(iter->value.first);
m_jsObjectToIDMap.remove(iter);
}
}
void NetscapePluginInstanceProxy::LocalObjectMap::clear()
{
m_idToJSObjectMap.clear();
m_jsObjectToIDMap.clear();
}
bool NetscapePluginInstanceProxy::LocalObjectMap::forget(uint32_t objectID)
{
if (objectID == HashTraits<uint32_t>::emptyValue() || HashTraits<uint32_t>::isDeletedValue(objectID)) {
LOG_ERROR("NetscapePluginInstanceProxy::LocalObjectMap::forget: local object id %u is not valid.", objectID);
return true;
}
HashMap<uint32_t, JSC::Strong<JSC::JSObject>>::iterator iter = m_idToJSObjectMap.find(objectID);
if (iter == m_idToJSObjectMap.end()) {
LOG_ERROR("NetscapePluginInstanceProxy::LocalObjectMap::forget: local object %u doesn't exist.", objectID);
return true;
}
HashMap<JSC::JSObject*, std::pair<uint32_t, uint32_t>>::iterator rIter = m_jsObjectToIDMap.find(iter->value.get());
// If the object is being sent to plug-in right now, then it's not the time to forget.
if (rIter->value.second != 1)
return false;
m_jsObjectToIDMap.remove(rIter);
m_idToJSObjectMap.remove(iter);
return true;
}
static uint32_t pluginIDCounter;
bool NetscapePluginInstanceProxy::m_inDestroy;
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, netscapePluginInstanceProxyCounter, ("NetscapePluginInstanceProxy"));
NetscapePluginInstanceProxy::NetscapePluginInstanceProxy(NetscapePluginHostProxy* pluginHostProxy, WebHostedNetscapePluginView *pluginView, bool fullFramePlugin)
: m_pluginHostProxy(pluginHostProxy)
, m_pluginView(pluginView)
, m_requestTimer(*this, &NetscapePluginInstanceProxy::requestTimerFired)
, m_currentURLRequestID(0)
, m_renderContextID(0)
, m_rendererType(UseSoftwareRenderer)
, m_waitingForReply(false)
, m_pluginFunctionCallDepth(0)
, m_shouldStopSoon(false)
, m_currentRequestID(0)
, m_pluginIsWaitingForDraw(false)
{
ASSERT(m_pluginView);
if (fullFramePlugin) {
// For full frame plug-ins, the first requestID will always be the one for the already
// open stream.
++m_currentURLRequestID;
}
// Assign a plug-in ID.
do {
m_pluginID = ++pluginIDCounter;
} while (pluginHostProxy->pluginInstance(m_pluginID) || !m_pluginID);
#ifndef NDEBUG
netscapePluginInstanceProxyCounter.increment();
#endif
}
Ref<NetscapePluginInstanceProxy> NetscapePluginInstanceProxy::create(NetscapePluginHostProxy* pluginHostProxy, WebHostedNetscapePluginView *pluginView, bool fullFramePlugin)
{
auto proxy = adoptRef(*new NetscapePluginInstanceProxy(pluginHostProxy, pluginView, fullFramePlugin));
pluginHostProxy->addPluginInstance(proxy.ptr());
return proxy;
}
NetscapePluginInstanceProxy::~NetscapePluginInstanceProxy()
{
ASSERT(!m_pluginHostProxy);
m_pluginID = 0;
#ifndef NDEBUG
netscapePluginInstanceProxyCounter.decrement();
#endif
}
void NetscapePluginInstanceProxy::resize(NSRect size, NSRect clipRect)
{
uint32_t requestID = 0;
requestID = nextRequestID();
_WKPHResizePluginInstance(m_pluginHostProxy->port(), m_pluginID, requestID,
size.origin.x, size.origin.y, size.size.width, size.size.height,
clipRect.origin.x, clipRect.origin.y, clipRect.size.width, clipRect.size.height);
waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
}
void NetscapePluginInstanceProxy::setShouldHostLayersInWindowServer(bool shouldHostLayersInWindowServer)
{
_WKPHPluginShouldHostLayersInWindowServerChanged(m_pluginHostProxy->port(), m_pluginID, shouldHostLayersInWindowServer);
}
void NetscapePluginInstanceProxy::layerHostingModeChanged(bool hostsLayersInWindowServer, uint32_t renderContextID)
{
setRenderContextID(renderContextID);
[m_pluginView setHostsLayersInWindowServer:hostsLayersInWindowServer];
}
void NetscapePluginInstanceProxy::stopAllStreams()
{
for (auto& stream : copyToVector(m_streams.values()))
stream->stop();
}
void NetscapePluginInstanceProxy::cleanup()
{
stopAllStreams();
m_requestTimer.stop();
// Clear the object map, this will cause any outstanding JS objects that the plug-in had a reference to
// to go away when the next garbage collection takes place.
m_localObjects.clear();
if (Frame* frame = core([m_pluginView webFrame]))
frame->script().cleanupScriptObjectsForPlugin((__bridge void*)m_pluginView);
ProxyInstanceSet instances;
instances.swap(m_instances);
// Invalidate all proxy instances.
ProxyInstanceSet::const_iterator end = instances.end();
for (ProxyInstanceSet::const_iterator it = instances.begin(); it != end; ++it)
(*it)->invalidate();
m_pluginView = nil;
m_manualStream = nullptr;
}
void NetscapePluginInstanceProxy::invalidate()
{
// If the plug-in host has died, the proxy will be null.
if (!m_pluginHostProxy)
return;
m_pluginHostProxy->removePluginInstance(this);
m_pluginHostProxy = nullptr;
}
void NetscapePluginInstanceProxy::destroy()
{
uint32_t requestID = nextRequestID();
ASSERT(!m_inDestroy);
m_inDestroy = true;
FrameLoadMap::iterator end = m_pendingFrameLoads.end();
for (FrameLoadMap::iterator it = m_pendingFrameLoads.begin(); it != end; ++it)
[(it->key) _setInternalLoadDelegate:nil];
_WKPHDestroyPluginInstance(m_pluginHostProxy->port(), m_pluginID, requestID);
// If the plug-in host crashes while we're waiting for a reply, the last reference to the instance proxy
// will go away. Prevent this by protecting it here.
Ref<NetscapePluginInstanceProxy> protect(*this);
// We don't care about the reply here - we just want to block until the plug-in instance has been torn down.
waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
m_inDestroy = false;
cleanup();
invalidate();
}
void NetscapePluginInstanceProxy::setManualStream(Ref<HostedNetscapePluginStream>&& manualStream)
{
ASSERT(!m_manualStream);
m_manualStream = WTFMove(manualStream);
}
bool NetscapePluginInstanceProxy::cancelStreamLoad(uint32_t streamID, NPReason reason)
{
HostedNetscapePluginStream* stream;
if (m_manualStream && streamID == 1)
stream = m_manualStream.get();
else
stream = m_streams.get(streamID);
if (!stream)
return false;
stream->cancelLoad(reason);
return true;
}
void NetscapePluginInstanceProxy::disconnectStream(HostedNetscapePluginStream* stream)
{
if (stream == m_manualStream) {
m_manualStream = nullptr;
return;
}
ASSERT(m_streams.get(stream->streamID()) == stream);
m_streams.remove(stream->streamID());
}
void NetscapePluginInstanceProxy::pluginHostDied()
{
m_pluginHostProxy = nullptr;
[m_pluginView pluginHostDied];
cleanup();
}
void NetscapePluginInstanceProxy::focusChanged(bool hasFocus)
{
_WKPHPluginInstanceFocusChanged(m_pluginHostProxy->port(), m_pluginID, hasFocus);
}
void NetscapePluginInstanceProxy::windowFocusChanged(bool hasFocus)
{
_WKPHPluginInstanceWindowFocusChanged(m_pluginHostProxy->port(), m_pluginID, hasFocus);
}
void NetscapePluginInstanceProxy::windowFrameChanged(NSRect frame)
{
_WKPHPluginInstanceWindowFrameChanged(m_pluginHostProxy->port(), m_pluginID, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height,
NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]));
}
void NetscapePluginInstanceProxy::startTimers(bool throttleTimers)
{
_WKPHPluginInstanceStartTimers(m_pluginHostProxy->port(), m_pluginID, throttleTimers);
}
void NetscapePluginInstanceProxy::mouseEvent(NSView *pluginView, NSEvent *event, NPCocoaEventType type)
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
NSPoint screenPoint = [[event window] convertBaseToScreen:[event locationInWindow]];
ALLOW_DEPRECATED_DECLARATIONS_END
NSPoint pluginPoint = [pluginView convertPoint:[event locationInWindow] fromView:nil];
int clickCount;
if (type == NPCocoaEventMouseEntered || type == NPCocoaEventMouseExited)
clickCount = 0;
else
clickCount = [event clickCount];
_WKPHPluginInstanceMouseEvent(m_pluginHostProxy->port(), m_pluginID,
[event timestamp],
type, [event modifierFlags],
pluginPoint.x, pluginPoint.y,
screenPoint.x, screenPoint.y,
NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]),
[event buttonNumber], clickCount,
[event deltaX], [event deltaY], [event deltaZ]);
}
void NetscapePluginInstanceProxy::keyEvent(NSView *pluginView, NSEvent *event, NPCocoaEventType type)
{
NSData *charactersData = [[event characters] dataUsingEncoding:NSUTF8StringEncoding];
NSData *charactersIgnoringModifiersData = [[event charactersIgnoringModifiers] dataUsingEncoding:NSUTF8StringEncoding];
_WKPHPluginInstanceKeyboardEvent(m_pluginHostProxy->port(), m_pluginID,
[event timestamp],
type, [event modifierFlags],
const_cast<char*>(reinterpret_cast<const char*>([charactersData bytes])), [charactersData length],
const_cast<char*>(reinterpret_cast<const char*>([charactersIgnoringModifiersData bytes])), [charactersIgnoringModifiersData length],
[event isARepeat], [event keyCode], keyCharForEvent(event));
}
void NetscapePluginInstanceProxy::syntheticKeyDownWithCommandModifier(int keyCode, char character)
{
NSData *charactersData = [NSData dataWithBytes:&character length:1];
_WKPHPluginInstanceKeyboardEvent(m_pluginHostProxy->port(), m_pluginID,
[NSDate timeIntervalSinceReferenceDate],
NPCocoaEventKeyDown, NSEventModifierFlagCommand,
const_cast<char*>(reinterpret_cast<const char*>([charactersData bytes])), [charactersData length],
const_cast<char*>(reinterpret_cast<const char*>([charactersData bytes])), [charactersData length],
false, keyCode, character);
}
void NetscapePluginInstanceProxy::flagsChanged(NSEvent *event)
{
_WKPHPluginInstanceKeyboardEvent(m_pluginHostProxy->port(), m_pluginID,
[event timestamp], NPCocoaEventFlagsChanged,
[event modifierFlags], 0, 0, 0, 0, false, [event keyCode], 0);
}
void NetscapePluginInstanceProxy::insertText(NSString *text)
{
NSData *textData = [text dataUsingEncoding:NSUTF8StringEncoding];
_WKPHPluginInstanceInsertText(m_pluginHostProxy->port(), m_pluginID,
const_cast<char*>(reinterpret_cast<const char*>([textData bytes])), [textData length]);
}
bool NetscapePluginInstanceProxy::wheelEvent(NSView *pluginView, NSEvent *event)
{
NSPoint pluginPoint = [pluginView convertPoint:[event locationInWindow] fromView:nil];
uint32_t requestID = nextRequestID();
_WKPHPluginInstanceWheelEvent(m_pluginHostProxy->port(), m_pluginID, requestID,
[event timestamp], [event modifierFlags],
pluginPoint.x, pluginPoint.y, [event buttonNumber],
[event deltaX], [event deltaY], [event deltaZ]);
auto reply = waitForReply<NetscapePluginInstanceProxy::BooleanReply>(requestID);
return reply && reply->m_result;
}
void NetscapePluginInstanceProxy::print(CGContextRef context, unsigned width, unsigned height)
{
uint32_t requestID = nextRequestID();
_WKPHPluginInstancePrint(m_pluginHostProxy->port(), m_pluginID, requestID, width, height);
auto reply = waitForReply<NetscapePluginInstanceProxy::BooleanAndDataReply>(requestID);
if (!reply || !reply->m_returnValue)
return;
RetainPtr<CGDataProvider> dataProvider = adoptCF(CGDataProviderCreateWithCFData(reply->m_result.get()));
RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
RetainPtr<CGImageRef> image = adoptCF(CGImageCreate(width, height, 8, 32, width * 4, colorSpace.get(), kCGImageAlphaFirst, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
// Flip the context and draw the image.
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image.get());
CGContextRestoreGState(context);
}
void NetscapePluginInstanceProxy::snapshot(CGContextRef context, unsigned width, unsigned height)
{
uint32_t requestID = nextRequestID();
_WKPHPluginInstanceSnapshot(m_pluginHostProxy->port(), m_pluginID, requestID, width, height);
auto reply = waitForReply<NetscapePluginInstanceProxy::BooleanAndDataReply>(requestID);
if (!reply || !reply->m_returnValue)
return;
RetainPtr<CGDataProvider> dataProvider = adoptCF(CGDataProviderCreateWithCFData(reply->m_result.get()));
RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
RetainPtr<CGImageRef> image = adoptCF(CGImageCreate(width, height, 8, 32, width * 4, colorSpace.get(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image.get());
}
void NetscapePluginInstanceProxy::stopTimers()
{
_WKPHPluginInstanceStopTimers(m_pluginHostProxy->port(), m_pluginID);
}
void NetscapePluginInstanceProxy::status(const char* message)
{
RetainPtr<CFStringRef> status = adoptCF(CFStringCreateWithCString(0, message ? message : "", kCFStringEncodingUTF8));
if (!status)
return;
WebView *wv = [m_pluginView webView];
[[wv _UIDelegateForwarder] webView:wv setStatusText:(__bridge NSString *)status.get()];
}
NPError NetscapePluginInstanceProxy::loadURL(const char* url, const char* target, const char* postData, uint32_t postLen, LoadURLFlags flags, uint32_t& streamID)
{
if (!url)
return NPERR_INVALID_PARAM;
NSMutableURLRequest *request = [m_pluginView requestWithURLCString:url];
if (flags & IsPost) {
NSData *httpBody = nil;
if (flags & PostDataIsFile) {
// If we're posting a file, buf is either a file URL or a path to the file.
if (!postData)
return NPERR_INVALID_PARAM;
RetainPtr<CFStringRef> bufString = adoptCF(CFStringCreateWithCString(kCFAllocatorDefault, postData, kCFStringEncodingWindowsLatin1));
if (!bufString)
return NPERR_INVALID_PARAM;
NSURL *fileURL = [NSURL _web_URLWithDataAsString:(__bridge NSString *)bufString.get()];
NSString *path;
if ([fileURL isFileURL])
path = [fileURL path];
else
path = (__bridge NSString *)bufString.get();
httpBody = [NSData dataWithContentsOfFile:path];
if (!httpBody)
return NPERR_FILE_NOT_FOUND;
} else
httpBody = [NSData dataWithBytes:postData length:postLen];
if (![httpBody length])
return NPERR_INVALID_PARAM;
[request setHTTPMethod:@"POST"];
if (flags & AllowHeadersInPostData) {
if ([httpBody _web_startsWithBlankLine])
httpBody = [httpBody subdataWithRange:NSMakeRange(1, [httpBody length] - 1)];
else {
NSInteger location = [httpBody _web_locationAfterFirstBlankLine];
if (location != NSNotFound) {
// If the blank line is somewhere in the middle of postData, everything before is the header.
NSData *headerData = [httpBody subdataWithRange:NSMakeRange(0, location)];
NSMutableDictionary *header = [headerData _webkit_parseRFC822HeaderFields];
unsigned dataLength = [httpBody length] - location;
// Sometimes plugins like to set Content-Length themselves when they post,
// but CFNetwork does not like that. So we will remove the header
// and instead truncate the data to the requested length.
NSString *contentLength = [header objectForKey:@"Content-Length"];
if (contentLength)
dataLength = std::min(static_cast<unsigned>([contentLength intValue]), dataLength);
[header removeObjectForKey:@"Content-Length"];
if ([header count] > 0)
[request setAllHTTPHeaderFields:header];
// Everything after the blank line is the actual content of the POST.
httpBody = [httpBody subdataWithRange:NSMakeRange(location, dataLength)];
}
}
}
if (![httpBody length])
return NPERR_INVALID_PARAM;
// Plug-ins expect to receive uncached data when doing a POST (3347134).
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setHTTPBody:httpBody];
}
return loadRequest(request, target, flags & AllowPopups, streamID);
}
void NetscapePluginInstanceProxy::performRequest(PluginRequest* pluginRequest)
{
// Loading the request can cause the instance proxy to go away, so protect it.
Ref<NetscapePluginInstanceProxy> protect(*this);
ASSERT(m_pluginView);
NSURLRequest *request = pluginRequest->request();
NSString *frameName = pluginRequest->frameName();
WebFrame *frame = nil;
NSURL *URL = [request URL];
NSString *JSString = [URL _webkit_scriptIfJavaScriptURL];
ASSERT(frameName || JSString);
if (frameName) {
// FIXME - need to get rid of this window creation which
// bypasses normal targeted link handling
frame = kit(core([m_pluginView webFrame])->loader().findFrameForNavigation(frameName));
if (!frame) {
WebView *currentWebView = [m_pluginView webView];
NSDictionary *features = [[NSDictionary alloc] init];
WebView *newWebView = [[currentWebView _UIDelegateForwarder] webView:currentWebView
createWebViewWithRequest:nil
windowFeatures:features];
[features release];
if (!newWebView) {
_WKPHLoadURLNotify(m_pluginHostProxy->port(), m_pluginID, pluginRequest->requestID(), NPERR_GENERIC_ERROR);
return;
}
frame = [newWebView mainFrame];
core(frame)->tree().setName(frameName);
[[newWebView _UIDelegateForwarder] webViewShow:newWebView];
}
}
if (JSString) {
ASSERT(!frame || [m_pluginView webFrame] == frame);
evaluateJavaScript(pluginRequest);
} else {
[frame loadRequest:request];
// Check if another plug-in view or even this view is waiting for the frame to load.
// If it is, tell it that the load was cancelled because it will be anyway.
WebHostedNetscapePluginView *view = [frame _internalLoadDelegate];
if (view != nil) {
ASSERT([view isKindOfClass:[WebHostedNetscapePluginView class]]);
[view webFrame:frame didFinishLoadWithReason:NPRES_USER_BREAK];
}
m_pendingFrameLoads.set(frame, pluginRequest);
[frame _setInternalLoadDelegate:m_pluginView];
}
}
void NetscapePluginInstanceProxy::webFrameDidFinishLoadWithReason(WebFrame* webFrame, NPReason reason)
{
FrameLoadMap::iterator it = m_pendingFrameLoads.find(webFrame);
ASSERT(it != m_pendingFrameLoads.end());
PluginRequest* pluginRequest = it->value.get();
_WKPHLoadURLNotify(m_pluginHostProxy->port(), m_pluginID, pluginRequest->requestID(), reason);
m_pendingFrameLoads.remove(it);
[webFrame _setInternalLoadDelegate:nil];
}
void NetscapePluginInstanceProxy::evaluateJavaScript(PluginRequest* pluginRequest)
{
NSURL *URL = [pluginRequest->request() URL];
NSString *JSString = [URL _webkit_scriptIfJavaScriptURL];
ASSERT(JSString);
Ref<NetscapePluginInstanceProxy> protect(*this); // Executing arbitrary JavaScript can destroy the proxy.
NSString *result = [[m_pluginView webFrame] _stringByEvaluatingJavaScriptFromString:JSString forceUserGesture:pluginRequest->allowPopups()];
// Don't continue if stringByEvaluatingJavaScriptFromString caused the plug-in to stop.
if (!m_pluginHostProxy)
return;
if (pluginRequest->frameName() != nil)
return;
if ([result length] > 0) {
// Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does.
NSData *JSData = [result dataUsingEncoding:NSUTF8StringEncoding];
auto stream = HostedNetscapePluginStream::create(this, pluginRequest->requestID(), pluginRequest->request());
m_streams.add(stream->streamID(), stream.copyRef());
RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:URL
MIMEType:@"text/plain"
expectedContentLength:[JSData length]
textEncodingName:nil]);
stream->startStreamWithResponse(response.get());
stream->didReceiveData(0, static_cast<const char*>([JSData bytes]), [JSData length]);
stream->didFinishLoading(0);
}
}
void NetscapePluginInstanceProxy::requestTimerFired()
{
ASSERT(!m_pluginRequests.isEmpty());
ASSERT(m_pluginView);
RefPtr<PluginRequest> request = m_pluginRequests.first();
m_pluginRequests.removeFirst();
if (!m_pluginRequests.isEmpty())
m_requestTimer.startOneShot(0_s);
performRequest(request.get());
}
NPError NetscapePluginInstanceProxy::loadRequest(NSURLRequest *request, const char* cTarget, bool allowPopups, uint32_t& requestID)
{
NSURL *URL = [request URL];
if (!URL)
return NPERR_INVALID_URL;
// Don't allow requests to be loaded when the document loader is stopping all loaders.
DocumentLoader* documentLoader = [[m_pluginView dataSource] _documentLoader];
if (!documentLoader || documentLoader->isStopping())
return NPERR_GENERIC_ERROR;
NSString *target = nil;
if (cTarget) {
// Find the frame given the target string.
target = [NSString stringWithCString:cTarget encoding:NSISOLatin1StringEncoding];
}
WebFrame *frame = [m_pluginView webFrame];
// don't let a plugin start any loads if it is no longer part of a document that is being
// displayed unless the loads are in the same frame as the plugin.
if (documentLoader != core([m_pluginView webFrame])->loader().activeDocumentLoader() &&
(!cTarget || [frame findFrameNamed:target] != frame)) {
return NPERR_GENERIC_ERROR;
}
NSString *JSString = [URL _webkit_scriptIfJavaScriptURL];
if (JSString != nil) {
if (![[[m_pluginView webView] preferences] isJavaScriptEnabled]) {
// Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does.
return NPERR_GENERIC_ERROR;
}
} else {
if (!core([m_pluginView webFrame])->document()->securityOrigin().canDisplay(URL))
return NPERR_GENERIC_ERROR;
}
// FIXME: Handle wraparound
requestID = ++m_currentURLRequestID;
if (cTarget || JSString) {
// Make when targeting a frame or evaluating a JS string, perform the request after a delay because we don't
// want to potentially kill the plug-in inside of its URL request.
if (JSString && target && [frame findFrameNamed:target] != frame) {
// For security reasons, only allow JS requests to be made on the frame that contains the plug-in.
return NPERR_INVALID_PARAM;
}
auto pluginRequest = PluginRequest::create(requestID, request, target, allowPopups);
m_pluginRequests.append(WTFMove(pluginRequest));
m_requestTimer.startOneShot(0_s);
} else {
auto stream = HostedNetscapePluginStream::create(this, requestID, request);
ASSERT(!m_streams.contains(requestID));
m_streams.add(requestID, stream.copyRef());
stream->start();
}
return NPERR_NO_ERROR;
}
std::unique_ptr<NetscapePluginInstanceProxy::Reply> NetscapePluginInstanceProxy::processRequestsAndWaitForReply(uint32_t requestID)
{
ASSERT(m_pluginHostProxy);
std::unique_ptr<Reply> reply;
while (!(reply = m_replies.take(requestID))) {
if (!m_pluginHostProxy->processRequests())
return nullptr;
// The host proxy can be destroyed while executing a nested processRequests() call, in which case it's normal
// to get a success result, but be unable to keep looping.
if (!m_pluginHostProxy)
return nullptr;
}
return reply;
}
// NPRuntime support
bool NetscapePluginInstanceProxy::getWindowNPObject(uint32_t& objectID)
{
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
if (!frame->script().canExecuteScripts(NotAboutToExecuteScript))
objectID = 0;
else
objectID = m_localObjects.idForObject(pluginWorld().vm(), frame->windowProxy().jsWindowProxy(pluginWorld())->window());
return true;
}
bool NetscapePluginInstanceProxy::getPluginElementNPObject(uint32_t& objectID)
{
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
if (JSObject* object = frame->script().jsObjectForPluginElement([m_pluginView element]))
objectID = m_localObjects.idForObject(pluginWorld().vm(), object);
else
objectID = 0;
return true;
}
bool NetscapePluginInstanceProxy::forgetBrowserObjectID(uint32_t objectID)
{
return m_localObjects.forget(objectID);
}
bool NetscapePluginInstanceProxy::evaluate(uint32_t objectID, const String& script, data_t& resultData, mach_msg_type_number_t& resultLength, bool allowPopups)
{
resultData = nullptr;
resultLength = 0;
if (m_inDestroy)
return false;
if (!m_localObjects.contains(objectID)) {
LOG_ERROR("NetscapePluginInstanceProxy::evaluate: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
Strong<JSGlobalObject> globalObject(vm, frame->script().globalObject(pluginWorld()));
UserGestureIndicator gestureIndicator(allowPopups ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt);
JSValue result = JSC::evaluate(globalObject.get(), JSC::makeSource(script, { }));
marshalValue(globalObject.get(), result, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::invoke(uint32_t objectID, const Identifier& methodName, data_t argumentsData, mach_msg_type_number_t argumentsLength, data_t& resultData, mach_msg_type_number_t& resultLength)
{
resultData = nullptr;
resultLength = 0;
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::invoke: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue function = object->get(lexicalGlobalObject, methodName);
CallData callData;
CallType callType = getCallData(vm, function, callData);
if (callType == CallType::None)
return false;
MarkedArgumentBuffer argList;
demarshalValues(lexicalGlobalObject, argumentsData, argumentsLength, argList);
RELEASE_ASSERT(!argList.hasOverflowed());
JSValue value = call(lexicalGlobalObject, function, callType, callData, object, argList);
marshalValue(lexicalGlobalObject, value, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::invokeDefault(uint32_t objectID, data_t argumentsData, mach_msg_type_number_t argumentsLength, data_t& resultData, mach_msg_type_number_t& resultLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::invokeDefault: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
CallData callData;
CallType callType = object->methodTable(vm)->getCallData(object, callData);
if (callType == CallType::None)
return false;
MarkedArgumentBuffer argList;
demarshalValues(lexicalGlobalObject, argumentsData, argumentsLength, argList);
RELEASE_ASSERT(!argList.hasOverflowed());
JSValue value = call(lexicalGlobalObject, object, callType, callData, object, argList);
marshalValue(lexicalGlobalObject, value, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::construct(uint32_t objectID, data_t argumentsData, mach_msg_type_number_t argumentsLength, data_t& resultData, mach_msg_type_number_t& resultLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::construct: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
ConstructData constructData;
ConstructType constructType = object->methodTable(vm)->getConstructData(object, constructData);
if (constructType == ConstructType::None)
return false;
MarkedArgumentBuffer argList;
demarshalValues(lexicalGlobalObject, argumentsData, argumentsLength, argList);
RELEASE_ASSERT(!argList.hasOverflowed());
JSValue value = JSC::construct(lexicalGlobalObject, object, constructType, constructData, argList);
marshalValue(lexicalGlobalObject, value, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::getProperty(uint32_t objectID, const Identifier& propertyName, data_t& resultData, mach_msg_type_number_t& resultLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::getProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue value = object->get(lexicalGlobalObject, propertyName);
marshalValue(lexicalGlobalObject, value, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::getProperty(uint32_t objectID, unsigned propertyName, data_t& resultData, mach_msg_type_number_t& resultLength)
{
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::getProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue value = object->get(lexicalGlobalObject, propertyName);
marshalValue(lexicalGlobalObject, value, resultData, resultLength);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::setProperty(uint32_t objectID, const Identifier& propertyName, data_t valueData, mach_msg_type_number_t valueLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::setProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue value = demarshalValue(lexicalGlobalObject, valueData, valueLength);
PutPropertySlot slot(object);
object->methodTable(vm)->put(object, lexicalGlobalObject, propertyName, value, slot);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::setProperty(uint32_t objectID, unsigned propertyName, data_t valueData, mach_msg_type_number_t valueLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::setProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue value = demarshalValue(lexicalGlobalObject, valueData, valueLength);
object->methodTable(vm)->putByIndex(object, lexicalGlobalObject, propertyName, value, false);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::removeProperty(uint32_t objectID, const Identifier& propertyName)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::removeProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
if (!object->hasProperty(lexicalGlobalObject, propertyName)) {
scope.clearException();
return false;
}
object->methodTable(vm)->deleteProperty(object, lexicalGlobalObject, propertyName);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::removeProperty(uint32_t objectID, unsigned propertyName)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::removeProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
if (!object->hasProperty(lexicalGlobalObject, propertyName)) {
scope.clearException();
return false;
}
object->methodTable(vm)->deletePropertyByIndex(object, lexicalGlobalObject, propertyName);
scope.clearException();
return true;
}
bool NetscapePluginInstanceProxy::hasProperty(uint32_t objectID, const Identifier& propertyName)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::hasProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
bool result = object->hasProperty(lexicalGlobalObject, propertyName);
scope.clearException();
return result;
}
bool NetscapePluginInstanceProxy::hasProperty(uint32_t objectID, unsigned propertyName)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::hasProperty: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
bool result = object->hasProperty(lexicalGlobalObject, propertyName);
scope.clearException();
return result;
}
bool NetscapePluginInstanceProxy::hasMethod(uint32_t objectID, const Identifier& methodName)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::hasMethod: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
JSValue func = object->get(lexicalGlobalObject, methodName);
scope.clearException();
return !func.isUndefined();
}
bool NetscapePluginInstanceProxy::enumerate(uint32_t objectID, data_t& resultData, mach_msg_type_number_t& resultLength)
{
if (m_inDestroy)
return false;
JSObject* object = m_localObjects.get(objectID);
if (!object) {
LOG_ERROR("NetscapePluginInstanceProxy::enumerate: local object %u doesn't exist.", objectID);
return false;
}
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
VM& vm = pluginWorld().vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = frame->script().globalObject(pluginWorld());
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
object->methodTable(vm)->getPropertyNames(object, lexicalGlobalObject, propertyNames, EnumerationMode());
RetainPtr<NSMutableArray> array = adoptNS([[NSMutableArray alloc] init]);
for (unsigned i = 0; i < propertyNames.size(); i++) {
uint64_t methodName = reinterpret_cast<uint64_t>(_NPN_GetStringIdentifier(propertyNames[i].string().utf8().data()));
[array.get() addObject:[NSNumber numberWithLongLong:methodName]];
}
NSData *data = [NSPropertyListSerialization dataWithPropertyList:array.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
ASSERT(data);
resultLength = [data length];
mig_allocate(reinterpret_cast<vm_address_t*>(&resultData), resultLength);
memcpy(resultData, [data bytes], resultLength);
scope.clearException();
return true;
}
static bool getObjectID(NetscapePluginInstanceProxy* pluginInstanceProxy, JSObject* object, uint64_t& objectID)
{
JSC::VM& vm = object->vm();
if (object->classInfo(vm) != ProxyRuntimeObject::info())
return false;
ProxyRuntimeObject* runtimeObject = static_cast<ProxyRuntimeObject*>(object);
ProxyInstance* instance = runtimeObject->getInternalProxyInstance();
if (!instance)
return false;
if (instance->instanceProxy() != pluginInstanceProxy)
return false;
objectID = instance->objectID();
return true;
}
void NetscapePluginInstanceProxy::addValueToArray(NSMutableArray *array, JSGlobalObject* lexicalGlobalObject, JSValue value)
{
VM& vm = lexicalGlobalObject->vm();
JSLockHolder lock(vm);
if (value.isString()) {
[array addObject:[NSNumber numberWithInt:StringValueType]];
[array addObject:asString(value)->value(lexicalGlobalObject)];
} else if (value.isNumber()) {
[array addObject:[NSNumber numberWithInt:DoubleValueType]];
[array addObject:[NSNumber numberWithDouble:value.toNumber(lexicalGlobalObject)]];
} else if (value.isBoolean()) {
[array addObject:[NSNumber numberWithInt:BoolValueType]];
[array addObject:[NSNumber numberWithBool:value.toBoolean(lexicalGlobalObject)]];
} else if (value.isNull())
[array addObject:[NSNumber numberWithInt:NullValueType]];
else if (value.isObject()) {
JSObject* object = asObject(value);
uint64_t objectID;
if (getObjectID(this, object, objectID)) {
[array addObject:[NSNumber numberWithInt:NPObjectValueType]];
[array addObject:[NSNumber numberWithInt:objectID]];
} else {
[array addObject:[NSNumber numberWithInt:JSObjectValueType]];
[array addObject:[NSNumber numberWithInt:m_localObjects.idForObject(vm, object)]];
}
} else
[array addObject:[NSNumber numberWithInt:VoidValueType]];
}
void NetscapePluginInstanceProxy::marshalValue(JSGlobalObject* lexicalGlobalObject, JSValue value, data_t& resultData, mach_msg_type_number_t& resultLength)
{
RetainPtr<NSMutableArray> array = adoptNS([[NSMutableArray alloc] init]);
addValueToArray(array.get(), lexicalGlobalObject, value);
NSData *data = [NSPropertyListSerialization dataWithPropertyList:array.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
ASSERT(data);
resultLength = data.length;
mig_allocate(reinterpret_cast<vm_address_t*>(&resultData), resultLength);
memcpy(resultData, data.bytes, resultLength);
}
RetainPtr<NSData> NetscapePluginInstanceProxy::marshalValues(JSGlobalObject* lexicalGlobalObject, const ArgList& args)
{
RetainPtr<NSMutableArray> array = adoptNS([[NSMutableArray alloc] init]);
for (unsigned i = 0; i < args.size(); i++)
addValueToArray(array.get(), lexicalGlobalObject, args.at(i));
NSData *data = [NSPropertyListSerialization dataWithPropertyList:array.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:nullptr];
ASSERT(data);
return data;
}
bool NetscapePluginInstanceProxy::demarshalValueFromArray(JSGlobalObject* lexicalGlobalObject, NSArray *array, NSUInteger& index, JSValue& result)
{
if (index == [array count])
return false;
int type = [[array objectAtIndex:index++] intValue];
switch (type) {
case VoidValueType:
result = jsUndefined();
return true;
case NullValueType:
result = jsNull();
return true;
case BoolValueType:
result = jsBoolean([[array objectAtIndex:index++] boolValue]);
return true;
case DoubleValueType:
result = jsNumber([[array objectAtIndex:index++] doubleValue]);
return true;
case StringValueType: {
NSString *string = [array objectAtIndex:index++];
result = jsString(lexicalGlobalObject->vm(), String(string));
return true;
}
case JSObjectValueType: {
uint32_t objectID = [[array objectAtIndex:index++] intValue];
result = m_localObjects.get(objectID);
ASSERT(result);
return true;
}
case NPObjectValueType: {
uint32_t objectID = [[array objectAtIndex:index++] intValue];
Frame* frame = core([m_pluginView webFrame]);
if (!frame)
return false;
if (!frame->script().canExecuteScripts(NotAboutToExecuteScript))
return false;
auto rootObject = frame->script().createRootObject((__bridge void*)m_pluginView);
result = ProxyInstance::create(WTFMove(rootObject), this, objectID)->createRuntimeObject(lexicalGlobalObject);
return true;
}
default:
ASSERT_NOT_REACHED();
return false;
}
}
JSValue NetscapePluginInstanceProxy::demarshalValue(JSGlobalObject* lexicalGlobalObject, const char* valueData, mach_msg_type_number_t valueLength)
{
RetainPtr<NSData> data = adoptNS([[NSData alloc] initWithBytesNoCopy:(void*)valueData length:valueLength freeWhenDone:NO]);
NSArray *array = [NSPropertyListSerialization propertyListWithData:data.get() options:NSPropertyListImmutable format:nullptr error:nullptr];
NSUInteger position = 0;
JSValue value;
bool result = demarshalValueFromArray(lexicalGlobalObject, array, position, value);
ASSERT_UNUSED(result, result);
return value;
}
void NetscapePluginInstanceProxy::demarshalValues(JSGlobalObject* lexicalGlobalObject, data_t valuesData, mach_msg_type_number_t valuesLength, MarkedArgumentBuffer& result)
{
RetainPtr<NSData> data = adoptNS([[NSData alloc] initWithBytesNoCopy:valuesData length:valuesLength freeWhenDone:NO]);
NSArray *array = [NSPropertyListSerialization propertyListWithData:data.get() options:NSPropertyListImmutable format:nullptr error:nullptr];
NSUInteger position = 0;
JSValue value;
while (demarshalValueFromArray(lexicalGlobalObject, array, position, value))
result.append(value);
}
void NetscapePluginInstanceProxy::retainLocalObject(JSC::JSValue value)
{
if (!value.isObject() || value.inherits<ProxyRuntimeObject>(value.getObject()->vm()))
return;
m_localObjects.retain(asObject(value));
}
void NetscapePluginInstanceProxy::releaseLocalObject(JSC::JSValue value)
{
if (!value.isObject() || value.inherits<ProxyRuntimeObject>(value.getObject()->vm()))
return;
m_localObjects.release(asObject(value));
}
RefPtr<Instance> NetscapePluginInstanceProxy::createBindingsInstance(Ref<RootObject>&& rootObject)
{
uint32_t requestID = nextRequestID();
if (_WKPHGetScriptableNPObject(m_pluginHostProxy->port(), m_pluginID, requestID) != KERN_SUCCESS)
return nullptr;
auto reply = waitForReply<GetScriptableNPObjectReply>(requestID);
if (!reply)
return nullptr;
if (!reply->m_objectID)
return nullptr;
// Since the reply was non-null, "this" is still a valid pointer.
return ProxyInstance::create(WTFMove(rootObject), this, reply->m_objectID);
}
void NetscapePluginInstanceProxy::addInstance(ProxyInstance* instance)
{
ASSERT(!m_instances.contains(instance));
m_instances.add(instance);
}
void NetscapePluginInstanceProxy::removeInstance(ProxyInstance* instance)
{
ASSERT(m_instances.contains(instance));
m_instances.remove(instance);
}
void NetscapePluginInstanceProxy::willCallPluginFunction()
{
m_pluginFunctionCallDepth++;
}
void NetscapePluginInstanceProxy::didCallPluginFunction(bool& stopped)
{
ASSERT(m_pluginFunctionCallDepth > 0);
m_pluginFunctionCallDepth--;
// If -stop was called while we were calling into a plug-in function, and we're no longer
// inside a plug-in function, stop now.
if (!m_pluginFunctionCallDepth && m_shouldStopSoon) {
m_shouldStopSoon = false;
[m_pluginView stop];
stopped = true;
}
}
bool NetscapePluginInstanceProxy::shouldStop()
{
if (m_pluginFunctionCallDepth) {
m_shouldStopSoon = true;
return false;
}
return true;
}
uint32_t NetscapePluginInstanceProxy::nextRequestID()
{
uint32_t requestID = ++m_currentRequestID;
// We don't want to return the HashMap empty/deleted "special keys"
if (requestID == 0 || requestID == static_cast<uint32_t>(-1))
return nextRequestID();
return requestID;
}
void NetscapePluginInstanceProxy::invalidateRect(double x, double y, double width, double height)
{
ASSERT(m_pluginView);
m_pluginIsWaitingForDraw = true;
[m_pluginView invalidatePluginContentRect:NSMakeRect(x, y, width, height)];
}
void NetscapePluginInstanceProxy::didDraw()
{
if (!m_pluginIsWaitingForDraw)
return;
m_pluginIsWaitingForDraw = false;
_WKPHPluginInstanceDidDraw(m_pluginHostProxy->port(), m_pluginID);
}
bool NetscapePluginInstanceProxy::getCookies(data_t urlData, mach_msg_type_number_t urlLength, data_t& cookiesData, mach_msg_type_number_t& cookiesLength)
{
ASSERT(m_pluginView);
NSURL *url = [m_pluginView URLWithCString:urlData];
if (!url)
return false;
if (Frame* frame = core([m_pluginView webFrame])) {
auto* document = frame->document();
if (!document)
return false;
auto* page = document->page();
if (!page)
return false;
String cookieString = page->cookieJar().cookies(*document, url);
WTF::CString cookieStringUTF8 = cookieString.utf8();
if (cookieStringUTF8.isNull())
return false;
cookiesLength = cookieStringUTF8.length();
mig_allocate(reinterpret_cast<vm_address_t*>(&cookiesData), cookiesLength);
memcpy(cookiesData, cookieStringUTF8.data(), cookiesLength);
return true;
}
return false;
}
bool NetscapePluginInstanceProxy::setCookies(data_t urlData, mach_msg_type_number_t urlLength, data_t cookiesData, mach_msg_type_number_t cookiesLength)
{
ASSERT(m_pluginView);
NSURL *url = [m_pluginView URLWithCString:urlData];
if (!url)
return false;
if (Frame* frame = core([m_pluginView webFrame])) {
String cookieString = String::fromUTF8(cookiesData, cookiesLength);
if (!cookieString)
return false;
auto* document = frame->document();
if (!document)
return false;
auto* page = document->page();
if (!page)
return false;
page->cookieJar().setCookies(*document, url, cookieString);
return true;
}
return false;
}
bool NetscapePluginInstanceProxy::getProxy(data_t urlData, mach_msg_type_number_t urlLength, data_t& proxyData, mach_msg_type_number_t& proxyLength)
{
ASSERT(m_pluginView);
NSURL *url = [m_pluginView URLWithCString:urlData];
if (!url)
return false;
Vector<ProxyServer> proxyServers = proxyServersForURL(url);
WTF::CString proxyStringUTF8 = toString(proxyServers).utf8();
proxyLength = proxyStringUTF8.length();
mig_allocate(reinterpret_cast<vm_address_t*>(&proxyData), proxyLength);
memcpy(proxyData, proxyStringUTF8.data(), proxyLength);
return true;
}
bool NetscapePluginInstanceProxy::getAuthenticationInfo(data_t protocolData, data_t hostData, uint32_t port, data_t schemeData, data_t realmData,
data_t& usernameData, mach_msg_type_number_t& usernameLength, data_t& passwordData, mach_msg_type_number_t& passwordLength)
{
WTF::CString username;
WTF::CString password;
if (!WebKit::getAuthenticationInfo(protocolData, hostData, port, schemeData, realmData, username, password))
return false;
usernameLength = username.length();
mig_allocate(reinterpret_cast<vm_address_t*>(&usernameData), usernameLength);
memcpy(usernameData, username.data(), usernameLength);
passwordLength = password.length();
mig_allocate(reinterpret_cast<vm_address_t*>(&passwordData), passwordLength);
memcpy(passwordData, password.data(), passwordLength);
return true;
}
bool NetscapePluginInstanceProxy::convertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
double& destX, double& destY, NPCoordinateSpace destSpace)
{
ASSERT(m_pluginView);
return [m_pluginView convertFromX:sourceX andY:sourceY space:sourceSpace toX:&destX andY:&destY space:destSpace];
}
void NetscapePluginInstanceProxy::privateBrowsingModeDidChange(bool isPrivateBrowsingEnabled)
{
_WKPHPluginInstancePrivateBrowsingModeDidChange(m_pluginHostProxy->port(), m_pluginID, isPrivateBrowsingEnabled);
}
static String& globalExceptionString()
{
static NeverDestroyed<String> exceptionString;
return exceptionString;
}
void NetscapePluginInstanceProxy::setGlobalException(const String& exception)
{
globalExceptionString() = exception;
}
void NetscapePluginInstanceProxy::moveGlobalExceptionToExecState(JSGlobalObject* lexicalGlobalObject)
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (globalExceptionString().isNull())
return;
{
JSLockHolder lock(vm);
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, globalExceptionString()));
}
globalExceptionString() = String();
}
} // namespace WebKit
#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)