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