blob: 0e790f9c8dd4027f5be735944ef20ad65133db10 [file]
/*
* Copyright (C) 2010, 2015 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "DOMObjectCache.h"
#include <WebCore/DOMWindowProperty.h>
#include <WebCore/Document.h>
#include <WebCore/Frame.h>
#include <WebCore/FrameDestructionObserver.h>
#include <WebCore/Node.h>
#include <glib-object.h>
#include <wtf/HashMap.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RunLoop.h>
#include <wtf/Vector.h>
#include <wtf/glib/GRefPtr.h>
namespace WebKit {
struct DOMObjectCacheData {
DOMObjectCacheData(GObject* wrapper)
: object(wrapper)
, cacheReferences(1)
{
}
void clearObject()
{
ASSERT(object);
ASSERT(cacheReferences >= 1);
ASSERT(object->ref_count >= 1);
// Make sure we don't unref more than the references the object actually has. It can happen that user
// unreffed a reference owned by the cache.
cacheReferences = std::min(static_cast<unsigned>(object->ref_count), cacheReferences);
GRefPtr<GObject> protect(object);
do {
g_object_unref(object);
} while (--cacheReferences);
object = nullptr;
}
void* refObject()
{
ASSERT(object);
cacheReferences++;
return g_object_ref(object);
}
GObject* object;
unsigned cacheReferences;
};
class DOMObjectCacheFrameObserver;
typedef HashMap<WebCore::Frame*, std::unique_ptr<DOMObjectCacheFrameObserver>> DOMObjectCacheFrameObserverMap;
static DOMObjectCacheFrameObserverMap& domObjectCacheFrameObservers()
{
static NeverDestroyed<DOMObjectCacheFrameObserverMap> map;
return map;
}
static DOMObjectCacheFrameObserver& getOrCreateDOMObjectCacheFrameObserver(WebCore::Frame& frame)
{
DOMObjectCacheFrameObserverMap::AddResult result = domObjectCacheFrameObservers().add(&frame, nullptr);
if (result.isNewEntry)
result.iterator->value = std::make_unique<DOMObjectCacheFrameObserver>(frame);
return *result.iterator->value;
}
class DOMObjectCacheFrameObserver final: public WebCore::FrameDestructionObserver {
public:
DOMObjectCacheFrameObserver(WebCore::Frame& frame)
: FrameDestructionObserver(&frame)
{
}
~DOMObjectCacheFrameObserver()
{
ASSERT(m_objects.isEmpty());
}
void addObjectCacheData(DOMObjectCacheData& data)
{
ASSERT(!m_objects.contains(&data));
WebCore::DOMWindow* domWindow = m_frame->document()->domWindow();
if (domWindow && (!m_domWindowObserver || m_domWindowObserver->domWindow() != domWindow)) {
// New DOMWindow, clear the cache and create a new DOMWindowObserver.
clear();
m_domWindowObserver = std::make_unique<DOMWindowObserver>(*m_frame, *this, domWindow);
}
m_objects.append(&data);
g_object_weak_ref(data.object, DOMObjectCacheFrameObserver::objectFinalizedCallback, this);
}
private:
class DOMWindowObserver final: public WebCore::DOMWindowProperty {
WTF_MAKE_FAST_ALLOCATED;
public:
DOMWindowObserver(WebCore::Frame& frame, DOMObjectCacheFrameObserver& frameObserver, WebCore::DOMWindow* window)
: DOMWindowProperty(&frame)
, m_frameObserver(frameObserver)
, m_domWindow(window)
{
ASSERT(m_domWindow);
}
virtual ~DOMWindowObserver()
{
}
WebCore::DOMWindow* domWindow() const { return m_domWindow; }
private:
void willDetachGlobalObjectFromFrame() override
{
// Clear the DOMWindowProperty first, and then notify the Frame observer.
DOMWindowProperty::willDetachGlobalObjectFromFrame();
m_frameObserver.willDetachGlobalObjectFromFrame();
}
DOMObjectCacheFrameObserver& m_frameObserver;
WebCore::DOMWindow* m_domWindow;
};
static void objectFinalizedCallback(gpointer userData, GObject* finalizedObject)
{
DOMObjectCacheFrameObserver* observer = static_cast<DOMObjectCacheFrameObserver*>(userData);
observer->m_objects.removeFirstMatching([finalizedObject](DOMObjectCacheData* data) {
return data->object == finalizedObject;
});
}
void clear()
{
if (m_objects.isEmpty())
return;
auto objects = WTFMove(m_objects);
// Deleting of DOM wrappers might end up deleting the wrapped core object which could cause some problems
// for example if a Document is deleted during the frame destruction, so we remove the weak references now
// and delete the objects on next run loop iteration. See https://bugs.webkit.org/show_bug.cgi?id=151700.
for (auto* data : objects)
g_object_weak_unref(data->object, DOMObjectCacheFrameObserver::objectFinalizedCallback, this);
RunLoop::main().dispatch([objects] {
for (auto* data : objects)
data->clearObject();
});
}
void willDetachPage() override
{
clear();
}
void frameDestroyed() override
{
clear();
WebCore::Frame* frame = m_frame;
FrameDestructionObserver::frameDestroyed();
domObjectCacheFrameObservers().remove(frame);
}
void willDetachGlobalObjectFromFrame()
{
clear();
m_domWindowObserver = nullptr;
}
Vector<DOMObjectCacheData*, 8> m_objects;
std::unique_ptr<DOMWindowObserver> m_domWindowObserver;
};
typedef HashMap<void*, std::unique_ptr<DOMObjectCacheData>> DOMObjectMap;
static DOMObjectMap& domObjects()
{
static NeverDestroyed<DOMObjectMap> staticDOMObjects;
return staticDOMObjects;
}
void DOMObjectCache::forget(void* objectHandle)
{
ASSERT(domObjects().contains(objectHandle));
domObjects().remove(objectHandle);
}
void* DOMObjectCache::get(void* objectHandle)
{
DOMObjectCacheData* data = domObjects().get(objectHandle);
return data ? data->refObject() : nullptr;
}
void DOMObjectCache::put(void* objectHandle, void* wrapper)
{
DOMObjectMap::AddResult result = domObjects().add(objectHandle, nullptr);
if (result.isNewEntry)
result.iterator->value = std::make_unique<DOMObjectCacheData>(G_OBJECT(wrapper));
}
void DOMObjectCache::put(WebCore::Node* objectHandle, void* wrapper)
{
DOMObjectMap::AddResult result = domObjects().add(objectHandle, nullptr);
if (!result.isNewEntry)
return;
result.iterator->value = std::make_unique<DOMObjectCacheData>(G_OBJECT(wrapper));
if (WebCore::Frame* frame = objectHandle->document().frame())
getOrCreateDOMObjectCacheFrameObserver(*frame).addObjectCacheData(*result.iterator->value);
}
}