summaryrefslogtreecommitdiffstats
path: root/WebCore/bindings/objc/WebScriptObject.mm
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/bindings/objc/WebScriptObject.mm')
-rw-r--r--WebCore/bindings/objc/WebScriptObject.mm655
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
+