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 "PlatformBridge.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
61namespace 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//
84typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap;
85
86static GlobalHandleMap& globalHandleMap()
87{
88    DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ());
89    return staticGlobalHandleMap;
90}
91
92// The function is the place to set the break point to inspect
93// live global handles. Leaks are often come from leaked global handles.
94static void enumerateGlobalHandles()
95{
96    for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) {
97        GlobalHandleInfo* info = it->second;
98        UNUSED_PARAM(info);
99        v8::Value* handle = it->first;
100        UNUSED_PARAM(handle);
101    }
102}
103
104void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
105{
106    ASSERT(!globalHandleMap().contains(*handle));
107    globalHandleMap().set(*handle, new GlobalHandleInfo(host, type));
108}
109
110void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
111{
112    ASSERT(globalHandleMap().contains(*handle));
113    GlobalHandleInfo* info = globalHandleMap().take(*handle);
114    ASSERT(info->m_host == host);
115    delete info;
116}
117#endif // ifndef NDEBUG
118
119typedef HashMap<Node*, v8::Object*> DOMNodeMap;
120typedef HashMap<void*, v8::Object*> DOMObjectMap;
121
122#ifndef NDEBUG
123
124class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
125public:
126    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
127    {
128        WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
129        UNUSED_PARAM(type);
130        UNUSED_PARAM(object);
131    }
132};
133
134class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
135public:
136    void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
137    {
138        UNUSED_PARAM(object);
139        ASSERT(wrapper.IsWeak());
140    }
141};
142
143#endif // NDEBUG
144
145class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
146public:
147    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
148    {
149        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
150
151        // Additional handling of message port ensuring that entangled ports also
152        // have their wrappers entangled. This should ideally be handled when the
153        // ports are actually entangled in MessagePort::entangle, but to avoid
154        // forking MessagePort.* this is postponed to GC time. Having this postponed
155        // has the drawback that the wrappers are "entangled/unentangled" for each
156        // GC even though their entaglement most likely is still the same.
157        if (V8MessagePort::info.equals(typeInfo)) {
158            // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
159            // since the Chromium port implementation can't tell the difference.
160            MessagePort* port1 = static_cast<MessagePort*>(object);
161            if (port1->isEntangled() || port1->hasPendingActivity())
162                wrapper.ClearWeak();
163        } else {
164            ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
165            if (activeDOMObject && activeDOMObject->hasPendingActivity())
166                wrapper.ClearWeak();
167        }
168    }
169};
170
171// Implements v8::RetainedObjectInfo.
172class UnspecifiedGroup : public RetainedObjectInfo {
173public:
174    explicit UnspecifiedGroup(void* object)
175        : m_object(object)
176    {
177        ASSERT(m_object);
178    }
179
180    virtual void Dispose() { delete this; }
181
182    virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
183    {
184        ASSERT(other);
185        return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
186    }
187
188    virtual intptr_t GetHash()
189    {
190        return reinterpret_cast<intptr_t>(m_object);
191    }
192
193    virtual const char* GetLabel()
194    {
195        return "Object group";
196    }
197
198    virtual intptr_t GetEquivalenceClass()
199    {
200        return reinterpret_cast<intptr_t>(m_object);
201    }
202
203private:
204    void* m_object;
205};
206
207class GroupId {
208public:
209    GroupId() : m_type(NullType), m_groupId(0) {}
210    GroupId(Node* node) : m_type(NodeType), m_node(node) {}
211    GroupId(void* other) : m_type(OtherType), m_other(other) {}
212    bool operator!() const { return m_type == NullType; }
213    uintptr_t groupId() const { return m_groupId; }
214    RetainedObjectInfo* createRetainedObjectInfo() const
215    {
216        switch (m_type) {
217        case NullType:
218            return 0;
219        case NodeType:
220            return new RetainedDOMInfo(m_node);
221        case OtherType:
222            return new UnspecifiedGroup(m_other);
223        default:
224            return 0;
225        }
226    }
227
228private:
229    enum Type {
230        NullType,
231        NodeType,
232        OtherType
233    };
234    Type m_type;
235    union {
236        uintptr_t m_groupId;
237        Node* m_node;
238        void* m_other;
239    };
240};
241
242class GrouperItem {
243public:
244    GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
245    uintptr_t groupId() const { return m_groupId.groupId(); }
246    RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
247    v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
248
249private:
250    GroupId m_groupId;
251    v8::Persistent<v8::Object> m_wrapper;
252};
253
254bool operator<(const GrouperItem& a, const GrouperItem& b)
255{
256    return a.groupId() < b.groupId();
257}
258
259typedef Vector<GrouperItem> GrouperList;
260
261// If the node is in document, put it in the ownerDocument's object group.
262//
263// If an image element was created by JavaScript "new Image",
264// it is not in a document. However, if the load event has not
265// been fired (still onloading), it is treated as in the document.
266//
267// Otherwise, the node is put in an object group identified by the root
268// element of the tree to which it belongs.
269static GroupId calculateGroupId(Node* node)
270{
271    if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
272        return GroupId(node->document());
273
274    Node* root = node;
275    if (node->isAttributeNode()) {
276        root = static_cast<Attr*>(node)->ownerElement();
277        // If the attribute has no element, no need to put it in the group,
278        // because it'll always be a group of 1.
279        if (!root)
280            return GroupId();
281    } else {
282        while (Node* parent = root->parentNode())
283            root = parent;
284    }
285
286    return GroupId(root);
287}
288
289static GroupId calculateGroupId(StyleBase* styleBase)
290{
291    ASSERT(styleBase);
292    StyleBase* current = styleBase;
293    StyleSheet* styleSheet = 0;
294    while (true) {
295        // Special case: CSSStyleDeclarations might be either inline and in this case
296        // we need to group them with their node or regular ones.
297        if (current->isMutableStyleDeclaration()) {
298            CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(current);
299            if (cssMutableStyleDeclaration->isInlineStyleDeclaration()) {
300                ASSERT(cssMutableStyleDeclaration->parent()->isStyleSheet());
301                return calculateGroupId(cssMutableStyleDeclaration->node());
302            }
303            // Either we have no parent, or this parent is a CSSRule.
304            ASSERT(cssMutableStyleDeclaration->parent() == cssMutableStyleDeclaration->parentRule());
305        }
306
307        if (current->isStyleSheet())
308            styleSheet = static_cast<StyleSheet*>(current);
309
310        StyleBase* parent = current->parent();
311        if (!parent)
312            break;
313        current = parent;
314    }
315
316    if (styleSheet) {
317        if (Node* ownerNode = styleSheet->ownerNode())
318            return calculateGroupId(ownerNode);
319        return GroupId(styleSheet);
320    }
321
322    return GroupId(current);
323}
324
325class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
326public:
327    void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
328    {
329        GroupId groupId = calculateGroupId(node);
330        if (!groupId)
331            return;
332        m_grouper.append(GrouperItem(groupId, wrapper));
333    }
334
335    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
336    {
337        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
338
339        if (typeInfo->isSubclass(&V8StyleSheetList::info)) {
340            StyleSheetList* styleSheetList = static_cast<StyleSheetList*>(object);
341            GroupId groupId(styleSheetList);
342            if (Document* document = styleSheetList->document())
343                groupId = GroupId(document);
344            m_grouper.append(GrouperItem(groupId, wrapper));
345
346        } else if (typeInfo->isSubclass(&V8DOMImplementation::info)) {
347            DOMImplementation* domImplementation = static_cast<DOMImplementation*>(object);
348            GroupId groupId(domImplementation);
349            if (Document* document = domImplementation->ownerDocument())
350                groupId = GroupId(document);
351            m_grouper.append(GrouperItem(groupId, wrapper));
352
353        } else if (typeInfo->isSubclass(&V8StyleSheet::info) || typeInfo->isSubclass(&V8CSSRule::info)) {
354            m_grouper.append(GrouperItem(calculateGroupId(static_cast<StyleBase*>(object)), wrapper));
355
356        } else if (typeInfo->isSubclass(&V8CSSStyleDeclaration::info)) {
357            CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(object);
358
359            GroupId groupId = calculateGroupId(cssStyleDeclaration);
360            m_grouper.append(GrouperItem(groupId, wrapper));
361
362            // Keep alive "dirty" primitive values (i.e. the ones that
363            // have user-added properties) by creating implicit
364            // references between the style declaration and the values
365            // in it.
366            if (cssStyleDeclaration->isMutableStyleDeclaration()) {
367                CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(cssStyleDeclaration);
368                Vector<v8::Persistent<v8::Value> > values;
369                values.reserveCapacity(cssMutableStyleDeclaration->length());
370                CSSMutableStyleDeclaration::const_iterator end = cssMutableStyleDeclaration->end();
371                for (CSSMutableStyleDeclaration::const_iterator it = cssMutableStyleDeclaration->begin(); it != end; ++it) {
372                    v8::Persistent<v8::Object> value = store->domObjectMap().get(it->value());
373                    if (!value.IsEmpty() && value->IsDirty())
374                        values.append(value);
375                }
376                if (!values.isEmpty())
377                    v8::V8::AddImplicitReferences(wrapper, values.data(), values.size());
378            }
379
380        }
381    }
382
383    void applyGrouping()
384    {
385        // Group by sorting by the group id.
386        std::sort(m_grouper.begin(), m_grouper.end());
387
388        for (size_t i = 0; i < m_grouper.size(); ) {
389            // Seek to the next key (or the end of the list).
390            size_t nextKeyIndex = m_grouper.size();
391            for (size_t j = i; j < m_grouper.size(); ++j) {
392                if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
393                    nextKeyIndex = j;
394                    break;
395                }
396            }
397
398            ASSERT(nextKeyIndex > i);
399
400            // We only care about a group if it has more than one object. If it only
401            // has one object, it has nothing else that needs to be kept alive.
402            if (nextKeyIndex - i <= 1) {
403                i = nextKeyIndex;
404                continue;
405            }
406
407            size_t rootIndex = i;
408
409            Vector<v8::Persistent<v8::Value> > group;
410            group.reserveCapacity(nextKeyIndex - i);
411            for (; i < nextKeyIndex; ++i) {
412                v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
413                if (!wrapper.IsEmpty())
414                    group.append(wrapper);
415            }
416
417            if (group.size() > 1)
418                v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
419
420            ASSERT(i == nextKeyIndex);
421        }
422    }
423
424private:
425    GrouperList m_grouper;
426};
427
428// Create object groups for DOM tree nodes.
429void V8GCController::gcPrologue()
430{
431    v8::HandleScope scope;
432
433#ifndef NDEBUG
434    DOMObjectVisitor domObjectVisitor;
435    visitDOMObjectsInCurrentThread(&domObjectVisitor);
436#endif
437
438    // Run through all objects with possible pending activity making their
439    // wrappers non weak if there is pending activity.
440    GCPrologueVisitor prologueVisitor;
441    visitActiveDOMObjectsInCurrentThread(&prologueVisitor);
442
443    // Create object groups.
444    GrouperVisitor grouperVisitor;
445    visitDOMNodesInCurrentThread(&grouperVisitor);
446    visitDOMObjectsInCurrentThread(&grouperVisitor);
447    grouperVisitor.applyGrouping();
448
449    // Clean single element cache for string conversions.
450    lastStringImpl = 0;
451    lastV8String.Clear();
452}
453
454class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
455public:
456    void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
457    {
458        WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
459        if (V8MessagePort::info.equals(typeInfo)) {
460            MessagePort* port1 = static_cast<MessagePort*>(object);
461            // We marked this port as reachable in GCPrologueVisitor.  Undo this now since the
462            // port could be not reachable in the future if it gets disentangled (and also
463            // GCPrologueVisitor expects to see all handles marked as weak).
464            if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
465                wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
466        } else {
467            ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
468            if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
469                ASSERT(!wrapper.IsWeak());
470                // NOTE: To re-enable weak status of the active object we use
471                // |object| from the map and not |activeDOMObject|. The latter
472                // may be a different pointer (in case ActiveDOMObject is not
473                // the main base class of the object's class) and pointer
474                // identity is required by DOM map functions.
475                wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
476            }
477        }
478    }
479};
480
481int V8GCController::workingSetEstimateMB = 0;
482
483namespace {
484
485int getMemoryUsageInMB()
486{
487#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
488    return PlatformBridge::memoryUsageMB();
489#else
490    return 0;
491#endif
492}
493
494int getActualMemoryUsageInMB()
495{
496#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
497    return PlatformBridge::actualMemoryUsageMB();
498#else
499    return 0;
500#endif
501}
502
503}  // anonymous namespace
504
505void V8GCController::gcEpilogue()
506{
507    v8::HandleScope scope;
508
509    // Run through all objects with pending activity making their wrappers weak
510    // again.
511    GCEpilogueVisitor epilogueVisitor;
512    visitActiveDOMObjectsInCurrentThread(&epilogueVisitor);
513
514    workingSetEstimateMB = getActualMemoryUsageInMB();
515
516#ifndef NDEBUG
517    // Check all survivals are weak.
518    DOMObjectVisitor domObjectVisitor;
519    visitDOMObjectsInCurrentThread(&domObjectVisitor);
520
521    EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
522    visitDOMNodesInCurrentThread(&weakDOMNodeVisitor);
523
524    enumerateGlobalHandles();
525#endif
526}
527
528void V8GCController::checkMemoryUsage()
529{
530#if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
531    // These values are appropriate for Chromium only.
532    const int lowUsageMB = 256;  // If memory usage is below this threshold, do not bother forcing GC.
533    const int highUsageMB = 1024;  // If memory usage is above this threshold, force GC more aggresively.
534    const int highUsageDeltaMB = 128;  // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high.
535#elif PLATFORM(ANDROID)
536    // Query the PlatformBridge for memory thresholds as these vary device to device.
537    static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB();
538    static const int highUsageMB = PlatformBridge::highMemoryUsageMB();
539    static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB();
540#else
541    return;
542#endif
543
544    int memoryUsageMB = getMemoryUsageInMB();
545    if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
546        v8::V8::LowMemoryNotification();
547}
548
549
550}  // namespace WebCore
551