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 56typedef unsigned NSUInteger; 57#endif 58 59using namespace JSC; 60using namespace JSC::Bindings; 61using namespace WebCore; 62 63namespace WebCore { 64 65static NSMapTable* JSWrapperCache; 66 67NSObject* getJSWrapper(JSObject* impl) 68{ 69 if (!JSWrapperCache) 70 return nil; 71 return static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl)); 72} 73 74void addJSWrapper(NSObject* wrapper, JSObject* impl) 75{ 76 if (!JSWrapperCache) 77 JSWrapperCache = createWrapperCache(); 78 NSMapInsert(JSWrapperCache, impl, wrapper); 79} 80 81void removeJSWrapper(JSObject* impl) 82{ 83 if (!JSWrapperCache) 84 return; 85 NSMapRemove(JSWrapperCache, impl); 86} 87 88id 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 95static 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 128static 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 274static 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