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