haikuwebkit/Source/WebCore/bridge/objc/objc_runtime.mm

343 lines
12 KiB
Plaintext

/*
* Copyright (C) 2004-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.
*/
#import "config.h"
#import "objc_runtime.h"
#import "JSDOMBinding.h"
#import "ObjCRuntimeObject.h"
#import "WebScriptObject.h"
#import "WebScriptObjectProtocol.h"
#import "objc_instance.h"
#import "runtime_array.h"
#import "runtime_object.h"
#import <JavaScriptCore/Error.h>
#import <JavaScriptCore/IsoSubspacePerVM.h>
#import <JavaScriptCore/JSDestructibleObjectHeapCellType.h>
#import <JavaScriptCore/JSGlobalObject.h>
#import <JavaScriptCore/JSLock.h>
#import <wtf/RetainPtr.h>
using namespace WebCore;
namespace JSC {
namespace Bindings {
static JSC_DECLARE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive);
ClassStructPtr webScriptObjectClass()
{
static ClassStructPtr<WebScriptObject> webScriptObjectClass = NSClassFromString(@"WebScriptObject");
return webScriptObjectClass;
}
ClassStructPtr webUndefinedClass()
{
static ClassStructPtr<WebUndefined> webUndefinedClass = NSClassFromString(@"WebUndefined");
return webUndefinedClass;
}
// ---------------------- ObjcMethod ----------------------
ObjcMethod::ObjcMethod(ClassStructPtr aClass, SEL selector)
: _objcClass(aClass)
, _selector(selector)
{
}
int ObjcMethod::numParameters() const
{
return [getMethodSignature() numberOfArguments] - 2;
}
NSMethodSignature* ObjcMethod::getMethodSignature() const
{
return [_objcClass instanceMethodSignatureForSelector:_selector];
}
bool ObjcMethod::isFallbackMethod() const
{
return _selector == @selector(invokeUndefinedMethodFromWebScript:withArguments:);
}
// ---------------------- ObjcField ----------------------
ObjcField::ObjcField(Ivar ivar)
: _ivar(ivar)
, _name(adoptCF(CFStringCreateWithCString(0, ivar_getName(_ivar), kCFStringEncodingASCII)))
{
}
ObjcField::ObjcField(CFStringRef name)
: _ivar(0)
, _name(name)
{
}
JSValue ObjcField::valueFromInstance(JSGlobalObject* lexicalGlobalObject, const Instance* instance) const
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue result = jsUndefined();
id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
JSLock::DropAllLocks dropAllLocks(lexicalGlobalObject); // Can't put this inside the @try scope because it unwinds incorrectly.
@try {
if (id objcValue = [targetObject valueForKey:(__bridge NSString *)_name.get()])
result = convertObjcValueToValue(lexicalGlobalObject, &objcValue, ObjcObjectType, instance->rootObject());
{
JSLockHolder lock(lexicalGlobalObject);
ObjcInstance::moveGlobalExceptionToExecState(lexicalGlobalObject);
}
} @catch(NSException* localException) {
JSLockHolder lock(lexicalGlobalObject);
throwError(lexicalGlobalObject, scope, [localException reason]);
}
// Work around problem in some versions of GCC where result gets marked volatile and
// it can't handle copying from a volatile to non-volatile.
return const_cast<JSValue&>(result);
}
static id convertValueToObjcObject(JSGlobalObject* lexicalGlobalObject, JSValue value)
{
VM& vm = lexicalGlobalObject->vm();
RefPtr<RootObject> rootObject = findRootObject(vm.deprecatedVMEntryGlobalObject(lexicalGlobalObject));
if (!rootObject)
return nil;
return [webScriptObjectClass() _convertValueToObjcValue:value originRootObject:rootObject.get() rootObject:rootObject.get()];
}
bool ObjcField::setValueToInstance(JSGlobalObject* lexicalGlobalObject, const Instance* instance, JSValue aValue) const
{
JSC::VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
id value = convertValueToObjcObject(lexicalGlobalObject, aValue);
JSLock::DropAllLocks dropAllLocks(lexicalGlobalObject); // Can't put this inside the @try scope because it unwinds incorrectly.
@try {
[targetObject setValue:value forKey:(__bridge NSString *)_name.get()];
{
JSLockHolder lock(lexicalGlobalObject);
ObjcInstance::moveGlobalExceptionToExecState(lexicalGlobalObject);
}
return true;
} @catch(NSException* localException) {
JSLockHolder lock(lexicalGlobalObject);
throwError(lexicalGlobalObject, scope, [localException reason]);
return false;
}
}
// ---------------------- ObjcArray ----------------------
ObjcArray::ObjcArray(ObjectStructPtr a, RefPtr<RootObject>&& rootObject)
: Array(WTFMove(rootObject))
, _array(a)
{
}
bool ObjcArray::setValueAt(JSGlobalObject* lexicalGlobalObject, unsigned int index, JSValue aValue) const
{
JSC::VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (![_array.get() respondsToSelector:@selector(insertObject:atIndex:)]) {
throwTypeError(lexicalGlobalObject, scope, "Array is not mutable."_s);
return false;
}
if (index > [_array.get() count]) {
throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Index exceeds array size."));
return false;
}
// Always try to convert the value to an ObjC object, so it can be placed in the
// array.
ObjcValue oValue = convertValueToObjcValue (lexicalGlobalObject, aValue, ObjcObjectType);
@try {
[_array.get() insertObject:(__bridge id)oValue.objectValue atIndex:index];
return true;
} @catch(NSException* localException) {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Objective-C exception."));
return false;
}
}
JSValue ObjcArray::valueAt(JSGlobalObject* lexicalGlobalObject, unsigned int index) const
{
JSC::VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (index > [_array.get() count])
return throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Index exceeds array size."));
@try {
id obj = [_array.get() objectAtIndex:index];
if (obj)
return convertObjcValueToValue (lexicalGlobalObject, &obj, ObjcObjectType, m_rootObject.get());
} @catch(NSException* localException) {
return throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Objective-C exception."));
}
return jsUndefined();
}
unsigned int ObjcArray::getLength() const
{
return [_array.get() count];
}
const ClassInfo ObjcFallbackObjectImp::s_info = { "ObjcFallbackObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ObjcFallbackObjectImp) };
ObjcFallbackObjectImp::ObjcFallbackObjectImp(JSGlobalObject* globalObject, Structure* structure, ObjcInstance* i, const String& propertyName)
: JSDestructibleObject(globalObject->vm(), structure)
, _instance(i)
, m_item(propertyName)
{
}
void ObjcFallbackObjectImp::destroy(JSCell* cell)
{
static_cast<ObjcFallbackObjectImp*>(cell)->ObjcFallbackObjectImp::~ObjcFallbackObjectImp();
}
void ObjcFallbackObjectImp::finishCreation(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
Base::finishCreation(vm);
ASSERT(inherits(vm, info()));
putDirect(vm, vm.propertyNames->toPrimitiveSymbol,
JSFunction::create(vm, globalObject, 0, "[Symbol.toPrimitive]"_s, convertObjCFallbackObjectToPrimitive),
static_cast<unsigned>(PropertyAttribute::DontEnum));
}
bool ObjcFallbackObjectImp::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = globalObject->vm();
if (propertyName.uid() == vm.propertyNames->toPrimitiveSymbol.impl())
return JSObject::getOwnPropertySlot(object, globalObject, propertyName, slot);
// keep the prototype from getting called instead of just returning false
slot.setUndefined();
return true;
}
bool ObjcFallbackObjectImp::put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&)
{
return false;
}
static JSC_DECLARE_HOST_FUNCTION(callObjCFallbackObject);
JSC_DEFINE_HOST_FUNCTION(callObjCFallbackObject, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))
{
JSC::VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue thisValue = callFrame->thisValue();
if (!thisValue.inherits<ObjCRuntimeObject>(vm))
return throwVMTypeError(lexicalGlobalObject, scope);
JSValue result = jsUndefined();
ObjCRuntimeObject* runtimeObject = static_cast<ObjCRuntimeObject*>(asObject(thisValue));
ObjcInstance* objcInstance = runtimeObject->getInternalObjCInstance();
if (!objcInstance)
return JSValue::encode(throwRuntimeObjectInvalidAccessError(lexicalGlobalObject, scope));
objcInstance->begin();
id targetObject = objcInstance->getObject();
if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]){
ObjcClass* objcClass = static_cast<ObjcClass*>(objcInstance->getClass());
std::unique_ptr<ObjcMethod> fallbackMethod(makeUnique<ObjcMethod>(objcClass->isa(), @selector(invokeUndefinedMethodFromWebScript:withArguments:)));
const String& nameIdentifier = static_cast<ObjcFallbackObjectImp*>(callFrame->jsCallee())->propertyName();
fallbackMethod->setJavaScriptName(nameIdentifier.createCFString().get());
result = objcInstance->invokeObjcMethod(lexicalGlobalObject, callFrame, fallbackMethod.get());
}
objcInstance->end();
return JSValue::encode(result);
}
CallData ObjcFallbackObjectImp::getCallData(JSCell* cell)
{
CallData callData;
ObjcFallbackObjectImp* thisObject = jsCast<ObjcFallbackObjectImp*>(cell);
id targetObject = thisObject->_instance->getObject();
if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) {
callData.type = CallData::Type::Native;
callData.native.function = callObjCFallbackObject;
}
return callData;
}
bool ObjcFallbackObjectImp::deleteProperty(JSCell*, JSGlobalObject*, PropertyName, DeletePropertySlot&)
{
return false;
}
JSC_DEFINE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<ObjcFallbackObjectImp*>(vm, callFrame->thisValue());
if (!thisObject)
return throwVMTypeError(lexicalGlobalObject, scope, "ObjcFallbackObject[Symbol.toPrimitive] method called on incompatible |this| value."_s);
scope.release();
return JSValue::encode(thisObject->getInternalObjCInstance()->getValueOfUndefinedField(lexicalGlobalObject, Identifier::fromString(vm, thisObject->propertyName())));
}
bool ObjcFallbackObjectImp::toBoolean(JSGlobalObject*) const
{
id targetObject = _instance->getObject();
if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
return true;
return false;
}
JSC::IsoSubspace* ObjcFallbackObjectImp::subspaceForImpl(JSC::VM& vm)
{
static NeverDestroyed<JSC::IsoSubspacePerVM> perVM([] (JSC::VM& vm) { return ISO_SUBSPACE_PARAMETERS(vm.destructibleObjectHeapCellType.get(), ObjcFallbackObjectImp); });
return &perVM.get().forVM(vm);
}
}
}