1/*
2 * Copyright (C) 2010, 2015 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 <WebCore/DOMWindow.h>
23#include <WebCore/Document.h>
24#include <WebCore/Frame.h>
25#include <WebCore/FrameDestructionObserver.h>
26#include <WebCore/Node.h>
27#include <glib-object.h>
28#include <wtf/HashMap.h>
29#include <wtf/NeverDestroyed.h>
30#include <wtf/RunLoop.h>
31#include <wtf/Vector.h>
32#include <wtf/glib/GRefPtr.h>
33
34namespace WebKit {
35
36struct DOMObjectCacheData {
37 DOMObjectCacheData(GObject* wrapper)
38 : object(wrapper)
39 , cacheReferences(1)
40 {
41 }
42
43 void clearObject()
44 {
45 ASSERT(object);
46 ASSERT(cacheReferences >= 1);
47 ASSERT(object->ref_count >= 1);
48
49 // Make sure we don't unref more than the references the object actually has. It can happen that user
50 // unreffed a reference owned by the cache.
51 cacheReferences = std::min(static_cast<unsigned>(object->ref_count), cacheReferences);
52 GRefPtr<GObject> protect(object);
53 do {
54 g_object_unref(object);
55 } while (--cacheReferences);
56 object = nullptr;
57 }
58
59 void* refObject()
60 {
61 ASSERT(object);
62
63 cacheReferences++;
64 return g_object_ref(object);
65 }
66
67 GObject* object;
68 unsigned cacheReferences;
69};
70
71class DOMObjectCacheFrameObserver;
72typedef HashMap<WebCore::Frame*, std::unique_ptr<DOMObjectCacheFrameObserver>> DOMObjectCacheFrameObserverMap;
73
74static DOMObjectCacheFrameObserverMap& domObjectCacheFrameObservers()
75{
76 static NeverDestroyed<DOMObjectCacheFrameObserverMap> map;
77 return map;
78}
79
80static DOMObjectCacheFrameObserver& getOrCreateDOMObjectCacheFrameObserver(WebCore::Frame& frame)
81{
82 DOMObjectCacheFrameObserverMap::AddResult result = domObjectCacheFrameObservers().add(&frame, nullptr);
83 if (result.isNewEntry)
84 result.iterator->value = std::make_unique<DOMObjectCacheFrameObserver>(frame);
85 return *result.iterator->value;
86}
87
88class DOMObjectCacheFrameObserver final: public WebCore::FrameDestructionObserver {
89public:
90 DOMObjectCacheFrameObserver(WebCore::Frame& frame)
91 : FrameDestructionObserver(&frame)
92 {
93 }
94
95 ~DOMObjectCacheFrameObserver()
96 {
97 ASSERT(m_objects.isEmpty());
98 }
99
100 void addObjectCacheData(DOMObjectCacheData& data)
101 {
102 ASSERT(!m_objects.contains(&data));
103
104 WebCore::DOMWindow* domWindow = m_frame->document()->domWindow();
105 if (domWindow && (!m_domWindowObserver || m_domWindowObserver->window() != domWindow)) {
106 // New DOMWindow, clear the cache and create a new DOMWindowObserver.
107 clear();
108 m_domWindowObserver = std::make_unique<DOMWindowObserver>(*domWindow, *this);
109 }
110
111 m_objects.append(&data);
112 g_object_weak_ref(data.object, DOMObjectCacheFrameObserver::objectFinalizedCallback, this);
113 }
114
115private:
116 class DOMWindowObserver final : public WebCore::DOMWindow::Observer {
117 WTF_MAKE_FAST_ALLOCATED;
118 public:
119 DOMWindowObserver(WebCore::DOMWindow& window, DOMObjectCacheFrameObserver& frameObserver)
120 : m_window(makeWeakPtr(window))
121 , m_frameObserver(frameObserver)
122 {
123 window.registerObserver(*this);
124 }
125
126 ~DOMWindowObserver()
127 {
128 if (m_window)
129 m_window->unregisterObserver(*this);
130 }
131
132 WebCore::DOMWindow* window() const { return m_window.get(); }
133
134 private:
135 void willDetachGlobalObjectFromFrame() override
136 {
137 m_frameObserver.willDetachGlobalObjectFromFrame();
138 }
139
140 WeakPtr<WebCore::DOMWindow> m_window;
141 DOMObjectCacheFrameObserver& m_frameObserver;
142 };
143
144 static void objectFinalizedCallback(gpointer userData, GObject* finalizedObject)
145 {
146 DOMObjectCacheFrameObserver* observer = static_cast<DOMObjectCacheFrameObserver*>(userData);
147 observer->m_objects.removeFirstMatching([finalizedObject](DOMObjectCacheData* data) {
148 return data->object == finalizedObject;
149 });
150 }
151
152 void clear()
153 {
154 if (m_objects.isEmpty())
155 return;
156
157 auto objects = WTFMove(m_objects);
158
159 // Deleting of DOM wrappers might end up deleting the wrapped core object which could cause some problems
160 // for example if a Document is deleted during the frame destruction, so we remove the weak references now
161 // and delete the objects on next run loop iteration. See https://bugs.webkit.org/show_bug.cgi?id=151700.
162 for (auto* data : objects)
163 g_object_weak_unref(data->object, DOMObjectCacheFrameObserver::objectFinalizedCallback, this);
164
165 RunLoop::main().dispatch([objects] {
166 for (auto* data : objects)
167 data->clearObject();
168 });
169 }
170
171 void willDetachPage() override
172 {
173 clear();
174 }
175
176 void frameDestroyed() override
177 {
178 clear();
179 WebCore::Frame* frame = m_frame;
180 FrameDestructionObserver::frameDestroyed();
181 domObjectCacheFrameObservers().remove(frame);
182 }
183
184 void willDetachGlobalObjectFromFrame()
185 {
186 clear();
187 m_domWindowObserver = nullptr;
188 }
189
190 Vector<DOMObjectCacheData*, 8> m_objects;
191 std::unique_ptr<DOMWindowObserver> m_domWindowObserver;
192};
193
194typedef HashMap<void*, std::unique_ptr<DOMObjectCacheData>> DOMObjectMap;
195
196static DOMObjectMap& domObjects()
197{
198 static NeverDestroyed<DOMObjectMap> staticDOMObjects;
199 return staticDOMObjects;
200}
201
202void DOMObjectCache::forget(void* objectHandle)
203{
204 ASSERT(domObjects().contains(objectHandle));
205 domObjects().remove(objectHandle);
206}
207
208void* DOMObjectCache::get(void* objectHandle)
209{
210 DOMObjectCacheData* data = domObjects().get(objectHandle);
211 return data ? data->refObject() : nullptr;
212}
213
214void DOMObjectCache::put(void* objectHandle, void* wrapper)
215{
216 DOMObjectMap::AddResult result = domObjects().add(objectHandle, nullptr);
217 if (result.isNewEntry)
218 result.iterator->value = std::make_unique<DOMObjectCacheData>(G_OBJECT(wrapper));
219}
220
221void DOMObjectCache::put(WebCore::Node* objectHandle, void* wrapper)
222{
223 DOMObjectMap::AddResult result = domObjects().add(objectHandle, nullptr);
224 if (!result.isNewEntry)
225 return;
226
227 result.iterator->value = std::make_unique<DOMObjectCacheData>(G_OBJECT(wrapper));
228 if (WebCore::Frame* frame = objectHandle->document().frame())
229 getOrCreateDOMObjectCacheFrameObserver(*frame).addObjectCacheData(*result.iterator->value);
230}
231
232}
233