Home | History | Annotate | Download | only in objc
      1 /*
      2  * Copyright (C) 2004, 2006, 2007, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #import "config.h"
     27 #import "WebScriptObjectPrivate.h"
     28 
     29 #import "Bridge.h"
     30 #import "Console.h"
     31 #import "DOMInternal.h"
     32 #import "DOMWindow.h"
     33 #import "Frame.h"
     34 #import "JSDOMWindow.h"
     35 #import "JSDOMWindowCustom.h"
     36 #import "PlatformString.h"
     37 #import "StringSourceProvider.h"
     38 #import "WebCoreObjCExtras.h"
     39 #import "objc_instance.h"
     40 #import "runtime_object.h"
     41 #import "runtime_root.h"
     42 #import <JavaScriptCore/APICast.h>
     43 #import <interpreter/CallFrame.h>
     44 #import <runtime/InitializeThreading.h>
     45 #import <runtime/JSGlobalObject.h>
     46 #import <runtime/JSLock.h>
     47 #import <runtime/Completion.h>
     48 #import <runtime/Completion.h>
     49 
     50 #ifdef BUILDING_ON_TIGER
     51 typedef unsigned NSUInteger;
     52 #endif
     53 
     54 using namespace JSC;
     55 using namespace JSC::Bindings;
     56 using namespace WebCore;
     57 
     58 namespace WebCore {
     59 
     60 static NSMapTable* JSWrapperCache;
     61 
     62 NSObject* getJSWrapper(JSObject* impl)
     63 {
     64     if (!JSWrapperCache)
     65         return nil;
     66     return static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl));
     67 }
     68 
     69 void addJSWrapper(NSObject* wrapper, JSObject* impl)
     70 {
     71     if (!JSWrapperCache)
     72         JSWrapperCache = createWrapperCache();
     73     NSMapInsert(JSWrapperCache, impl, wrapper);
     74 }
     75 
     76 void removeJSWrapper(JSObject* impl)
     77 {
     78     if (!JSWrapperCache)
     79         return;
     80     NSMapRemove(JSWrapperCache, impl);
     81 }
     82 
     83 id createJSWrapper(JSC::JSObject* object, PassRefPtr<JSC::Bindings::RootObject> origin, PassRefPtr<JSC::Bindings::RootObject> root)
     84 {
     85     if (id wrapper = getJSWrapper(object))
     86         return [[wrapper retain] autorelease];
     87     return [[[WebScriptObject alloc] _initWithJSObject:object originRootObject:origin rootObject:root] autorelease];
     88 }
     89 
     90 static void addExceptionToConsole(ExecState* exec)
     91 {
     92     JSDOMWindow* window = asJSDOMWindow(exec->dynamicGlobalObject());
     93     if (!window || !exec->hadException())
     94         return;
     95     reportCurrentException(exec);
     96 }
     97 
     98 } // namespace WebCore
     99 
    100 @implementation WebScriptObjectPrivate
    101 
    102 @end
    103 
    104 @implementation WebScriptObject
    105 
    106 + (void)initialize
    107 {
    108     JSC::initializeThreading();
    109 #ifndef BUILDING_ON_TIGER
    110     WebCoreObjCFinalizeOnMainThread(self);
    111 #endif
    112 }
    113 
    114 + (id)scriptObjectForJSObject:(JSObjectRef)jsObject originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject
    115 {
    116     if (id domWrapper = createDOMWrapper(toJS(jsObject), originRootObject, rootObject))
    117         return domWrapper;
    118 
    119     return WebCore::createJSWrapper(toJS(jsObject), originRootObject, rootObject);
    120 }
    121 
    122 static void _didExecute(WebScriptObject *obj)
    123 {
    124     ASSERT(JSLock::lockCount() > 0);
    125 
    126     RootObject* root = [obj _rootObject];
    127     if (!root)
    128         return;
    129 
    130     ExecState* exec = root->globalObject()->globalExec();
    131     KJSDidExecuteFunctionPtr func = Instance::didExecuteFunction();
    132     if (func)
    133         func(exec, root->globalObject());
    134 }
    135 
    136 - (void)_setImp:(JSObject*)imp originRootObject:(PassRefPtr<RootObject>)originRootObject rootObject:(PassRefPtr<RootObject>)rootObject
    137 {
    138     // This function should only be called once, as a (possibly lazy) initializer.
    139     ASSERT(!_private->imp);
    140     ASSERT(!_private->rootObject);
    141     ASSERT(!_private->originRootObject);
    142     ASSERT(imp);
    143 
    144     _private->imp = imp;
    145     _private->rootObject = rootObject.releaseRef();
    146     _private->originRootObject = originRootObject.releaseRef();
    147 
    148     WebCore::addJSWrapper(self, imp);
    149 
    150     if (_private->rootObject)
    151         _private->rootObject->gcProtect(imp);
    152 }
    153 
    154 - (void)_setOriginRootObject:(PassRefPtr<RootObject>)originRootObject andRootObject:(PassRefPtr<RootObject>)rootObject
    155 {
    156     ASSERT(_private->imp);
    157 
    158     if (rootObject)
    159         rootObject->gcProtect(_private->imp);
    160 
    161     if (_private->rootObject && _private->rootObject->isValid())
    162         _private->rootObject->gcUnprotect(_private->imp);
    163 
    164     if (_private->rootObject)
    165         _private->rootObject->deref();
    166 
    167     if (_private->originRootObject)
    168         _private->originRootObject->deref();
    169 
    170     _private->rootObject = rootObject.releaseRef();
    171     _private->originRootObject = originRootObject.releaseRef();
    172 }
    173 
    174 - (id)_initWithJSObject:(JSC::JSObject*)imp originRootObject:(PassRefPtr<JSC::Bindings::RootObject>)originRootObject rootObject:(PassRefPtr<JSC::Bindings::RootObject>)rootObject
    175 {
    176     ASSERT(imp);
    177 
    178     self = [super init];
    179     _private = [[WebScriptObjectPrivate alloc] init];
    180     [self _setImp:imp originRootObject:originRootObject rootObject:rootObject];
    181 
    182     return self;
    183 }
    184 
    185 - (JSObject*)_imp
    186 {
    187     // Associate the WebScriptObject with the JS wrapper for the ObjC DOM wrapper.
    188     // This is done on lazily, on demand.
    189     if (!_private->imp && _private->isCreatedByDOMWrapper)
    190         [self _initializeScriptDOMNodeImp];
    191     return [self _rootObject] ? _private->imp : 0;
    192 }
    193 
    194 - (BOOL)_hasImp
    195 {
    196     return _private->imp != nil;
    197 }
    198 
    199 // Node that DOMNode overrides this method. So you should almost always
    200 // use this method call instead of _private->rootObject directly.
    201 - (RootObject*)_rootObject
    202 {
    203     return _private->rootObject && _private->rootObject->isValid() ? _private->rootObject : 0;
    204 }
    205 
    206 - (RootObject *)_originRootObject
    207 {
    208     return _private->originRootObject && _private->originRootObject->isValid() ? _private->originRootObject : 0;
    209 }
    210 
    211 - (BOOL)_isSafeScript
    212 {
    213     RootObject *root = [self _rootObject];
    214     if (!root)
    215         return false;
    216 
    217     if (!_private->originRootObject)
    218         return true;
    219 
    220     if (!_private->originRootObject->isValid())
    221         return false;
    222 
    223     return root->globalObject()->allowsAccessFrom(_private->originRootObject->globalObject());
    224 }
    225 
    226 - (void)dealloc
    227 {
    228     if (WebCoreObjCScheduleDeallocateOnMainThread([WebScriptObject class], self))
    229         return;
    230 
    231     if (_private->imp)
    232         WebCore::removeJSWrapper(_private->imp);
    233 
    234     if (_private->rootObject && _private->rootObject->isValid())
    235         _private->rootObject->gcUnprotect(_private->imp);
    236 
    237     if (_private->rootObject)
    238         _private->rootObject->deref();
    239 
    240     if (_private->originRootObject)
    241         _private->originRootObject->deref();
    242 
    243     [_private release];
    244 
    245     [super dealloc];
    246 }
    247 
    248 - (void)finalize
    249 {
    250     if (_private->rootObject && _private->rootObject->isValid())
    251         _private->rootObject->gcUnprotect(_private->imp);
    252 
    253     if (_private->rootObject)
    254         _private->rootObject->deref();
    255 
    256     if (_private->originRootObject)
    257         _private->originRootObject->deref();
    258 
    259     [super finalize];
    260 }
    261 
    262 + (BOOL)throwException:(NSString *)exceptionMessage
    263 {
    264     ObjcInstance::setGlobalException(exceptionMessage);
    265     return YES;
    266 }
    267 
    268 static void getListFromNSArray(ExecState *exec, NSArray *array, RootObject* rootObject, MarkedArgumentBuffer& aList)
    269 {
    270     int i, numObjects = array ? [array count] : 0;
    271 
    272     for (i = 0; i < numObjects; i++) {
    273         id anObject = [array objectAtIndex:i];
    274         aList.append(convertObjcValueToValue(exec, &anObject, ObjcObjectType, rootObject));
    275     }
    276 }
    277 
    278 - (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args
    279 {
    280     if (![self _isSafeScript])
    281         return nil;
    282 
    283     JSLock lock(SilenceAssertionsOnly);
    284 
    285     // Look up the function object.
    286     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    287     ASSERT(!exec->hadException());
    288 
    289     JSValue function = [self _imp]->get(exec, Identifier(exec, String(name)));
    290     CallData callData;
    291     CallType callType = function.getCallData(callData);
    292     if (callType == CallTypeNone)
    293         return nil;
    294 
    295     MarkedArgumentBuffer argList;
    296     getListFromNSArray(exec, args, [self _rootObject], argList);
    297 
    298     if (![self _isSafeScript])
    299         return nil;
    300 
    301     [self _rootObject]->globalObject()->globalData()->timeoutChecker.start();
    302     JSValue result = JSC::call(exec, function, callType, callData, [self _imp], argList);
    303     [self _rootObject]->globalObject()->globalData()->timeoutChecker.stop();
    304 
    305     if (exec->hadException()) {
    306         addExceptionToConsole(exec);
    307         result = jsUndefined();
    308         exec->clearException();
    309     }
    310 
    311     // Convert and return the result of the function call.
    312     id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
    313 
    314     _didExecute(self);
    315 
    316     return resultObj;
    317 }
    318 
    319 - (id)evaluateWebScript:(NSString *)script
    320 {
    321     if (![self _isSafeScript])
    322         return nil;
    323 
    324     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    325     ASSERT(!exec->hadException());
    326 
    327     JSValue result;
    328     JSLock lock(SilenceAssertionsOnly);
    329 
    330     [self _rootObject]->globalObject()->globalData()->timeoutChecker.start();
    331     Completion completion = JSC::evaluate([self _rootObject]->globalObject()->globalExec(), [self _rootObject]->globalObject()->globalScopeChain(), makeSource(String(script)), JSC::JSValue());
    332     [self _rootObject]->globalObject()->globalData()->timeoutChecker.stop();
    333     ComplType type = completion.complType();
    334 
    335     if (type == Normal) {
    336         result = completion.value();
    337         if (!result)
    338             result = jsUndefined();
    339     } else
    340         result = jsUndefined();
    341 
    342     if (exec->hadException()) {
    343         addExceptionToConsole(exec);
    344         result = jsUndefined();
    345         exec->clearException();
    346     }
    347 
    348     id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
    349 
    350     _didExecute(self);
    351 
    352     return resultObj;
    353 }
    354 
    355 - (void)setValue:(id)value forKey:(NSString *)key
    356 {
    357     if (![self _isSafeScript])
    358         return;
    359 
    360     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    361     ASSERT(!exec->hadException());
    362 
    363     JSLock lock(SilenceAssertionsOnly);
    364 
    365     PutPropertySlot slot;
    366     [self _imp]->put(exec, Identifier(exec, String(key)), convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject]), slot);
    367 
    368     if (exec->hadException()) {
    369         addExceptionToConsole(exec);
    370         exec->clearException();
    371     }
    372 
    373     _didExecute(self);
    374 }
    375 
    376 - (id)valueForKey:(NSString *)key
    377 {
    378     if (![self _isSafeScript])
    379         return nil;
    380 
    381     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    382     ASSERT(!exec->hadException());
    383 
    384     id resultObj;
    385     {
    386         // Need to scope this lock to ensure that we release the lock before calling
    387         // [super valueForKey:key] which might throw an exception and bypass the JSLock destructor,
    388         // leaving the lock permanently held
    389         JSLock lock(SilenceAssertionsOnly);
    390 
    391         JSValue result = [self _imp]->get(exec, Identifier(exec, String(key)));
    392 
    393         if (exec->hadException()) {
    394             addExceptionToConsole(exec);
    395             result = jsUndefined();
    396             exec->clearException();
    397         }
    398 
    399         resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
    400     }
    401 
    402     if ([resultObj isKindOfClass:[WebUndefined class]])
    403         resultObj = [super valueForKey:key];    // defaults to throwing an exception
    404 
    405     JSLock lock(SilenceAssertionsOnly);
    406     _didExecute(self);
    407 
    408     return resultObj;
    409 }
    410 
    411 - (void)removeWebScriptKey:(NSString *)key
    412 {
    413     if (![self _isSafeScript])
    414         return;
    415 
    416     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    417     ASSERT(!exec->hadException());
    418 
    419     JSLock lock(SilenceAssertionsOnly);
    420     [self _imp]->deleteProperty(exec, Identifier(exec, String(key)));
    421 
    422     if (exec->hadException()) {
    423         addExceptionToConsole(exec);
    424         exec->clearException();
    425     }
    426 
    427     _didExecute(self);
    428 }
    429 
    430 - (NSString *)stringRepresentation
    431 {
    432     if (![self _isSafeScript]) {
    433         // This is a workaround for a gcc 3.3 internal compiler error.
    434         return @"Undefined";
    435     }
    436 
    437     JSLock lock(SilenceAssertionsOnly);
    438     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    439 
    440     id result = convertValueToObjcValue(exec, [self _imp], ObjcObjectType).objectValue;
    441 
    442     NSString *description = [result description];
    443 
    444     _didExecute(self);
    445 
    446     return description;
    447 }
    448 
    449 - (id)webScriptValueAtIndex:(unsigned)index
    450 {
    451     if (![self _isSafeScript])
    452         return nil;
    453 
    454     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    455     ASSERT(!exec->hadException());
    456 
    457     JSLock lock(SilenceAssertionsOnly);
    458     JSValue result = [self _imp]->get(exec, index);
    459 
    460     if (exec->hadException()) {
    461         addExceptionToConsole(exec);
    462         result = jsUndefined();
    463         exec->clearException();
    464     }
    465 
    466     id resultObj = [WebScriptObject _convertValueToObjcValue:result originRootObject:[self _originRootObject] rootObject:[self _rootObject]];
    467 
    468     _didExecute(self);
    469 
    470     return resultObj;
    471 }
    472 
    473 - (void)setWebScriptValueAtIndex:(unsigned)index value:(id)value
    474 {
    475     if (![self _isSafeScript])
    476         return;
    477 
    478     ExecState* exec = [self _rootObject]->globalObject()->globalExec();
    479     ASSERT(!exec->hadException());
    480 
    481     JSLock lock(SilenceAssertionsOnly);
    482     [self _imp]->put(exec, index, convertObjcValueToValue(exec, &value, ObjcObjectType, [self _rootObject]));
    483 
    484     if (exec->hadException()) {
    485         addExceptionToConsole(exec);
    486         exec->clearException();
    487     }
    488 
    489     _didExecute(self);
    490 }
    491 
    492 - (void)setException:(NSString *)description
    493 {
    494     if (![self _rootObject])
    495         return;
    496     ObjcInstance::setGlobalException(description, [self _rootObject]->globalObject());
    497 }
    498 
    499 - (JSObjectRef)JSObject
    500 {
    501     if (![self _isSafeScript])
    502         return NULL;
    503 
    504     return toRef([self _imp]);
    505 }
    506 
    507 + (id)_convertValueToObjcValue:(JSValue)value originRootObject:(RootObject*)originRootObject rootObject:(RootObject*)rootObject
    508 {
    509     if (value.isObject()) {
    510         JSObject* object = asObject(value);
    511         ExecState* exec = rootObject->globalObject()->globalExec();
    512         JSLock lock(SilenceAssertionsOnly);
    513 
    514         if (object->classInfo() != &RuntimeObjectImp::s_info) {
    515             JSValue runtimeObject = object->get(exec, Identifier(exec, "__apple_runtime_object"));
    516             if (runtimeObject && runtimeObject.isObject())
    517                 object = asObject(runtimeObject);
    518         }
    519 
    520         if (object->classInfo() == &RuntimeObjectImp::s_info) {
    521             RuntimeObjectImp* imp = static_cast<RuntimeObjectImp*>(object);
    522             ObjcInstance *instance = static_cast<ObjcInstance*>(imp->getInternalInstance());
    523             if (instance)
    524                 return instance->getObject();
    525             return nil;
    526         }
    527 
    528         return [WebScriptObject scriptObjectForJSObject:toRef(object) originRootObject:originRootObject rootObject:rootObject];
    529     }
    530 
    531     if (value.isString()) {
    532         ExecState* exec = rootObject->globalObject()->globalExec();
    533         const UString& u = asString(value)->value(exec);
    534         return [NSString stringWithCharacters:u.data() length:u.size()];
    535     }
    536 
    537     if (value.isNumber())
    538         return [NSNumber numberWithDouble:value.uncheckedGetNumber()];
    539 
    540     if (value.isBoolean())
    541         return [NSNumber numberWithBool:value.getBoolean()];
    542 
    543     if (value.isUndefined())
    544         return [WebUndefined undefined];
    545 
    546     // jsNull is not returned as NSNull because existing applications do not expect
    547     // that return value. Return as nil for compatibility. <rdar://problem/4651318> <rdar://problem/4701626>
    548     // Other types (e.g., UnspecifiedType) also return as nil.
    549     return nil;
    550 }
    551 
    552 @end
    553 
    554 @interface WebScriptObject (WebKitCocoaBindings)
    555 
    556 - (id)objectAtIndex:(unsigned)index;
    557 
    558 @end
    559 
    560 @implementation WebScriptObject (WebKitCocoaBindings)
    561 
    562 #if 0
    563 
    564 // FIXME: We'd like to add this, but we can't do that until this issue is resolved:
    565 // http://bugs.webkit.org/show_bug.cgi?id=13129: presence of 'count' method on
    566 // WebScriptObject breaks Democracy player.
    567 
    568 - (unsigned)count
    569 {
    570     id length = [self valueForKey:@"length"];
    571     if (![length respondsToSelector:@selector(intValue)])
    572         return 0;
    573     return [length intValue];
    574 }
    575 
    576 #endif
    577 
    578 - (id)objectAtIndex:(unsigned)index
    579 {
    580     return [self webScriptValueAtIndex:index];
    581 }
    582 
    583 @end
    584 
    585 @implementation WebUndefined
    586 
    587 + (id)allocWithZone:(NSZone *)unusedZone
    588 {
    589     UNUSED_PARAM(unusedZone);
    590 
    591     static WebUndefined *sharedUndefined = 0;
    592     if (!sharedUndefined)
    593         sharedUndefined = [super allocWithZone:NULL];
    594     return sharedUndefined;
    595 }
    596 
    597 - (NSString *)description
    598 {
    599     return @"undefined";
    600 }
    601 
    602 - (id)initWithCoder:(NSCoder *)unusedCoder
    603 {
    604     UNUSED_PARAM(unusedCoder);
    605 
    606     return self;
    607 }
    608 
    609 - (void)encodeWithCoder:(NSCoder *)unusedCoder
    610 {
    611     UNUSED_PARAM(unusedCoder);
    612 }
    613 
    614 - (id)copyWithZone:(NSZone *)unusedZone
    615 {
    616     UNUSED_PARAM(unusedZone);
    617 
    618     return self;
    619 }
    620 
    621 - (id)retain
    622 {
    623     return self;
    624 }
    625 
    626 - (void)release
    627 {
    628 }
    629 
    630 - (NSUInteger)retainCount
    631 {
    632     return UINT_MAX;
    633 }
    634 
    635 - (id)autorelease
    636 {
    637     return self;
    638 }
    639 
    640 - (void)dealloc
    641 {
    642     ASSERT(false);
    643     return;
    644     [super dealloc]; // make -Wdealloc-check happy
    645 }
    646 
    647 + (WebUndefined *)undefined
    648 {
    649     return [WebUndefined allocWithZone:NULL];
    650 }
    651 
    652 @end
    653