d2ef25d7f1c07f1d38f6955d3b142cbf30a338c7
[WebKit-https.git] / Source / WebCore / bindings / objc / WebScriptObject.mm
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