Don't leak Documents when using MutationObserver from extensions
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Mar 2013 02:35:53 +0000 (02:35 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Mar 2013 02:35:53 +0000 (02:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=111234

Patch by Elliott Sprehn <esprehn@gmail.com> on 2013-03-01
Reviewed by Adam Barth.

.:

* ManualTests/leak-observer-nonmain-world.html: Added.

Source/WebCore:

MutationObserverCallback holds a WorldContextHandle which secretly isn't
a handle to anything when it's for the main world. When it's for a non-main
world though, like those used in extensions, it becomes a strong reference
to the v8::Context which results in leaks by creating cycles:

MutationObserver -> Callback -> World -> Document -> Node -> MutationObserver.

Instead we should keep a RefPtr to a DOMWrapperWorld in the callback and then
get the v8::Context from that inside handleEvent.

Tests: ManualTests/leak-observer-nonmain-world.html

* bindings/v8/V8Binding.cpp:
(WebCore::toV8Context): Added overload that takes a DOMWrapperWorld.
* bindings/v8/V8Binding.h:
* bindings/v8/V8MutationCallback.cpp:
(WebCore::V8MutationCallback::V8MutationCallback):
(WebCore::V8MutationCallback::handleEvent):
* bindings/v8/V8MutationCallback.h:
(V8MutationCallback):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@144522 268f45cc-cd09-0410-ab3c-d52691b4dbfc

ChangeLog
ManualTests/leak-observer-nonmain-world.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/v8/V8Binding.cpp
Source/WebCore/bindings/v8/V8Binding.h
Source/WebCore/bindings/v8/V8MutationCallback.cpp
Source/WebCore/bindings/v8/V8MutationCallback.h

index 9004d06..260ccae 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2013-03-01  Elliott Sprehn  <esprehn@gmail.com>
+
+        Don't leak Documents when using MutationObserver from extensions
+        https://bugs.webkit.org/show_bug.cgi?id=111234
+
+        Reviewed by Adam Barth.
+
+        * ManualTests/leak-observer-nonmain-world.html: Added.
+
 2013-02-27  Zan Dobersek  <zdobersek@igalia.com>
 
         [GTK] Disable MathML support in release builds
diff --git a/ManualTests/leak-observer-nonmain-world.html b/ManualTests/leak-observer-nonmain-world.html
new file mode 100644 (file)
index 0000000..3fd736c
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+
+<p>Test that using mutation observers from the non-main world doesn't leak the document.</p>
+<p>Expected output of this test is LEAK: 28 WebCoreNode</p>
+
+<iframe></iframe>
+
+<script>
+testRunner.dumpAsText();
+testRunner.waitUntilDone();
+
+var iframe = document.querySelector('iframe');
+var count = 0;
+var totalRuns = 5;
+
+iframe.onload = function() {
+    if (count++ < totalRuns) {
+        testRunner.evaluateScriptInIsolatedWorld(1, 'new MutationObserver(function(){}).observe(document, {childList: true, subtree: true});');
+        iframe.srcdoc = "<body><input autofocus></body>";
+        GCController.collect();
+    } else {
+        GCController.collect();
+        testRunner.notifyDone();
+    }
+};
+
+// Need autofocus since evaluateScriptInIsolatedWorld runs in the focused frame.
+iframe.srcdoc = "<body><input autofocus></body>";
+</script>
+
index 0d2cb6d..4864f8d 100644 (file)
@@ -1,3 +1,31 @@
+2013-03-01  Elliott Sprehn  <esprehn@gmail.com>
+
+        Don't leak Documents when using MutationObserver from extensions
+        https://bugs.webkit.org/show_bug.cgi?id=111234
+
+        Reviewed by Adam Barth.
+
+        MutationObserverCallback holds a WorldContextHandle which secretly isn't
+        a handle to anything when it's for the main world. When it's for a non-main
+        world though, like those used in extensions, it becomes a strong reference
+        to the v8::Context which results in leaks by creating cycles:
+
+        MutationObserver -> Callback -> World -> Document -> Node -> MutationObserver.
+
+        Instead we should keep a RefPtr to a DOMWrapperWorld in the callback and then
+        get the v8::Context from that inside handleEvent.
+
+        Tests: ManualTests/leak-observer-nonmain-world.html
+
+        * bindings/v8/V8Binding.cpp:
+        (WebCore::toV8Context): Added overload that takes a DOMWrapperWorld.
+        * bindings/v8/V8Binding.h:
+        * bindings/v8/V8MutationCallback.cpp:
+        (WebCore::V8MutationCallback::V8MutationCallback):
+        (WebCore::V8MutationCallback::handleEvent):
+        * bindings/v8/V8MutationCallback.h:
+        (V8MutationCallback):
+
 2013-03-01  Bear Travis  <betravis@adobe.com>
 
         [css exclusions] Move ExclusionShapeInsideInfo into RenderBlockRareData
index 863f4fd..830bad3 100644 (file)
@@ -266,6 +266,26 @@ v8::Local<v8::Context> toV8Context(ScriptExecutionContext* context, const WorldC
     return v8::Local<v8::Context>();
 }
 
+v8::Local<v8::Context> toV8Context(ScriptExecutionContext* context, DOMWrapperWorld* world)
+{
+    if (context->isDocument()) {
+        if (Frame* frame = static_cast<Document*>(context)->frame()) {
+            // FIXME: Store the DOMWrapperWorld for the main world in the v8::Context so callers
+            // that are looking up their world with DOMWrapperWorld::getWorld(v8::Context::GetCurrent())
+            // won't end up passing null here when later trying to get their v8::Context back.
+            if (!world)
+                return frame->script()->mainWorldContext();
+            return v8::Local<v8::Context>::New(frame->script()->windowShell(world)->context());
+        }
+#if ENABLE(WORKERS)
+    } else if (context->isWorkerContext()) {
+        if (WorkerScriptController* script = static_cast<WorkerContext*>(context)->script())
+            return script->context();
+#endif
+    }
+    return v8::Local<v8::Context>();
+}
+
 bool handleOutOfMemory()
 {
     v8::Local<v8::Context> context = v8::Context::GetCurrent();
index 12aabe7..a17eb83 100644 (file)
@@ -445,6 +445,7 @@ namespace WebCore {
 
     // Returns the context associated with a ScriptExecutionContext.
     v8::Local<v8::Context> toV8Context(ScriptExecutionContext*, const WorldContextHandle&);
+    v8::Local<v8::Context> toV8Context(ScriptExecutionContext*, DOMWrapperWorld*);
 
     // Returns the frame object of the window object associated with
     // a context, if the window is currently being displayed in the Frame.
index 029aee8..ad99a6b 100644 (file)
@@ -38,7 +38,7 @@ namespace WebCore {
 V8MutationCallback::V8MutationCallback(v8::Handle<v8::Object> callback, ScriptExecutionContext* context, v8::Handle<v8::Object> owner, v8::Isolate* isolate)
     : ActiveDOMCallback(context)
     , m_callback(callback)
-    , m_worldContext(UseCurrentWorld)
+    , m_world(DOMWrapperWorld::getWorld(v8::Context::GetCurrent()))
 {
     owner->SetHiddenValue(V8HiddenPropertyName::callback(), callback);
     m_callback.get().MakeWeak(isolate, this, &V8MutationCallback::weakCallback);
@@ -55,7 +55,7 @@ bool V8MutationCallback::handleEvent(MutationRecordArray* mutations, MutationObs
 
     v8::HandleScope handleScope;
 
-    v8::Handle<v8::Context> v8Context = toV8Context(scriptExecutionContext(), m_worldContext);
+    v8::Handle<v8::Context> v8Context = toV8Context(scriptExecutionContext(), m_world.get());
     if (v8Context.IsEmpty())
         return true;
 
index 5a36642..c4f98fe 100644 (file)
 #define V8MutationCallback_h
 
 #include "ActiveDOMCallback.h"
+#include "DOMWrapperWorld.h"
 #include "MutationCallback.h"
 #include "ScopedPersistent.h"
-#include "WorldContextHandle.h"
 #include <v8.h>
+#include <wtf/RefPtr.h>
 
 namespace WebCore {
 
@@ -58,7 +59,7 @@ private:
     }
 
     ScopedPersistent<v8::Object> m_callback;
-    WorldContextHandle m_worldContext;
+    RefPtr<DOMWrapperWorld> m_world;
 };
 
 }