1/*
2 * Copyright (C) 2014 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 INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO_TRACK) && ENABLE(DATACUE_VALUE)
29#include "SerializedPlatformRepresentationMac.h"
30
31#import "JSDOMBinding.h"
32#import "SoftLinking.h"
33#import <objc/runtime.h>
34#import <runtime/ArrayBuffer.h>
35#import <runtime/JSArrayBuffer.h>
36#import <AVFoundation/AVFoundation.h>
37#import <CoreMedia/CoreMedia.h>
38#import <Foundation/NSString.h>
39#import <JavaScriptCore/APICast.h>
40#import <JavaScriptCore/JavaScriptCore.h>
41#import <JavaScriptCore/JSContextRef.h>
42#import <JavaScriptCore/JSObjectRef.h>
43#import <wtf/text/Base64.h>
44
45SOFT_LINK_FRAMEWORK_OPTIONAL(CoreMedia)
46SOFT_LINK(CoreMedia, CMTimeCopyAsDictionary, CFDictionaryRef, (CMTime time, CFAllocatorRef allocator), (time, allocator))
47
48typedef AVMetadataItem AVMetadataItemType;
49SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
50SOFT_LINK_CLASS(AVFoundation, AVMetadataItem)
51#define AVMetadataItem getAVMetadataItemClass()
52
53
54namespace WebCore {
55
56#if JSC_OBJC_API_ENABLED
57static JSValue *jsValueWithDataInContext(NSData *, JSContext *);
58static JSValue *jsValueWithArrayInContext(NSArray *, JSContext *);
59static JSValue *jsValueWithDictionaryInContext(NSDictionary *, JSContext *);
60static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *, JSContext *);
61static JSValue *jsValueWithValueInContext(id, JSContext *);
62#endif
63
64SerializedPlatformRepresentationMac::SerializedPlatformRepresentationMac(id nativeValue)
65    : SerializedPlatformRepresentation()
66    , m_nativeValue(nativeValue)
67{
68}
69
70SerializedPlatformRepresentationMac::~SerializedPlatformRepresentationMac()
71{
72}
73
74PassRef<SerializedPlatformRepresentation> SerializedPlatformRepresentationMac::create(id nativeValue)
75{
76    return adoptRef(*new SerializedPlatformRepresentationMac(nativeValue));
77}
78
79PassRefPtr<ArrayBuffer> SerializedPlatformRepresentationMac::data() const
80{
81    return nullptr;
82}
83
84JSC::JSValue SerializedPlatformRepresentationMac::deserialize(JSC::ExecState* exec) const
85{
86#if JSC_OBJC_API_ENABLED
87    if (!m_nativeValue)
88        return JSC::jsNull();
89
90    JSGlobalContextRef jsGlobalContextRef = toGlobalRef(exec->lexicalGlobalObject()->globalExec());
91    JSContext *jsContext = [JSContext contextWithJSGlobalContextRef:jsGlobalContextRef];
92    JSValue *serializedValue = jsValueWithValueInContext(m_nativeValue.get(), jsContext);
93
94    return toJS(exec, [serializedValue JSValueRef]);
95#else
96    UNUSED_PARAM(exec);
97    return JSC::jsNull();
98#endif
99}
100
101bool SerializedPlatformRepresentationMac::isEqual(const SerializedPlatformRepresentation& other) const
102{
103    if (other.platformType() != SerializedPlatformRepresentation::ObjC)
104        return false;
105
106    const SerializedPlatformRepresentationMac* otherObjC = toSerializedPlatformRepresentationMac(&other);
107
108    if (!m_nativeValue || !otherObjC->nativeValue())
109        return false;
110
111    return [m_nativeValue.get() isEqual:otherObjC->nativeValue()];
112}
113
114SerializedPlatformRepresentationMac* toSerializedPlatformRepresentationMac(SerializedPlatformRepresentation* rep)
115{
116    return const_cast<SerializedPlatformRepresentationMac*>(toSerializedPlatformRepresentationMac(const_cast<const SerializedPlatformRepresentation*>(rep)));
117}
118
119const SerializedPlatformRepresentationMac* toSerializedPlatformRepresentationMac(const SerializedPlatformRepresentation* rep)
120{
121    ASSERT_WITH_SECURITY_IMPLICATION(rep->platformType() == SerializedPlatformRepresentation::ObjC);
122    return static_cast<const SerializedPlatformRepresentationMac*>(rep);
123}
124
125#if JSC_OBJC_API_ENABLED
126static JSValue *jsValueWithValueInContext(id value, JSContext *context)
127{
128    if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])
129        return [JSValue valueWithObject:value inContext:context];
130
131    if ([value isKindOfClass:[NSLocale class]])
132        return [JSValue valueWithObject:[value localeIdentifier] inContext:context];
133
134    if ([value isKindOfClass:[NSDictionary class]])
135        return jsValueWithDictionaryInContext(value, context);
136
137    if ([value isKindOfClass:[NSArray class]])
138        return jsValueWithArrayInContext(value, context);
139
140    if ([value isKindOfClass:[NSData class]])
141        return jsValueWithDataInContext(value, context);
142
143    if ([value isKindOfClass:[AVMetadataItem class]])
144        return jsValueWithAVMetadataItemInContext(value, context);
145
146    return nil;
147}
148
149static JSValue *jsValueWithDataInContext(NSData *data, JSContext *context)
150{
151    RefPtr<ArrayBuffer> dataArray = ArrayBuffer::create([data bytes], [data length]);
152
153    JSC::ExecState* exec = toJS([context JSGlobalContextRef]);
154    JSC::JSValue array = toJS(exec, JSC::jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), dataArray.get());
155
156    return [JSValue valueWithJSValueRef:toRef(exec, array) inContext:context];
157}
158
159static JSValue *jsValueWithArrayInContext(NSArray *array, JSContext *context)
160{
161    JSValueRef exception = 0;
162    JSValue *result = [JSValue valueWithNewArrayInContext:context];
163    JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
164    if (exception)
165        return [JSValue valueWithUndefinedInContext:context];
166
167    NSUInteger count = [array count];
168    for (NSUInteger i = 0; i < count; ++i) {
169        JSValue *value = jsValueWithValueInContext([array objectAtIndex:i], context);
170        if (!value)
171            continue;
172
173        JSObjectSetPropertyAtIndex([context JSGlobalContextRef], resultObject, (unsigned)i, [value JSValueRef], &exception);
174        if (exception)
175            continue;
176    }
177
178    return result;
179}
180
181static JSValue *jsValueWithDictionaryInContext(NSDictionary *dictionary, JSContext *context)
182{
183    JSValueRef exception = 0;
184    JSValue *result = [JSValue valueWithNewObjectInContext:context];
185    JSObjectRef resultObject = JSValueToObject([context JSGlobalContextRef], [result JSValueRef], &exception);
186    if (exception)
187        return [JSValue valueWithUndefinedInContext:context];
188
189    for (id key in [dictionary keyEnumerator]) {
190        if (![key isKindOfClass:[NSString class]])
191            continue;
192
193        JSValue *value = jsValueWithValueInContext([dictionary objectForKey:key], context);
194        if (!value)
195            continue;
196
197        JSStringRef name = JSStringCreateWithCFString((CFStringRef)key);
198        JSObjectSetProperty([context JSGlobalContextRef], resultObject, name, [value JSValueRef], 0, &exception);
199        if (exception)
200            continue;
201    }
202
203    return result;
204}
205
206static JSValue *jsValueWithAVMetadataItemInContext(AVMetadataItemType *item, JSContext *context)
207{
208    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
209
210    NSDictionary *extras = [item extraAttributes];
211    for (id key in [extras keyEnumerator]) {
212        if (![key isKindOfClass:[NSString class]])
213            continue;
214        id value = [extras objectForKey:key];
215        NSString *keyString = key;
216
217        if ([key isEqualToString:@"MIMEtype"])
218            keyString = @"type";
219        else if ([key isEqualToString:@"dataTypeNamespace"] || [key isEqualToString:@"pictureType"])
220            continue;
221        else if ([key isEqualToString:@"dataType"]) {
222            id dataTypeNamespace = [extras objectForKey:@"dataTypeNamespace"];
223            if (!dataTypeNamespace || ![dataTypeNamespace isKindOfClass:[NSString class]] || ![dataTypeNamespace isEqualToString:@"org.iana.media-type"])
224                continue;
225            keyString = @"type";
226        } else if ([value isKindOfClass:[NSString class]]) {
227            if (![value length])
228                continue;
229            keyString = [key lowercaseString];
230        }
231
232        [dictionary setObject:value forKey:keyString];
233    }
234
235    if (item.key)
236        [dictionary setObject:item.key forKey:@"key"];
237
238    if (item.locale)
239        [dictionary setObject:item.locale forKey:@"locale"];
240
241    if (item.value)
242        [dictionary setObject:item.value forKey:@"data"];
243
244    return jsValueWithDictionaryInContext(dictionary, context);
245}
246#endif
247
248} // namespace WebCore
249
250#endif
251