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
56typedef unsigned NSUInteger;
57#endif
58
59using namespace JSC;
60using namespace JSC::Bindings;
61using namespace WebCore;
62
63namespace WebCore {
64
65static NSMapTable* JSWrapperCache;
66
67NSObject* getJSWrapper(JSObject* impl)
68{
69    if (!JSWrapperCache)
70        return nil;
71    return static_cast<NSObject*>(NSMapGet(JSWrapperCache, impl));
72}
73
74void addJSWrapper(NSObject* wrapper, JSObject* impl)
75{
76    if (!JSWrapperCache)
77        JSWrapperCache = createWrapperCache();
78    NSMapInsert(JSWrapperCache, impl, wrapper);
79}
80
81void removeJSWrapper(JSObject* impl)
82{
83    if (!JSWrapperCache)
84        return;
85    NSMapRemove(JSWrapperCache, impl);
86}
87
88id 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
95static 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
128static 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
274static 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
679