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