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 "Console.h" 30#import "DOMInternal.h" 31#import "DOMWindow.h" 32#import "Frame.h" 33#import "JSDOMWindow.h" 34#import "JSDOMWindowCustom.h" 35#import "PlatformString.h" 36#import "StringSourceProvider.h" 37#import "WebCoreObjCExtras.h" 38#import "objc_instance.h" 39#import "runtime.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 51typedef unsigned NSUInteger; 52#endif 53 54using namespace JSC; 55using namespace JSC::Bindings; 56using namespace WebCore; 57 58namespace WebCore { 59 60static NSMapTable* JSWrapperCache; 61 62NSObject* getJSWrapper(JSObject* impl) 63{ 64 if (!JSWrapperCache) 65 return nil; 66 return static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl)); 67} 68 69void addJSWrapper(NSObject* wrapper, JSObject* impl) 70{ 71 if (!JSWrapperCache) 72 JSWrapperCache = createWrapperCache(); 73 NSMapInsert(JSWrapperCache, impl, wrapper); 74} 75 76void removeJSWrapper(JSObject* impl) 77{ 78 if (!JSWrapperCache) 79 return; 80 NSMapRemove(JSWrapperCache, impl); 81} 82 83id 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 90static 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 122static 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 268static 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 = 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))); 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 const UString& u = asString(value)->value(); 533 return [NSString stringWithCharacters:u.data() length:u.size()]; 534 } 535 536 if (value.isNumber()) 537 return [NSNumber numberWithDouble:value.uncheckedGetNumber()]; 538 539 if (value.isBoolean()) 540 return [NSNumber numberWithBool:value.getBoolean()]; 541 542 if (value.isUndefined()) 543 return [WebUndefined undefined]; 544 545 // jsNull is not returned as NSNull because existing applications do not expect 546 // that return value. Return as nil for compatibility. <rdar://problem/4651318> <rdar://problem/4701626> 547 // Other types (e.g., UnspecifiedType) also return as nil. 548 return nil; 549} 550 551@end 552 553@interface WebScriptObject (WebKitCocoaBindings) 554 555- (id)objectAtIndex:(unsigned)index; 556 557@end 558 559@implementation WebScriptObject (WebKitCocoaBindings) 560 561#if 0 562 563// FIXME: We'd like to add this, but we can't do that until this issue is resolved: 564// http://bugs.webkit.org/show_bug.cgi?id=13129: presence of 'count' method on 565// WebScriptObject breaks Democracy player. 566 567- (unsigned)count 568{ 569 id length = [self valueForKey:@"length"]; 570 if (![length respondsToSelector:@selector(intValue)]) 571 return 0; 572 return [length intValue]; 573} 574 575#endif 576 577- (id)objectAtIndex:(unsigned)index 578{ 579 return [self webScriptValueAtIndex:index]; 580} 581 582@end 583 584@implementation WebUndefined 585 586+ (id)allocWithZone:(NSZone *)unusedZone 587{ 588 UNUSED_PARAM(unusedZone); 589 590 static WebUndefined *sharedUndefined = 0; 591 if (!sharedUndefined) 592 sharedUndefined = [super allocWithZone:NULL]; 593 return sharedUndefined; 594} 595 596- (NSString *)description 597{ 598 return @"undefined"; 599} 600 601- (id)initWithCoder:(NSCoder *)unusedCoder 602{ 603 UNUSED_PARAM(unusedCoder); 604 605 return self; 606} 607 608- (void)encodeWithCoder:(NSCoder *)unusedCoder 609{ 610 UNUSED_PARAM(unusedCoder); 611} 612 613- (id)copyWithZone:(NSZone *)unusedZone 614{ 615 UNUSED_PARAM(unusedZone); 616 617 return self; 618} 619 620- (id)retain 621{ 622 return self; 623} 624 625- (void)release 626{ 627} 628 629- (NSUInteger)retainCount 630{ 631 return UINT_MAX; 632} 633 634- (id)autorelease 635{ 636 return self; 637} 638 639- (void)dealloc 640{ 641 ASSERT(false); 642 return; 643 [super dealloc]; // make -Wdealloc-check happy 644} 645 646+ (WebUndefined *)undefined 647{ 648 return [WebUndefined allocWithZone:NULL]; 649} 650 651@end 652