1/*
2 *  Copyright (C) 2010 Igalia S.L.
3 *
4 *  This library is free software; you can redistribute it and/or
5 *  modify it under the terms of the GNU Lesser General Public
6 *  License as published by the Free Software Foundation; either
7 *  version 2 of the License, or (at your option) any later version.
8 *
9 *  This library is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 *  Lesser General Public License for more details.
13 *
14 *  You should have received a copy of the GNU Lesser General Public
15 *  License along with this library; if not, write to the Free Software
16 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19#include "config.h"
20#include "DOMObjectCache.h"
21
22#include "Document.h"
23#include "Node.h"
24#include <glib-object.h>
25#include <wtf/HashMap.h>
26
27namespace WebKit {
28
29typedef struct {
30    GObject* object;
31    WebCore::Frame* frame;
32    guint timesReturned;
33} DOMObjectCacheData;
34
35typedef HashMap<void*, DOMObjectCacheData*> DOMObjectMap;
36
37static DOMObjectMap& domObjects()
38{
39    static DOMObjectMap staticDOMObjects;
40    return staticDOMObjects;
41}
42
43static WebCore::Frame* getFrameFromHandle(void* objectHandle)
44{
45    WebCore::Node* node = static_cast<WebCore::Node*>(objectHandle);
46    if (!node->inDocument())
47        return 0;
48    WebCore::Document* document = node->document();
49    if (!document)
50        return 0;
51    return document->frame();
52}
53
54void DOMObjectCache::forget(void* objectHandle)
55{
56    DOMObjectCacheData* cacheData = domObjects().get(objectHandle);
57    ASSERT(cacheData);
58    g_slice_free(DOMObjectCacheData, cacheData);
59    domObjects().take(objectHandle);
60}
61
62static void weakRefNotify(gpointer data, GObject* zombie)
63{
64    gboolean* objectDead = static_cast<gboolean*>(data);
65    *objectDead = TRUE;
66}
67
68void DOMObjectCache::clearByFrame(WebCore::Frame* frame)
69{
70    Vector<DOMObjectCacheData*> toUnref;
71
72    // Unreffing the objects removes them from the cache in their
73    // finalize method, so just save them to do that while we are not
74    // iterating the cache itself.
75    DOMObjectMap::iterator end = domObjects().end();
76    for (DOMObjectMap::iterator iter = domObjects().begin(); iter != end; ++iter) {
77        DOMObjectCacheData* data = iter->value;
78        ASSERT(data);
79        if ((!frame || data->frame == frame) && data->timesReturned)
80            toUnref.append(data);
81    }
82
83    Vector<DOMObjectCacheData*>::iterator last = toUnref.end();
84    for (Vector<DOMObjectCacheData*>::iterator it = toUnref.begin(); it != last; ++it) {
85        DOMObjectCacheData* data = *it;
86        // We can't really know what the user has done with the DOM
87        // objects, so in case any of the external references to them
88        // were unreffed (but not all, otherwise the object would be
89        // dead and out of the cache) we'll add a weak ref before we
90        // start to get rid of the cache's own references; if the
91        // object dies in the middle of the process, we'll just stop.
92        gboolean objectDead = FALSE;
93        g_object_weak_ref(data->object, weakRefNotify, &objectDead);
94        // We need to check objectDead first, otherwise the cache data
95        // might be garbage already.
96        while (!objectDead && data->timesReturned > 0) {
97            // If this is the last unref we are going to do,
98            // disconnect the weak ref. We cannot do it afterwards
99            // because the object might be dead at that point.
100            if (data->timesReturned == 1) {
101                g_object_weak_unref(data->object, weakRefNotify, &objectDead);
102                // At this point, the next time the DOMObject is
103                // unref'ed it will be finalized,
104                // DOMObject::finalize() will call
105                // DOMObjectCache::forget(), which will free 'data'.
106                // Toggling 'objectDead' here will ensure we don't
107                // dereference an invalid pointer in the next
108                // iteration.
109                objectDead = TRUE;
110            }
111            data->timesReturned--;
112            g_object_unref(data->object);
113        }
114    }
115}
116
117DOMObjectCache::~DOMObjectCache()
118{
119    clearByFrame();
120}
121
122void* DOMObjectCache::get(void* objectHandle)
123{
124    DOMObjectCacheData* data = domObjects().get(objectHandle);
125    if (!data)
126        return 0;
127
128    // We want to add one ref each time a wrapper is returned, so that
129    // the user can manually unref them if he chooses to.
130    ASSERT(data->object);
131    data->timesReturned++;
132    return g_object_ref(data->object);
133}
134
135void* DOMObjectCache::put(void* objectHandle, void* wrapper)
136{
137    if (domObjects().get(objectHandle))
138        return wrapper;
139
140    DOMObjectCacheData* data = g_slice_new(DOMObjectCacheData);
141    data->object = static_cast<GObject*>(wrapper);
142    data->frame = 0;
143    data->timesReturned = 1;
144
145    domObjects().set(objectHandle, data);
146    return wrapper;
147}
148
149void* DOMObjectCache::put(WebCore::Node* objectHandle, void* wrapper)
150{
151    // call the ::put version that takes void* to do the basic cache
152    // insertion work
153    put(static_cast<void*>(objectHandle), wrapper);
154
155    DOMObjectCacheData* data = domObjects().get(objectHandle);
156    ASSERT(data);
157
158    data->frame = getFrameFromHandle(objectHandle);
159
160    return wrapper;
161}
162
163}
164