1/*
2 * Copyright (C) 2006, 2007, 2008, 2010 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "WebElementDictionary.h"
30
31#import "DOMNodeInternal.h"
32#import "WebDOMOperations.h"
33#import "WebFrame.h"
34#import "WebFrameInternal.h"
35#import "WebKitLogging.h"
36#import "WebTypesInternal.h"
37#import "WebView.h"
38#import "WebViewPrivate.h"
39#import <WebCore/Frame.h>
40#import <WebCore/HitTestResult.h>
41#import <WebCore/Image.h>
42#import <WebCore/WebCoreObjCExtras.h>
43#import <WebKit/DOMCore.h>
44#import <WebKit/DOMExtensions.h>
45#import <runtime/InitializeThreading.h>
46#import <wtf/Threading.h>
47
48using namespace WebCore;
49
50static CFMutableDictionaryRef lookupTable = NULL;
51
52static void addLookupKey(NSString *key, SEL selector)
53{
54    CFDictionaryAddValue(lookupTable, key, selector);
55}
56
57static void cacheValueForKey(const void *key, const void *value, void *self)
58{
59    // calling objectForKey will cache the value in our _cache dictionary
60    [(WebElementDictionary *)self objectForKey:(NSString *)key];
61}
62
63@implementation WebElementDictionary
64
65+ (void)initialize
66{
67    JSC::initializeThreading();
68    WTF::initializeMainThreadToProcessMainThread();
69#ifndef BUILDING_ON_TIGER
70    WebCoreObjCFinalizeOnMainThread(self);
71#endif
72}
73
74+ (void)initializeLookupTable
75{
76    if (lookupTable)
77        return;
78
79    lookupTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL);
80
81    addLookupKey(WebElementDOMNodeKey, @selector(_domNode));
82    addLookupKey(WebElementFrameKey, @selector(_webFrame));
83    addLookupKey(WebElementImageAltStringKey, @selector(_altDisplayString));
84    addLookupKey(WebElementImageKey, @selector(_image));
85    addLookupKey(WebElementImageRectKey, @selector(_imageRect));
86    addLookupKey(WebElementImageURLKey, @selector(_absoluteImageURL));
87    addLookupKey(WebElementIsSelectedKey, @selector(_isSelected));
88    addLookupKey(WebElementMediaURLKey, @selector(_absoluteMediaURL));
89    addLookupKey(WebElementSpellingToolTipKey, @selector(_spellingToolTip));
90    addLookupKey(WebElementTitleKey, @selector(_title));
91    addLookupKey(WebElementLinkURLKey, @selector(_absoluteLinkURL));
92    addLookupKey(WebElementLinkTargetFrameKey, @selector(_targetWebFrame));
93    addLookupKey(WebElementLinkTitleKey, @selector(_titleDisplayString));
94    addLookupKey(WebElementLinkLabelKey, @selector(_textContent));
95    addLookupKey(WebElementLinkIsLiveKey, @selector(_isLiveLink));
96    addLookupKey(WebElementIsContentEditableKey, @selector(_isContentEditable));
97    addLookupKey(WebElementIsInScrollBarKey, @selector(_isInScrollBar));
98}
99
100- (id)initWithHitTestResult:(const HitTestResult&)result
101{
102    [[self class] initializeLookupTable];
103    [super init];
104    _result = new HitTestResult(result);
105    return self;
106}
107
108- (void)dealloc
109{
110    if (WebCoreObjCScheduleDeallocateOnMainThread([WebElementDictionary class], self))
111        return;
112
113    delete _result;
114    [_cache release];
115    [_nilValues release];
116    [super dealloc];
117}
118
119- (void)finalize
120{
121    ASSERT_MAIN_THREAD();
122    delete _result;
123    [super finalize];
124}
125
126- (void)_fillCache
127{
128    CFDictionaryApplyFunction(lookupTable, cacheValueForKey, self);
129    _cacheComplete = YES;
130}
131
132- (NSUInteger)count
133{
134    if (!_cacheComplete)
135        [self _fillCache];
136    return [_cache count];
137}
138
139- (NSEnumerator *)keyEnumerator
140{
141    if (!_cacheComplete)
142        [self _fillCache];
143    return [_cache keyEnumerator];
144}
145
146- (id)objectForKey:(id)key
147{
148    id value = [_cache objectForKey:key];
149    if (value || _cacheComplete || [_nilValues containsObject:key])
150        return value;
151
152    SEL selector = (SEL)CFDictionaryGetValue(lookupTable, key);
153    if (!selector)
154        return nil;
155    value = [self performSelector:selector];
156
157    unsigned lookupTableCount = CFDictionaryGetCount(lookupTable);
158    if (value) {
159        if (!_cache)
160            _cache = [[NSMutableDictionary alloc] initWithCapacity:lookupTableCount];
161        [_cache setObject:value forKey:key];
162    } else {
163        if (!_nilValues)
164            _nilValues = [[NSMutableSet alloc] initWithCapacity:lookupTableCount];
165        [_nilValues addObject:key];
166    }
167
168    _cacheComplete = ([_cache count] + [_nilValues count]) == lookupTableCount;
169
170    return value;
171}
172
173- (DOMNode *)_domNode
174{
175    return kit(_result->innerNonSharedNode());
176}
177
178- (WebFrame *)_webFrame
179{
180    return [[[self _domNode] ownerDocument] webFrame];
181}
182
183// String's NSString* operator converts null Strings to empty NSStrings for compatibility
184// with AppKit. We need to work around that here.
185static NSString* NSStringOrNil(String coreString)
186{
187    if (coreString.isNull())
188        return nil;
189    return coreString;
190}
191
192- (NSString *)_altDisplayString
193{
194    return NSStringOrNil(_result->altDisplayString());
195}
196
197- (NSString *)_spellingToolTip
198{
199    TextDirection dir;
200    return NSStringOrNil(_result->spellingToolTip(dir));
201}
202
203- (NSImage *)_image
204{
205    Image* image = _result->image();
206    return image ? image->getNSImage() : nil;
207}
208
209- (NSValue *)_imageRect
210{
211    IntRect rect = _result->imageRect();
212    return rect.isEmpty() ? nil : [NSValue valueWithRect:rect];
213}
214
215- (NSURL *)_absoluteImageURL
216{
217    return _result->absoluteImageURL();
218}
219
220- (NSURL *)_absoluteMediaURL
221{
222    return _result->absoluteMediaURL();
223}
224
225- (NSNumber *)_isSelected
226{
227    return [NSNumber numberWithBool:_result->isSelected()];
228}
229
230- (NSString *)_title
231{
232    TextDirection dir;
233    return NSStringOrNil(_result->title(dir));
234}
235
236- (NSURL *)_absoluteLinkURL
237{
238    return _result->absoluteLinkURL();
239}
240
241- (WebFrame *)_targetWebFrame
242{
243    return kit(_result->targetFrame());
244}
245
246- (NSString *)_titleDisplayString
247{
248    return NSStringOrNil(_result->titleDisplayString());
249}
250
251- (NSString *)_textContent
252{
253    return NSStringOrNil(_result->textContent());
254}
255
256- (NSNumber *)_isLiveLink
257{
258    return [NSNumber numberWithBool:_result->isLiveLink()];
259}
260
261- (NSNumber *)_isContentEditable
262{
263    return [NSNumber numberWithBool:_result->isContentEditable()];
264}
265
266- (NSNumber *)_isInScrollBar
267{
268    return [NSNumber numberWithBool:_result->scrollbar() != 0];
269}
270
271@end
272