diff options
Diffstat (limited to 'Source/WebCore/bindings/objc/WebScriptObject.mm')
-rw-r--r-- | Source/WebCore/bindings/objc/WebScriptObject.mm | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/Source/WebCore/bindings/objc/WebScriptObject.mm b/Source/WebCore/bindings/objc/WebScriptObject.mm new file mode 100644 index 0000000..6bf7afe --- /dev/null +++ b/Source/WebCore/bindings/objc/WebScriptObject.mm @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2004, 2006, 2007, 2008 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 "Bridge.h" +#import "Console.h" +#import "DOMInternal.h" +#import "DOMWindow.h" +#import "Frame.h" +#import "JSDOMWindow.h" +#import "JSDOMWindowCustom.h" +#import "JSHTMLElement.h" +#import "JSMainThreadExecState.h" +#import "JSPluginElementFunctions.h" +#import "ObjCRuntimeObject.h" +#import "PlatformString.h" +#import "StringSourceProvider.h" +#import "WebCoreObjCExtras.h" +#import "objc_instance.h" +#import "runtime_object.h" +#import "runtime_root.h" +#import <JavaScriptCore/APICast.h> +#import <interpreter/CallFrame.h> +#import <runtime/InitializeThreading.h> +#import <runtime/JSGlobalObject.h> +#import <runtime/JSLock.h> +#import <runtime/Completion.h> +#import <runtime/Completion.h> +#import <wtf/Threading.h> + +#ifdef BUILDING_ON_TIGER +typedef unsigned NSUInteger; +#endif + +using namespace JSC; +using namespace JSC::Bindings; +using namespace WebCore; + +namespace WebCore { + +static NSMapTable* JSWrapperCache; + +NSObject* getJSWrapper(JSObject* impl) +{ + if (!JSWrapperCache) + return nil; + return static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl)); +} + +void addJSWrapper(NSObject* wrapper, JSObject* impl) +{ + if (!JSWrapperCache) + JSWrapperCache = createWrapperCache(); + NSMapInsert(JSWrapperCache, impl, wrapper); +} + +void removeJSWrapper(JSObject* impl) +{ + if (!JSWrapperCache) + return; + NSMapRemove(JSWrapperCache, impl); +} + +id createJSWrapper(JSC::JSObject* object, PassRefPtr<JSC::Bindings::RootObject> origin, PassRefPtr<JSC::Bindings::RootObject> root) +{ + if (id wrapper = getJSWrapper(object)) + return [[wrapper retain] autorelease]; + return [[[WebScriptObject alloc] _initWithJSObject:object originRootObject:origin rootObject:root] autorelease]; +} + +static void addExceptionToConsole(ExecState* exec) +{ + JSDOMWindow* window = asJSDOMWindow(exec->dynamicGlobalObject()); + if (!window || !exec->hadException()) + return; + reportCurrentException(exec); +} + +} // namespace WebCore + +@implementation WebScriptObjectPrivate + +@end + +@implementation WebScriptObject + ++ (void)initialize +{ + JSC::initializeThreading(); + WTF::initializeMainThreadToProcessMainThread(); +#ifndef BUILDING_ON_TIGER + WebCoreObjCFinalizeOnMainThread(self); +#endif +} + ++ (id)scriptObjectForJSObject:(JSObjectRef)jsObject originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject +{ + if (id domWrapper = 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:(JSC::JSObject*)imp originRootObject:(PassRefPtr<JSC::Bindings::RootObject>)originRootObject rootObject:(PassRefPtr<JSC::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 (WebCoreObjCScheduleDeallocateOnMainThread([WebScriptObject class], self)) + return; + + 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->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 +{ + ObjcInstance::setGlobalException(exceptionMessage); + return YES; +} + +static void getListFromNSArray(ExecState *exec, NSArray *array, RootObject* rootObject, MarkedArgumentBuffer& 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; + + JSLock lock(SilenceAssertionsOnly); + + // Look up the function object. + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSValue function = [self _imp]->get(exec, Identifier(exec, stringToUString(String(name)))); + CallData callData; + CallType callType = getCallData(function, callData); + if (callType == CallTypeNone) + return nil; + + MarkedArgumentBuffer argList; + getListFromNSArray(exec, args, [self _rootObject], argList); + + if (![self _isSafeScript]) + return nil; + + [self _rootObject]->globalObject()->globalData().timeoutChecker.start(); + JSValue result = JSMainThreadExecState::call(exec, function, callType, callData, [self _imp], argList); + [self _rootObject]->globalObject()->globalData().timeoutChecker.stop(); + + if (exec->hadException()) { + addExceptionToConsole(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(SilenceAssertionsOnly); + + [self _rootObject]->globalObject()->globalData().timeoutChecker.start(); + Completion completion = JSMainThreadExecState::evaluate([self _rootObject]->globalObject()->globalExec(), [self _rootObject]->globalObject()->globalScopeChain(), makeSource(String(script)), JSC::JSValue()); + [self _rootObject]->globalObject()->globalData().timeoutChecker.stop(); + ComplType type = completion.complType(); + + if (type == Normal) { + result = completion.value(); + if (!result) + result = jsUndefined(); + } else + result = jsUndefined(); + + if (exec->hadException()) { + addExceptionToConsole(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(SilenceAssertionsOnly); + + PutPropertySlot slot; + [self _imp]->put(exec, Identifier(exec, stringToUString(String(key))), convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject]), slot); + + if (exec->hadException()) { + addExceptionToConsole(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(SilenceAssertionsOnly); + + JSValue result = [self _imp]->get(exec, Identifier(exec, stringToUString(String(key)))); + + if (exec->hadException()) { + addExceptionToConsole(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(SilenceAssertionsOnly); + _didExecute(self); + + return resultObj; +} + +- (void)removeWebScriptKey:(NSString *)key +{ + if (![self _isSafeScript]) + return; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock(SilenceAssertionsOnly); + [self _imp]->deleteProperty(exec, Identifier(exec, stringToUString(String(key)))); + + if (exec->hadException()) { + addExceptionToConsole(exec); + exec->clearException(); + } + + _didExecute(self); +} + +- (BOOL)hasWebScriptKey:(NSString *)key +{ + if (![self _isSafeScript]) + return NO; + + ExecState* exec = [self _rootObject]->globalObject()->globalExec(); + ASSERT(!exec->hadException()); + + JSLock lock(SilenceAssertionsOnly); + BOOL result = [self _imp]->hasProperty(exec, Identifier(exec, stringToUString(String(key)))); + + if (exec->hadException()) { + addExceptionToConsole(exec); + exec->clearException(); + } + + _didExecute(self); + + return result; +} + +- (NSString *)stringRepresentation +{ + if (![self _isSafeScript]) { + // This is a workaround for a gcc 3.3 internal compiler error. + return @"Undefined"; + } + + JSLock lock(SilenceAssertionsOnly); + 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(SilenceAssertionsOnly); + JSValue result = [self _imp]->get(exec, index); + + if (exec->hadException()) { + addExceptionToConsole(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(SilenceAssertionsOnly); + [self _imp]->put(exec, index, convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject])); + + if (exec->hadException()) { + addExceptionToConsole(exec); + exec->clearException(); + } + + _didExecute(self); +} + +- (void)setException:(NSString *)description +{ + if (![self _rootObject]) + return; + ObjcInstance::setGlobalException(description, [self _rootObject]->globalObject()); +} + +- (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 = asObject(value); + JSLock lock(SilenceAssertionsOnly); + + if (object->inherits(&JSHTMLElement::s_info)) { + // Plugin elements cache the instance internally. + HTMLElement* el = static_cast<JSHTMLElement*>(object)->impl(); + ObjcInstance* instance = static_cast<ObjcInstance*>(pluginInstance(el)); + if (instance) + return instance->getObject(); + } else if (object->inherits(&ObjCRuntimeObject::s_info)) { + ObjCRuntimeObject* runtimeObject = static_cast<ObjCRuntimeObject*>(object); + ObjcInstance* instance = runtimeObject->getInternalObjCInstance(); + if (instance) + return instance->getObject(); + return nil; + } + + return [WebScriptObject scriptObjectForJSObject:toRef(object) originRootObject:originRootObject rootObject:rootObject]; + } + + if (value.isString()) { + ExecState* exec = rootObject->globalObject()->globalExec(); + const UString& u = asString(value)->value(exec); + return [NSString stringWithCharacters:u.characters() length:u.length()]; + } + + if (value.isNumber()) + return [NSNumber numberWithDouble:value.uncheckedGetNumber()]; + + 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: We'd like to add this, but we can't do that until this issue is resolved: +// http://bugs.webkit.org/show_bug.cgi?id=13129: presence of 'count' method on +// WebScriptObject breaks Democracy player. + +- (unsigned)count +{ + id length = [self valueForKey:@"length"]; + if (![length respondsToSelector:@selector(intValue)]) + return 0; + return [length intValue]; +} + +#endif + +- (id)objectAtIndex:(unsigned)index +{ + return [self webScriptValueAtIndex:index]; +} + +@end + +@implementation WebUndefined + ++ (id)allocWithZone:(NSZone *)unusedZone +{ + UNUSED_PARAM(unusedZone); + + static WebUndefined *sharedUndefined = 0; + if (!sharedUndefined) + sharedUndefined = [super allocWithZone:NULL]; + return sharedUndefined; +} + +- (NSString *)description +{ + return @"undefined"; +} + +- (id)initWithCoder:(NSCoder *)unusedCoder +{ + UNUSED_PARAM(unusedCoder); + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)unusedCoder +{ + UNUSED_PARAM(unusedCoder); +} + +- (id)copyWithZone:(NSZone *)unusedZone +{ + UNUSED_PARAM(unusedZone); + + return self; +} + +- (id)retain +{ + return self; +} + +- (void)release +{ +} + +- (NSUInteger)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 |