| /* |
| * 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/context.h> |
| #import <JavaScriptCore/objc_instance.h> |
| #import <JavaScriptCore/runtime_object.h> |
| #import <JavaScriptCore/APICast.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->interpreter()->globalExec(); |
| KJSDidExecuteFunctionPtr func = Instance::didExecuteFunction(); |
| if (func) |
| func(exec, static_cast<JSObject*>(root->interpreter()->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 _private->originRootObject->interpreter()->isSafeScript(root->interpreter()); |
| } |
| |
| - (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; |
| |
| Interpreter *first, *interp = Interpreter::firstInterpreter(); |
| |
| // 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. |
| first = interp; |
| do { |
| if (!interp) |
| return NO; |
| |
| // If the interpreter has a context, we set the exception. |
| if (interp->context()) { |
| ExecState *exec = interp->context()->execState(); |
| |
| if (exec) { |
| throwError(exec, GeneralError, exceptionMessage); |
| return YES; |
| } |
| } |
| interp = interp->nextInterpreter(); |
| } while (interp != first); |
| |
| return NO; |
| } |
| |
| static List listFromNSArray(ExecState *exec, NSArray *array, RootObject* rootObject) |
| { |
| int i, numObjects = array ? [array count] : 0; |
| List aList; |
| |
| for (i = 0; i < numObjects; i++) { |
| id anObject = [array objectAtIndex:i]; |
| aList.append(convertObjcValueToValue(exec, &anObject, ObjcObjectType, rootObject)); |
| } |
| return aList; |
| } |
| |
| - (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args |
| { |
| if (![self _isSafeScript]) |
| return nil; |
| |
| // Look up the function object. |
| ExecState* exec = [self _rootObject]->interpreter()->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 = listFromNSArray(exec, args, [self _rootObject]); |
| |
| if (![self _isSafeScript]) |
| return nil; |
| |
| [self _rootObject]->interpreter()->startTimeoutCheck(); |
| JSValue *result = funcImp->call(exec, [self _imp], argList); |
| [self _rootObject]->interpreter()->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]->interpreter()->globalExec(); |
| ASSERT(!exec->hadException()); |
| |
| JSValue *result; |
| JSLock lock; |
| |
| [self _rootObject]->interpreter()->startTimeoutCheck(); |
| Completion completion = [self _rootObject]->interpreter()->evaluate(UString(), 0, String(script)); |
| [self _rootObject]->interpreter()->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]->interpreter()->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]->interpreter()->globalExec(); |
| ASSERT(!exec->hadException()); |
| |
| JSLock lock; |
| JSValue *result = [self _imp]->get(exec, String(key)); |
| |
| if (exec->hadException()) { |
| LOG_EXCEPTION(exec); |
| result = jsUndefined(); |
| exec->clearException(); |
| } |
| |
| id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]]; |
| if ([resultObj isKindOfClass:[WebUndefined class]]) |
| resultObj = [super valueForKey:key]; // defaults to throwing an exception |
| |
| _didExecute(self); |
| |
| return resultObj; |
| } |
| |
| - (void)removeWebScriptKey:(NSString *)key |
| { |
| if (![self _isSafeScript]) |
| return; |
| |
| ExecState* exec = [self _rootObject]->interpreter()->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]->interpreter()->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]->interpreter()->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]->interpreter()->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; |
| |
| if ([self _rootObject]->interpreter()->context()) { |
| ExecState *exec = [self _rootObject]->interpreter()->context()->execState(); |
| |
| ASSERT(exec); |
| throwError(exec, GeneralError, description); |
| } else |
| throwError([self _rootObject]->interpreter()->globalExec(), 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); |
| Interpreter* interpreter = rootObject->interpreter(); |
| ExecState *exec = interpreter->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 |
| |