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