diff options
Diffstat (limited to 'WebCore/bindings/objc/WebScriptObject.mm')
-rw-r--r-- | WebCore/bindings/objc/WebScriptObject.mm | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/WebCore/bindings/objc/WebScriptObject.mm b/WebCore/bindings/objc/WebScriptObject.mm new file mode 100644 index 0000000..4f1168b --- /dev/null +++ b/WebCore/bindings/objc/WebScriptObject.mm @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2004, 2006, 2007 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 COMPUTER, 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 COMPUTER, 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 "WebScriptObjectPrivate.h" + +#import "DOMInternal.h" +#import "Frame.h" +#import "PlatformString.h" +#import "WebCoreObjCExtras.h" +#import "WebCoreFrameBridge.h" +#import <JavaScriptCore/ExecState.h> +#import <JavaScriptCore/objc_instance.h> +#import <JavaScriptCore/runtime_object.h> +#import <JavaScriptCore/APICast.h> +#import <JavaScriptCore/JSGlobalObject.h> +#import <JavaScriptCore/interpreter.h> + +using namespace KJS; +using namespace KJS::Bindings; +using namespace WebCore; + +#define LOG_EXCEPTION(exec) \ + if (Interpreter::shouldPrintExceptions()) \ + printf("%s:%d:[%d] JavaScript exception: %s\n", __FILE__, __LINE__, getpid(), exec->exception()->toObject(exec)->get(exec, exec->propertyNames().message)->toString(exec).ascii()); + +@interface WebFrame +- (WebCoreFrameBridge *)_bridge; // implemented in WebKit +@end + +namespace WebCore { + +typedef HashMap<JSObject*, NSObject*> JSWrapperMap; +static JSWrapperMap* JSWrapperCache; + +NSObject* getJSWrapper(JSObject* impl) +{ + if (!JSWrapperCache) + return nil; + return JSWrapperCache->get(impl); +} + +void addJSWrapper(NSObject* wrapper, JSObject* impl) +{ + if (!JSWrapperCache) + JSWrapperCache = new JSWrapperMap; + JSWrapperCache->set(impl, wrapper); +} + +void removeJSWrapper(JSObject* impl) +{ + if (!JSWrapperCache) + return; + JSWrapperCache->remove(impl); +} + +id createJSWrapper(KJS::JSObject* object, PassRefPtr<KJS::Bindings::RootObject> origin, PassRefPtr<KJS::Bindings::RootObject> root) +{ + if (id wrapper = getJSWrapper(object)) + return [[wrapper retain] autorelease]; + return [[[WebScriptObject alloc] _initWithJSObject:object originRootObject:origin rootObject:root] autorelease]; +} + +} // namespace WebCore + +@implementation WebScriptObjectPrivate + +@end + +@implementation WebScriptObject + +#ifndef BUILDING_ON_TIGER ++ (void)initialize +{ + WebCoreObjCFinalizeOnMainThread(self); +} +#endif + ++ (id)scriptObjectForJSObject:(JSObjectRef)jsObject originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject +{ + if (id domWrapper = WebCore::createDOMWrapper(toJS(jsObject), originRootObject, rootObject)) + return domWrapper; + + return WebCore::createJSWrapper(toJS(jsObject), originRootObject, rootObject); +} + +static void _didExecute(WebScriptObject *obj) +{ + ASSERT(JSLock::lockCount() > 0); + + RootObject* root = [obj _rootObject]; + if (!root) + return; + + ExecState* exec = root->globalObject()->globalExec(); + KJSDidExecuteFunctionPtr func = Instance::didExecuteFunction(); + if (func) + func(exec, root->globalObject()); +} + +- (void)_setImp:(JSObject*)imp originRootObject:(PassRefPtr<RootObject>)originRootObject rootObject:(PassRefPtr<RootObject>)rootObject +{ + // This function should only be called once, as a (possibly lazy) initializer. + ASSERT(!_private->imp); + ASSERT(!_private->rootObject); + ASSERT(!_private->originRootObject); + ASSERT(imp); + + _private->imp = imp; + _private->rootObject = rootObject.releaseRef(); + _private->originRootObject = originRootObject.releaseRef(); + + WebCore::addJSWrapper(self, imp); + + if (_private->rootObject) + _private->rootObject->gcProtect(imp); +} + +- (void)_setOriginRootObject:(PassRefPtr<RootObject>)originRootObject andRootObject:(PassRefPtr<RootObject>)rootObject +{ + ASSERT(_private->imp); + + if (rootObject) + rootObject->gcProtect(_private->imp); + + if (_private->rootObject && _private->rootObject->isValid()) + _private->rootObject->gcUnprotect(_private->imp); + + if (_private->rootObject) + _private->rootObject->deref(); + + if (_private->originRootObject) + _private->originRootObject->deref(); + + _private->rootObject = rootObject.releaseRef(); + _private->originRootObject = originRootObject.releaseRef(); +} + +- (id)_initWithJSObject:(KJS::JSObject*)imp originRootObject:(PassRefPtr<KJS::Bindings::RootObject>)originRootObject rootObject:(PassRefPtr<KJS::Bindings::RootObject>)rootObject +{ + ASSERT(imp); + + self = [super init]; + _private = [[WebScriptObjectPrivate alloc] init]; + [self _setImp:imp originRootObject:originRootObject rootObject:rootObject]; + + return self; +} + +- (JSObject*)_imp +{ + // Associate the WebScriptObject with the JS wrapper for the ObjC DOM wrapper. + // This is done on lazily, on demand. + if (!_private->imp && _private->isCreatedByDOMWrapper) + [self _initializeScriptDOMNodeImp]; + return [self _rootObject] ? _private->imp : 0; +} + +- (BOOL)_hasImp +{ + return _private->imp != nil; +} + +// Node that DOMNode overrides this method. So you should almost always +// use this method call instead of _private->rootObject directly. +- (RootObject*)_rootObject +{ + return _private->rootObject && _private->rootObject->isValid() ? _private->rootObject : 0; +} + +- (RootObject *)_originRootObject +{ + return _private->originRootObject && _private->originRootObject->isValid() ? _private->originRootObject : 0; +} + +- (BOOL)_isSafeScript +{ + RootObject *root = [self _rootObject]; + if (!root) + return false; + + if (!_private->originRootObject) + return true; + + if (!_private->originRootObject->isValid()) + return false; + + return root->globalObject()->allowsAccessFrom(_private->originRootObject->globalObject()); +} + +- (void)dealloc +{ + if (_private->imp) + WebCore::removeJSWrapper(_private->imp); + + if (_private->rootObject && _private->rootObject->isValid()) + _private->rootObject->gcUnprotect(_private->imp); + + if (_private->rootObject) + _private->rootObject->deref(); + + if (_private->originRootObject) + _private->originRootObject->deref(); + + [_private release]; + + [super dealloc]; +} + +- (void)finalize +{ + if (_private->imp) + WebCore::removeJSWrapper(_private->imp); + + if (_private->rootObject && _private->rootObject->isValid()) + _private->rootObject->gcUnprotect(_private->imp); + + if (_private->rootObject) + _private->rootObject->deref(); + + if (_private->originRootObject) + _private->originRootObject->deref(); + + [super finalize]; +} + ++ (BOOL)throwException:(NSString *)exceptionMessage +{ + JSLock lock; + + // This code assumes that we only ever have one running interpreter. A + // good assumption for now, as we depend on that elsewhere. However, + // in the future we may have the ability to run multiple interpreters, + // in which case this will have to change. + + if (ExecState::activeExecStates().size()) { + throwError(ExecState::activeExecStates().last(), GeneralError, exceptionMessage); + return YES; + } + + return NO; +} + +static void getListFromNSArray(ExecState *exec, NSArray *array, RootObject* rootObject, List& aList) +{ + int i, numObjects = array ? [array count] : 0; + + for (i = 0; i < numObjects; i++) { + id anObject = [array objectAtIndex:i]; + aList.append(convertObjcValueToValue(exec, &anObject, ObjcObjectType, rootObject)); + } +} + +- (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args +{ + if (![self _isSafeScript]) + return nil; + + // Look up the function object. + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock; + + JSValue* func = [self _imp]->get(exec, String(name)); + + if (!func || !func->isObject()) + // Maybe throw an exception here? + return 0; + + // Call the function object. + JSObject *funcImp = static_cast<JSObject*>(func); + if (!funcImp->implementsCall()) + return 0; + + List argList; + getListFromNSArray(exec, args, [self _rootObject], argList); + + if (![self _isSafeScript]) + return nil; + + [self _rootObject]->globalObject()->startTimeoutCheck(); + JSValue *result = funcImp->call(exec, [self _imp], argList); + [self _rootObject]->globalObject()->stopTimeoutCheck(); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + result = jsUndefined(); + exec->clearException(); + } + + // Convert and return the result of the function call. + id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]]; + + _didExecute(self); + + return resultObj; +} + +- (id)evaluateWebScript:(NSString *)script +{ + if (![self _isSafeScript]) + return nil; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSValue *result; + JSLock lock; + + [self _rootObject]->globalObject()->startTimeoutCheck(); + Completion completion = Interpreter::evaluate([self _rootObject]->globalObject()->globalExec(), UString(), 0, String(script)); + [self _rootObject]->globalObject()->stopTimeoutCheck(); + ComplType type = completion.complType(); + + if (type == Normal) { + result = completion.value(); + if (!result) + result = jsUndefined(); + } else + result = jsUndefined(); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + result = jsUndefined(); + exec->clearException(); + } + + id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]]; + + _didExecute(self); + + return resultObj; +} + +- (void)setValue:(id)value forKey:(NSString *)key +{ + if (![self _isSafeScript]) + return; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock; + [self _imp]->put(exec, String(key), convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject])); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + exec->clearException(); + } + + _didExecute(self); +} + +- (id)valueForKey:(NSString *)key +{ + if (![self _isSafeScript]) + return nil; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + id resultObj; + { + // Need to scope this lock to ensure that we release the lock before calling + // [super valueForKey:key] which might throw an exception and bypass the JSLock destructor, + // leaving the lock permanently held + JSLock lock; + + JSValue *result = [self _imp]->get(exec, String(key)); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + result = jsUndefined(); + exec->clearException(); + } + + resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]]; + } + + if ([resultObj isKindOfClass:[WebUndefined class]]) + resultObj = [super valueForKey:key]; // defaults to throwing an exception + + JSLock lock; + _didExecute(self); + + return resultObj; +} + +- (void)removeWebScriptKey:(NSString *)key +{ + if (![self _isSafeScript]) + return; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock; + [self _imp]->deleteProperty(exec, String(key)); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + exec->clearException(); + } + + _didExecute(self); +} + +- (NSString *)stringRepresentation +{ + if (![self _isSafeScript]) + // This is a workaround for a gcc 3.3 internal compiler error. + return @"Undefined"; + + JSLock lock; + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + + id result = convertValueToObjcValue(exec, [self _imp], ObjcObjectType).objectValue; + + NSString *description = [result description]; + + _didExecute(self); + + return description; +} + +- (id)webScriptValueAtIndex:(unsigned)index +{ + if (![self _isSafeScript]) + return nil; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock; + JSValue *result = [self _imp]->get(exec, index); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + result = jsUndefined(); + exec->clearException(); + } + + id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]]; + + _didExecute(self); + + return resultObj; +} + +- (void)setWebScriptValueAtIndex:(unsigned)index value:(id)value +{ + if (![self _isSafeScript]) + return; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock; + [self _imp]->put(exec, index, convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject])); + + if (exec->hadException()) { + LOG_EXCEPTION(exec); + exec->clearException(); + } + + _didExecute(self); +} + +- (void)setException:(NSString *)description +{ + if (![self _rootObject]) + return; + + JSLock lock; + + ExecState* exec = 0; + JSObject* globalObject = [self _rootObject]->globalObject(); + ExecStateStack::const_iterator end = ExecState::activeExecStates().end(); + for (ExecStateStack::const_iterator it = ExecState::activeExecStates().begin(); it != end; ++it) + if ((*it)->dynamicGlobalObject() == globalObject) + exec = *it; + + if (exec) + throwError(exec, GeneralError, description); +} + +- (JSObjectRef)JSObject +{ + if (![self _isSafeScript]) + return NULL; + + return toRef([self _imp]); +} + ++ (id)_convertValueToObjcValue:(JSValue*)value originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject +{ + if (value->isObject()) { + JSObject* object = static_cast<JSObject*>(value); + ExecState* exec = rootObject->globalObject()->globalExec(); + JSLock lock; + + if (object->classInfo() != &RuntimeObjectImp::info) { + JSValue* runtimeObject = object->get(exec, "__apple_runtime_object"); + if (runtimeObject && runtimeObject->isObject()) + object = static_cast<RuntimeObjectImp*>(runtimeObject); + } + + if (object->classInfo() == &RuntimeObjectImp::info) { + RuntimeObjectImp* imp = static_cast<RuntimeObjectImp*>(object); + ObjcInstance *instance = static_cast<ObjcInstance*>(imp->getInternalInstance()); + if (instance) + return instance->getObject(); + return nil; + } + + return [WebScriptObject scriptObjectForJSObject:toRef(object) originRootObject:originRootObject rootObject:rootObject]; + } + + if (value->isString()) { + UString u = value->getString(); + return [NSString stringWithCharacters:(const unichar*)u.data() length:u.size()]; + } + + if (value->isNumber()) + return [NSNumber numberWithDouble:value->getNumber()]; + + if (value->isBoolean()) + return [NSNumber numberWithBool:value->getBoolean()]; + + if (value->isUndefined()) + return [WebUndefined undefined]; + + // jsNull is not returned as NSNull because existing applications do not expect + // that return value. Return as nil for compatibility. <rdar://problem/4651318> <rdar://problem/4701626> + // Other types (e.g., UnspecifiedType) also return as nil. + return nil; +} + +@end + +@interface WebScriptObject (WebKitCocoaBindings) + +- (id)objectAtIndex:(unsigned)index; + +@end + +@implementation WebScriptObject (WebKitCocoaBindings) + +#if 0 +// FIXME: presence of 'count' method on WebScriptObject breaks Democracy player +// http://bugs.webkit.org/show_bug.cgi?id=13129 + +- (unsigned)count +{ + id length = [self valueForKey:@"length"]; + if ([length respondsToSelector:@selector(intValue)]) + return [length intValue]; + else + return 0; +} + +#endif + +- (id)objectAtIndex:(unsigned)index +{ + return [self webScriptValueAtIndex:index]; +} + +@end + +@implementation WebUndefined + ++ (id)allocWithZone:(NSZone *)zone +{ + static WebUndefined *sharedUndefined = 0; + if (!sharedUndefined) + sharedUndefined = [super allocWithZone:NULL]; + return sharedUndefined; +} + +- (NSString *)description +{ + return @"undefined"; +} + +- (id)initWithCoder:(NSCoder *)coder +{ + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder +{ +} + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)retain +{ + return self; +} + +- (void)release +{ +} + +- (unsigned)retainCount +{ + return UINT_MAX; +} + +- (id)autorelease +{ + return self; +} + +- (void)dealloc +{ + ASSERT(false); + return; + [super dealloc]; // make -Wdealloc-check happy +} + ++ (WebUndefined *)undefined +{ + return [WebUndefined allocWithZone:NULL]; +} + +@end + |