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