initial import
[vuplus_webkit] / Source / WebCore / bindings / v8 / V8GCController.cpp
1 /*
2  * Copyright (C) 2009 Google 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 are
6  * met:
7  * 
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  * 
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "V8GCController.h"
33
34 #include "ActiveDOMObject.h"
35 #include "Attr.h"
36 #include "DOMDataStore.h"
37 #include "DOMImplementation.h"
38 #include "HTMLImageElement.h"
39 #include "HTMLNames.h"
40 #include "MessagePort.h"
41 #include "PlatformSupport.h"
42 #include "RetainedDOMInfo.h"
43 #include "RetainedObjectInfo.h"
44 #include "V8Binding.h"
45 #include "V8CSSRule.h"
46 #include "V8CSSRuleList.h"
47 #include "V8CSSStyleDeclaration.h"
48 #include "V8DOMImplementation.h"
49 #include "V8MessagePort.h"
50 #include "V8StyleSheet.h"
51 #include "V8StyleSheetList.h"
52 #include "WrapperTypeInfo.h"
53
54 #include <algorithm>
55 #include <utility>
56 #include <v8-debug.h>
57 #include <wtf/HashMap.h>
58 #include <wtf/StdLibExtras.h>
59 #include <wtf/UnusedParam.h>
60
61 namespace WebCore {
62
63 #ifndef NDEBUG
64 // Keeps track of global handles created (not JS wrappers
65 // of DOM objects). Often these global handles are source
66 // of leaks.
67 //
68 // If you want to let a C++ object hold a persistent handle
69 // to a JS object, you should register the handle here to
70 // keep track of leaks.
71 //
72 // When creating a persistent handle, call:
73 //
74 // #ifndef NDEBUG
75 //    V8GCController::registerGlobalHandle(type, host, handle);
76 // #endif
77 //
78 // When releasing the handle, call:
79 //
80 // #ifndef NDEBUG
81 //    V8GCController::unregisterGlobalHandle(type, host, handle);
82 // #endif
83 //
84
85 static GlobalHandleMap& currentGlobalHandleMap()
86 {
87     return V8BindingPerIsolateData::current()->globalHandleMap();
88 }
89
90 // The function is the place to set the break point to inspect
91 // live global handles. Leaks are often come from leaked global handles.
92 static void enumerateGlobalHandles()
93 {
94     GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
95     for (GlobalHandleMap::iterator it = globalHandleMap.begin(), end = globalHandleMap.end(); it != end; ++it) {
96         GlobalHandleInfo* info = it->second;
97         UNUSED_PARAM(info);
98         v8::Value* handle = it->first;
99         UNUSED_PARAM(handle);
100     }
101 }
102
103 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
104 {
105     GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
106     ASSERT(!globalHandleMap.contains(*handle));
107     globalHandleMap.set(*handle, new GlobalHandleInfo(host, type));
108 }
109
110 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
111 {
112     GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
113     ASSERT(globalHandleMap.contains(*handle));
114     GlobalHandleInfo* info = globalHandleMap.take(*handle);
115     ASSERT(info->m_host == host);
116     delete info;
117 }
118 #endif // ifndef NDEBUG
119
120 typedef HashMap<Node*, v8::Object*> DOMNodeMap;
121 typedef HashMap<void*, v8::Object*> DOMObjectMap;
122
123 #ifndef NDEBUG
124
125 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
126 public:
127     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
128     {
129         WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
130         UNUSED_PARAM(type);
131         UNUSED_PARAM(object);
132     }
133 };
134
135 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
136 public:
137     void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
138     {
139         UNUSED_PARAM(object);
140         ASSERT(wrapper.IsWeak());
141     }
142 };
143
144 #endif // NDEBUG
145
146 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
147 public:
148     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
149     {
150         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);  
151
152         // Additional handling of message port ensuring that entangled ports also
153         // have their wrappers entangled. This should ideally be handled when the
154         // ports are actually entangled in MessagePort::entangle, but to avoid
155         // forking MessagePort.* this is postponed to GC time. Having this postponed
156         // has the drawback that the wrappers are "entangled/unentangled" for each
157         // GC even though their entaglement most likely is still the same.
158         if (V8MessagePort::info.equals(typeInfo)) {
159             // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
160             // since the Chromium port implementation can't tell the difference.
161             MessagePort* port1 = static_cast<MessagePort*>(object);
162             if (port1->isEntangled() || port1->hasPendingActivity())
163                 wrapper.ClearWeak();
164         } else {
165             ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
166             if (activeDOMObject && activeDOMObject->hasPendingActivity())
167                 wrapper.ClearWeak();
168         }
169     }
170 };
171
172 // Implements v8::RetainedObjectInfo.
173 class UnspecifiedGroup : public RetainedObjectInfo {
174 public:
175     explicit UnspecifiedGroup(void* object)
176         : m_object(object)
177     {
178         ASSERT(m_object);
179     }
180     
181     virtual void Dispose() { delete this; }
182   
183     virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
184     {
185         ASSERT(other);
186         return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
187     }
188
189     virtual intptr_t GetHash()
190     {
191         return reinterpret_cast<intptr_t>(m_object);
192     }
193     
194     virtual const char* GetLabel()
195     {
196         return "Object group";
197     }
198
199     virtual intptr_t GetEquivalenceClass()
200     {
201         return reinterpret_cast<intptr_t>(m_object);
202     }
203
204 private:
205     void* m_object;
206 };
207
208 class GroupId {
209 public:
210     GroupId() : m_type(NullType), m_groupId(0) {}
211     GroupId(Node* node) : m_type(NodeType), m_node(node) {}
212     GroupId(void* other) : m_type(OtherType), m_other(other) {}
213     bool operator!() const { return m_type == NullType; }
214     uintptr_t groupId() const { return m_groupId; }
215     RetainedObjectInfo* createRetainedObjectInfo() const
216     {
217         switch (m_type) {
218         case NullType:
219             return 0;
220         case NodeType:
221             return new RetainedDOMInfo(m_node);
222         case OtherType:
223             return new UnspecifiedGroup(m_other);
224         default:
225             return 0;
226         }
227     }
228     
229 private:
230     enum Type {
231         NullType,
232         NodeType,
233         OtherType
234     };
235     Type m_type;
236     union {
237         uintptr_t m_groupId;
238         Node* m_node;
239         void* m_other;
240     };
241 };
242
243 class GrouperItem {
244 public:
245     GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
246     uintptr_t groupId() const { return m_groupId.groupId(); }
247     RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
248     v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
249
250 private:
251     GroupId m_groupId;
252     v8::Persistent<v8::Object> m_wrapper;
253 };
254
255 bool operator<(const GrouperItem& a, const GrouperItem& b)
256 {
257     return a.groupId() < b.groupId();
258 }
259
260 typedef Vector<GrouperItem> GrouperList;
261
262 // If the node is in document, put it in the ownerDocument's object group.
263 //
264 // If an image element was created by JavaScript "new Image",
265 // it is not in a document. However, if the load event has not
266 // been fired (still onloading), it is treated as in the document.
267 //
268 // Otherwise, the node is put in an object group identified by the root
269 // element of the tree to which it belongs.
270 static GroupId calculateGroupId(Node* node)
271 {
272     if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
273         return GroupId(node->document());
274
275     Node* root = node;
276     if (node->isAttributeNode()) {
277         root = static_cast<Attr*>(node)->ownerElement();
278         // If the attribute has no element, no need to put it in the group,
279         // because it'll always be a group of 1.
280         if (!root)
281             return GroupId();
282     } else {
283         while (Node* parent = root->parentOrHostNode())
284             root = parent;
285     }
286
287     return GroupId(root);
288 }
289
290 class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
291 public:
292     void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
293     {
294         if (node->hasEventListeners()) {
295             Vector<v8::Persistent<v8::Value> > listeners;
296             EventListenerIterator iterator(node);
297             while (EventListener* listener = iterator.nextListener()) {
298                 if (listener->type() != EventListener::JSEventListenerType)
299                     continue;
300                 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
301                 if (!v8listener->hasExistingListenerObject())
302                     continue;
303                 listeners.append(v8listener->existingListenerObjectPersistentHandle());
304             }
305             if (!listeners.isEmpty())
306                 v8::V8::AddImplicitReferences(wrapper, listeners.data(), listeners.size());
307         }
308
309         GroupId groupId = calculateGroupId(node);
310         if (!groupId)
311             return;
312         m_grouper.append(GrouperItem(groupId, wrapper));
313     }
314
315     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
316     {
317     }
318
319     void applyGrouping()
320     {
321         // Group by sorting by the group id.
322         std::sort(m_grouper.begin(), m_grouper.end());
323
324         for (size_t i = 0; i < m_grouper.size(); ) {
325             // Seek to the next key (or the end of the list).
326             size_t nextKeyIndex = m_grouper.size();
327             for (size_t j = i; j < m_grouper.size(); ++j) {
328                 if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
329                     nextKeyIndex = j;
330                     break;
331                 }
332             }
333
334             ASSERT(nextKeyIndex > i);
335
336             // We only care about a group if it has more than one object. If it only
337             // has one object, it has nothing else that needs to be kept alive.
338             if (nextKeyIndex - i <= 1) {
339                 i = nextKeyIndex;
340                 continue;
341             }
342
343             size_t rootIndex = i;
344             
345             Vector<v8::Persistent<v8::Value> > group;
346             group.reserveCapacity(nextKeyIndex - i);
347             for (; i < nextKeyIndex; ++i) {
348                 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
349                 if (!wrapper.IsEmpty())
350                     group.append(wrapper);
351             }
352
353             if (group.size() > 1)
354                 v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
355
356             ASSERT(i == nextKeyIndex);
357         }
358     }
359
360 private:
361     GrouperList m_grouper;
362 };
363
364 // Create object groups for DOM tree nodes.
365 void V8GCController::gcPrologue()
366 {
367     v8::HandleScope scope;
368
369 #ifndef NDEBUG
370     DOMObjectVisitor domObjectVisitor;
371     visitDOMObjects(&domObjectVisitor);
372 #endif
373
374     // Run through all objects with possible pending activity making their
375     // wrappers non weak if there is pending activity.
376     GCPrologueVisitor prologueVisitor;
377     visitActiveDOMObjects(&prologueVisitor);
378
379     // Create object groups.
380     GrouperVisitor grouperVisitor;
381     visitDOMNodes(&grouperVisitor);
382     visitDOMObjects(&grouperVisitor);
383     grouperVisitor.applyGrouping();
384
385     // Clean single element cache for string conversions.
386     V8BindingPerIsolateData* data = V8BindingPerIsolateData::current();
387     data->stringCache()->clearOnGC();
388 }
389
390 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
391 public:
392     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
393     {
394         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
395         if (V8MessagePort::info.equals(typeInfo)) {
396             MessagePort* port1 = static_cast<MessagePort*>(object);
397             // We marked this port as reachable in GCPrologueVisitor.  Undo this now since the
398             // port could be not reachable in the future if it gets disentangled (and also
399             // GCPrologueVisitor expects to see all handles marked as weak).
400             if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
401                 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
402         } else {
403             ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
404             if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
405                 ASSERT(!wrapper.IsWeak());
406                 // NOTE: To re-enable weak status of the active object we use
407                 // |object| from the map and not |activeDOMObject|. The latter
408                 // may be a different pointer (in case ActiveDOMObject is not
409                 // the main base class of the object's class) and pointer
410                 // identity is required by DOM map functions.
411                 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
412             }
413         }
414     }
415 };
416
417 int V8GCController::workingSetEstimateMB = 0;
418
419 namespace {
420
421 int getMemoryUsageInMB()
422 {
423 #if PLATFORM(CHROMIUM)
424     return PlatformSupport::memoryUsageMB();
425 #else
426     return 0;
427 #endif
428 }
429
430 int getActualMemoryUsageInMB()
431 {
432 #if PLATFORM(CHROMIUM)
433     return PlatformSupport::actualMemoryUsageMB();
434 #else
435     return 0;
436 #endif
437 }
438
439 }  // anonymous namespace
440
441 void V8GCController::gcEpilogue()
442 {
443     v8::HandleScope scope;
444
445     // Run through all objects with pending activity making their wrappers weak
446     // again.
447     GCEpilogueVisitor epilogueVisitor;
448     visitActiveDOMObjects(&epilogueVisitor);
449
450     workingSetEstimateMB = getActualMemoryUsageInMB();
451
452 #ifndef NDEBUG
453     // Check all survivals are weak.
454     DOMObjectVisitor domObjectVisitor;
455     visitDOMObjects(&domObjectVisitor);
456
457     EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
458     visitDOMNodes(&weakDOMNodeVisitor);
459
460     enumerateGlobalHandles();
461 #endif
462 }
463
464 void V8GCController::checkMemoryUsage()
465 {
466 #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
467     const int lowMemoryUsageMB = PlatformSupport::lowMemoryUsageMB();
468     const int highMemoryUsageMB = PlatformSupport::highMemoryUsageMB();
469     const int highUsageDeltaMB = PlatformSupport::highUsageDeltaMB();
470 #else
471     return;
472 #endif
473
474     int memoryUsageMB = getMemoryUsageInMB();
475     if ((memoryUsageMB > lowMemoryUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highMemoryUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
476         v8::V8::LowMemoryNotification();
477 }
478
479
480 }  // namespace WebCore