Remove leak of objects between isolated worlds on custom events, message events,...
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Jul 2015 18:11:40 +0000 (18:11 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Jul 2015 18:11:40 +0000 (18:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=118884

Reviewed by Filip Pizlo and Mark Lam.
Patch by Keith Miller  <keith_miller@apple.com>.

Source/WebCore:

Tests: fast/events/event-leak-objects.html
       fast/events/event-properties-gc.html

Fixes an issue where objects passed as certain properties of events could cross isolated worlds. This
was fixed by checking that any object passed by an event must be serializable or originate from the same
isolated world as the one it is currently being accessed in. In the case of MessageEvents and PopStateEvents we
cache the values of the data and state properties, respectively, as they may be a deserialized object. In case
an object was deserialized in a world with elevated privileges we also check the cached value is from the same
world, if it is from a different world we recompute it. For testing purposes, I added a new function to Internals
that determines whether a JSObject originated in the current world.

* CMakeLists.txt:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/DOMWrapperWorld.h:
(WebCore::worldForDOMObject):
* bindings/js/JSBindingsAllInOne.cpp:
* bindings/js/JSCustomEventCustom.cpp: Copied from Source/WebCore/dom/CustomEvent.cpp.
(WebCore::JSCustomEvent::detail):
* bindings/js/JSMessageEventCustom.cpp:
(WebCore::JSMessageEvent::data):
* bindings/js/JSPopStateEventCustom.cpp:
(WebCore::JSPopStateEvent::state):
* dom/CustomEvent.cpp:
(WebCore::CustomEvent::initCustomEvent):
(WebCore::CustomEvent::trySerializeDetail):
* dom/CustomEvent.h:
* dom/CustomEvent.idl:
* dom/MessageEvent.cpp:
(WebCore::MessageEvent::initMessageEvent):
(WebCore::MessageEvent::trySerializeData):
* dom/MessageEvent.h:
* dom/PopStateEvent.cpp:
(WebCore::PopStateEvent::trySerializeState):
* dom/PopStateEvent.h:
* testing/Internals.cpp:
(WebCore::Internals::isFromCurrentWorld):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

These tests ensure ensure objects are not leaked across isolated worlds and that those properties are not prematurely
garbage collected.

* fast/events/constructors/custom-event-constructor-expected.txt:
* fast/events/constructors/custom-event-constructor.html:
* fast/events/event-leak-objects-expected.txt: Added.
* fast/events/event-leak-objects.html: Added.
* fast/events/event-properties-gc-expected.txt: Added.
* fast/events/event-properties-gc.html: Added.

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

25 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/constructors/custom-event-constructor-expected.txt
LayoutTests/fast/events/constructors/custom-event-constructor.html
LayoutTests/fast/events/event-leak-objects-expected.txt
LayoutTests/fast/events/event-leak-objects.html
LayoutTests/fast/events/event-properties-gc-expected.txt
LayoutTests/fast/events/event-properties-gc.html
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/DOMWrapperWorld.h
Source/WebCore/bindings/js/JSBindingsAllInOne.cpp
Source/WebCore/bindings/js/JSCustomEventCustom.cpp
Source/WebCore/bindings/js/JSMessageEventCustom.cpp
Source/WebCore/bindings/js/JSPopStateEventCustom.cpp
Source/WebCore/dom/CustomEvent.cpp
Source/WebCore/dom/CustomEvent.h
Source/WebCore/dom/CustomEvent.idl
Source/WebCore/dom/MessageEvent.cpp
Source/WebCore/dom/MessageEvent.h
Source/WebCore/dom/PopStateEvent.cpp
Source/WebCore/dom/PopStateEvent.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index 265583c..c8c4f86 100644 (file)
@@ -1,3 +1,21 @@
+2015-07-16  Mark Lam  <mark.lam@apple.com>
+
+        Remove leak of objects between isolated worlds on custom events, message events, and pop state events.
+        https://bugs.webkit.org/show_bug.cgi?id=118884
+
+        Reviewed by Filip Pizlo and Mark Lam.
+        Patch by Keith Miller  <keith_miller@apple.com>.
+
+        These tests ensure ensure objects are not leaked across isolated worlds and that those properties are not prematurely
+        garbage collected.
+
+        * fast/events/constructors/custom-event-constructor-expected.txt:
+        * fast/events/constructors/custom-event-constructor.html:
+        * fast/events/event-leak-objects-expected.txt: Added.
+        * fast/events/event-leak-objects.html: Added.
+        * fast/events/event-properties-gc-expected.txt: Added.
+        * fast/events/event-properties-gc.html: Added.
+
 2015-07-16  Simon Fraser  <simon.fraser@apple.com>
 
         Fix disappearing position:fixed elements in fixed layout mode
index 648c596..9f0f9c0 100644 (file)
@@ -13,8 +13,11 @@ PASS new CustomEvent('eventType', { detail: 10 }).detail is 10
 PASS new CustomEvent('eventType', { detail: 'string' }).detail is 'string'
 PASS new CustomEvent('eventType', { detail: detailObject }).detail is detailObject
 PASS new CustomEvent('eventType', { detail: document }).detail is document
+PASS new CustomEvent('eventType', { detail: undefined }).detail is undefined
+PASS new CustomEvent('eventType', { detail: null }).detail is null
 PASS new CustomEvent('eventType', { get detail() { return true; } }).detail is true
 PASS new CustomEvent('eventType', { get detail() { throw 'Custom Error'; } }) threw exception Custom Error.
+PASS event.detail is detailObject
 PASS successfullyParsed is true
 
 TEST COMPLETE
index a860a92..99d7501 100644 (file)
@@ -31,11 +31,23 @@ shouldBe("new CustomEvent('eventType', { detail: detailObject }).detail", "detai
 // Detail is a DOM object
 shouldBe("new CustomEvent('eventType', { detail: document }).detail", "document");
 
+// Detail is undefined.
+shouldBe("new CustomEvent('eventType', { detail: undefined }).detail", "undefined");
+
+// Detail is null.
+shouldBe("new CustomEvent('eventType', { detail: null }).detail", "null");
+
 // Detail is a getter.
 shouldBe("new CustomEvent('eventType', { get detail() { return true; } }).detail", "true");
 
 // Detail throws an exeception.
 shouldThrow("new CustomEvent('eventType', { get detail() { throw 'Custom Error'; } })");
+
+// try initCustomEvent
+var event = document.createEvent('CustomEvent');
+event.initCustomEvent('eventType', true, false, detailObject);
+shouldBe("event.detail", "detailObject");
+
 </script>
 <script src="../../../resources/js-test-post.js"></script>
 </body>
index e69de29..10856b6 100644 (file)
@@ -0,0 +1,58 @@
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS window.internals.isFromCurrentWorld(event) is true
+PASS checkAllPropertiesFromCurrentWorld(resultValue) is true
+PASS Object.pageDefinedVar is undefined.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index e69de29..7e2a5ef 100644 (file)
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+window.jsTestIsAsync = true;
+
+// The events that we want to test, with the properties that each one uses.
+var events = [
+    { eventKind: "CustomEvent", propName: "detail" },
+    { eventKind: "MessageEvent", propName: "data"  },
+    { eventKind: "PopStateEvent", propName: "state" }
+];
+
+// Types we can put in the property of the Event
+var values = [
+    { propValue: "document" },
+    { propValue: '{"bar":1}' },
+    { propValue: "5" },
+    { propValue: '"hello"' },
+    { propValue: "true" },
+    { propValue: "[1,2,3]" }
+];
+
+function merge(obj1, obj2) {
+    var newObj = { };
+    for (var attrname in obj1) { newObj[attrname] = obj1[attrname]; }
+    for (var attrname in obj2) { newObj[attrname] = obj2[attrname]; }
+    return newObj;
+}
+
+// Format the tests so they look like [ { eventKind: ..., propValue: ... }, ... { eventKind: ..., propValue: ... } ]
+var tests = events.map(function(event) {
+    return values.map(function(value) {
+        return merge(event, value);
+    })
+});
+tests = tests.reduce(function(a, b) {
+    return a.concat(b);
+});
+
+// We need to scan all the properies of value to ensure they all came from the current world.
+// Assumes window.internals exists.
+function checkAllPropertiesFromCurrentWorld(value) {
+    var allFromCurrentWorld = true;
+    while (allFromCurrentWorld && value && typeof value === "object") {
+        allFromCurrentWorld = allFromCurrentWorld && window.internals.isFromCurrentWorld(value);
+        for (var prop in value)
+            allFromCurrentWorld = allFromCurrentWorld && checkAllPropertiesFromCurrentWorld(value[prop], seenValues);
+        value = Object.getPrototypeOf(value);
+    }
+
+    return allFromCurrentWorld;
+}
+
+function addListener(eventKind, eventString, prop) {
+    document.addEventListener(eventString, function(event) {
+        eventValue = event
+        resultValue = event[prop]
+
+        if (window.internals) {
+            shouldBeTrue("window.internals.isFromCurrentWorld(event)");
+            shouldBeTrue("checkAllPropertiesFromCurrentWorld(resultValue)");
+        }
+
+        // The property defined in the isolated world should be undefined.
+        shouldBeUndefined("Object.pageDefinedVar");
+        window.postMessage("done", "*");
+    });
+}
+
+function sendDocumentEvent(eventKind, eventString, prop, value) {
+    var constructor = eval(eventKind);
+    var initializer = { };
+    initializer[prop] = value;
+    var newEvent = new constructor(eventString, initializer);
+    // Try to access the property in a different world to make sure caching issues do not occur
+    newEvent[prop];
+    document.dispatchEvent(newEvent);
+}
+
+function runScript(eventKind, propName, propValue, number) {
+    // Final string should have the form:
+    //     document.pageDefinedVar = 1; (function sendDocumentObject(eventKind, propName, result) {...})(...);
+    // When evaluated in the isolated world, should initiate the event with the
+    // document object as the specificed property value.
+    var eventString = eventKind + number;
+
+    var script = "Object.pageDefinedVar = 1; "
+        + "(" + sendDocumentEvent.toString() + ")('"
+        + eventKind + "', '" + eventString + "', '" + propName + "', " + propValue + ");";
+    addListener(eventKind, eventString);
+    testRunner.evaluateScriptInIsolatedWorld(0, script);
+}
+
+// Run the tests whenever a notification arrives, which indicates that the
+// previous test has finished.
+window.addEventListener("message", function(message) {
+    runNextTest();
+}, false);
+
+// Keep a count to make a unique string
+var count = 1;
+function runNextTest () {
+    var test = tests.pop();
+    if (!test) {
+        finishJSTest();
+        return;
+    }
+    runScript(test.eventKind, test.propName, test.propValue, count++);
+};
+
+// This test is meaningless without testRunner.
+if (window.testRunner) {
+    runNextTest();
+}
+</script>
+</body>
+<script src="../../resources/js-test-post.js"></script>
+</html>
index e69de29..3c65f0f 100644 (file)
@@ -0,0 +1,7 @@
+PASS event[prop] is "foo"
+PASS event[prop] is "foo"
+PASS event[prop] is "foo"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index e69de29..b249e38 100644 (file)
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+window.jsTestIsAsync = true;
+
+function addListener(eventType, prop) {
+    document.addEventListener(eventType, function(event) {
+        window.prop = prop;
+        // Despite the earlier assignement of the local variable to null and
+        // the following garabage collection, the property should still be
+        // present here.
+        shouldBeEqualToString("event[prop]", "foo");
+        window.prop = undefined;
+        window.postMessage("done", "*");
+    });
+}
+
+// Run the tests whenever a notification arrives, which indicates that the
+// previous test has finished.
+window.addEventListener("message", function(message) {
+    runNextTest();
+}, false);
+
+function newEvent(eventType, prop, value) {
+   return eval("new " + eventType + "('" + eventType + "', { " + prop + ": value })");
+}
+
+// The events that we want to test, with the properties that each one uses.
+var events = [
+    { eventType: "CustomEvent", prop: "detail" },
+    { eventType: "MessageEvent", prop: "data" },
+    { eventType: "PopStateEvent", prop: "state" }
+];
+
+function runNextTest () {
+    var evt = events.pop();
+    if (!evt) {
+        finishJSTest();
+        return;
+    }
+    var value = "foo";
+    var eventToDispatch = newEvent(evt.eventType, evt.prop, value);
+    value = null;
+    gc();
+    addListener(evt.eventType, evt.prop);
+    document.dispatchEvent(eventToDispatch);
+};
+
+// This test is meaningless without testRunner.
+if (window.testRunner) {
+    runNextTest();
+}
+</script>
+</body>
+<script src="../../resources/js-test-post.js"></script>
+</html>
index 000f999..d5460b7 100644 (file)
@@ -1097,6 +1097,7 @@ set(WebCore_SOURCES
     bindings/js/JSCryptoKeyPairCustom.cpp
     bindings/js/JSCryptoKeySerializationJWK.cpp
     bindings/js/JSCryptoOperationData.cpp
+    bindings/js/JSCustomEventCustom.cpp
     bindings/js/JSCustomSQLStatementErrorCallback.cpp
     bindings/js/JSCustomXPathNSResolver.cpp
     bindings/js/JSDOMBinding.cpp
index 152b1f3..2a1a748 100644 (file)
@@ -1,3 +1,50 @@
+2015-07-17  Mark Lam  <mark.lam@apple.com>
+
+        Remove leak of objects between isolated worlds on custom events, message events, and pop state events.
+        https://bugs.webkit.org/show_bug.cgi?id=118884
+
+        Reviewed by Filip Pizlo and Mark Lam.
+        Patch by Keith Miller  <keith_miller@apple.com>.
+
+        Tests: fast/events/event-leak-objects.html
+               fast/events/event-properties-gc.html
+
+        Fixes an issue where objects passed as certain properties of events could cross isolated worlds. This
+        was fixed by checking that any object passed by an event must be serializable or originate from the same
+        isolated world as the one it is currently being accessed in. In the case of MessageEvents and PopStateEvents we
+        cache the values of the data and state properties, respectively, as they may be a deserialized object. In case
+        an object was deserialized in a world with elevated privileges we also check the cached value is from the same
+        world, if it is from a different world we recompute it. For testing purposes, I added a new function to Internals
+        that determines whether a JSObject originated in the current world.
+
+        * CMakeLists.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/DOMWrapperWorld.h:
+        (WebCore::worldForDOMObject):
+        * bindings/js/JSBindingsAllInOne.cpp:
+        * bindings/js/JSCustomEventCustom.cpp: Copied from Source/WebCore/dom/CustomEvent.cpp.
+        (WebCore::JSCustomEvent::detail):
+        * bindings/js/JSMessageEventCustom.cpp:
+        (WebCore::JSMessageEvent::data):
+        * bindings/js/JSPopStateEventCustom.cpp:
+        (WebCore::JSPopStateEvent::state):
+        * dom/CustomEvent.cpp:
+        (WebCore::CustomEvent::initCustomEvent):
+        (WebCore::CustomEvent::trySerializeDetail):
+        * dom/CustomEvent.h:
+        * dom/CustomEvent.idl:
+        * dom/MessageEvent.cpp:
+        (WebCore::MessageEvent::initMessageEvent):
+        (WebCore::MessageEvent::trySerializeData):
+        * dom/MessageEvent.h:
+        * dom/PopStateEvent.cpp:
+        (WebCore::PopStateEvent::trySerializeState):
+        * dom/PopStateEvent.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::isFromCurrentWorld):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2015-07-17  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Cleanup PasteboardHelper
index 3252f15..edda1be 100644 (file)
                DEBCCDD216646E8200A452E1 /* RenderMediaControlElements.h in Headers */ = {isa = PBXBuildFile; fileRef = DE49B308165F2FE10010338D /* RenderMediaControlElements.h */; };
                DEBCCDD416646EAF00A452E1 /* MediaControlElementTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DE49B300165F2FC60010338D /* MediaControlElementTypes.h */; };
                DEBCCDD516646EB200A452E1 /* MediaControlElementTypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DE49B2FF165F2FC60010338D /* MediaControlElementTypes.cpp */; };
+               DEC297611B4F2F8D005F5945 /* JSCustomEventCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEC2975D1B4DEB2A005F5945 /* JSCustomEventCustom.cpp */; };
                DF9AFD7213FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = DF9AFD7013FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.h */; };
                DF9AFD7313FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.mm in Sources */ = {isa = PBXBuildFile; fileRef = DF9AFD7113FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.mm */; };
                E0FEF372B17C53EAC1C1FBEE /* EventSource.h in Headers */ = {isa = PBXBuildFile; fileRef = E0FEF371B17C53EAC1C1FBEE /* EventSource.h */; };
                DE49B300165F2FC60010338D /* MediaControlElementTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaControlElementTypes.h; sourceTree = "<group>"; };
                DE49B307165F2FE10010338D /* RenderMediaControlElements.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderMediaControlElements.cpp; sourceTree = "<group>"; };
                DE49B308165F2FE10010338D /* RenderMediaControlElements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderMediaControlElements.h; sourceTree = "<group>"; };
+               DEC2975D1B4DEB2A005F5945 /* JSCustomEventCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCustomEventCustom.cpp; sourceTree = "<group>"; };
                DF9AFD7013FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaPlayerPrivateAVFoundationObjC.h; sourceTree = "<group>"; };
                DF9AFD7113FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaPlayerPrivateAVFoundationObjC.mm; sourceTree = "<group>"; };
                E0FEF371B07C53EAC1C1FBEE /* EventSource.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EventSource.idl; sourceTree = "<group>"; };
                BC4EDEF70C08F414007EDD49 /* Custom */ = {
                        isa = PBXGroup;
                        children = (
+                               DEC2975D1B4DEB2A005F5945 /* JSCustomEventCustom.cpp */,
                                BC2ED6BB0C6BD2F000920BFF /* JSAttrCustom.cpp */,
                                FDEAAAEF12B02EE400DCF33B /* JSAudioBufferSourceNodeCustom.cpp */,
                                FDEAAAF012B02EE400DCF33B /* JSAudioContextCustom.cpp */,
                                B2227AD90D00BF220071B782 /* SVGTransformable.cpp in Sources */,
                                B2227ADC0D00BF220071B782 /* SVGTransformDistance.cpp in Sources */,
                                B2227ADE0D00BF220071B782 /* SVGTransformList.cpp in Sources */,
+                               DEC297611B4F2F8D005F5945 /* JSCustomEventCustom.cpp in Sources */,
                                B2227AE10D00BF220071B782 /* SVGTRefElement.cpp in Sources */,
                                B2227AE40D00BF220071B782 /* SVGTSpanElement.cpp in Sources */,
                                B2227AE90D00BF220071B782 /* SVGURIReference.cpp in Sources */,
index f88e1c4..f04883c 100644 (file)
@@ -74,6 +74,11 @@ inline DOMWrapperWorld& currentWorld(JSC::ExecState* exec)
     return JSC::jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject())->world();
 }
 
+inline DOMWrapperWorld& worldForDOMObject(JSC::JSObject* object)
+{
+    return JSC::jsCast<JSDOMGlobalObject*>(object->globalObject())->world();
+}
+    
 } // namespace WebCore
 
 #endif // DOMWrapperWorld_h
index 9d8a741..4e6d2e3 100644 (file)
@@ -45,6 +45,7 @@
 #include "JSCharacterDataCustom.cpp"
 #include "JSCommandLineAPIHostCustom.cpp"
 #include "JSCryptoCustom.cpp"
+#include "JSCustomEventCustom.cpp"
 #include "JSCustomSQLStatementErrorCallback.cpp"
 #include "JSCustomXPathNSResolver.cpp"
 #include "JSDOMBinding.cpp"
index e69de29..dad73a2 100644 (file)
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "JSCustomEvent.h"
+
+#include "CustomEvent.h"
+#include "DOMWrapperWorld.h"
+#include <runtime/JSCInlines.h>
+#include <runtime/JSCJSValue.h>
+#include <runtime/Structure.h>
+
+using namespace JSC;
+
+namespace WebCore {
+    
+JSValue JSCustomEvent::detail(ExecState* exec) const
+{
+    CustomEvent& event = impl();
+    
+    if (event.detail().hasNoValue())
+        return jsNull();
+
+    JSValue detail = event.detail().jsValue();
+    
+    if (detail.isObject() && &worldForDOMObject(detail.getObject()) != &currentWorld(exec)) {
+        // We need to make sure CustomEvents do not leak their detail property across isolated DOM worlds.
+        // Ideally, we would check that the worlds have different privileges but that's not possible yet.
+        RefPtr<SerializedScriptValue> serializedDetail = event.trySerializeDetail(exec);
+        
+        if (!serializedDetail)
+            return jsNull();
+        
+        return serializedDetail->deserialize(exec, globalObject(), nullptr);
+    }
+    
+    return detail;
+}
+
+} // namespace WebCore
+
index 0e14bcd..be645c0 100644 (file)
@@ -46,8 +46,12 @@ namespace WebCore {
 
 JSValue JSMessageEvent::data(ExecState* exec) const
 {
-    if (JSValue cachedValue = m_data.get())
-        return cachedValue;
+    if (JSValue cachedValue = m_data.get()) {
+        // We cannot use a cached object if we are in a different world than the one it was created in.
+        if (!cachedValue.isObject() || &worldForDOMObject(cachedValue.getObject()) == &currentWorld(exec))
+            return cachedValue;
+        ASSERT_NOT_REACHED();
+    }
 
     MessageEvent& event = impl();
     JSValue result;
@@ -56,8 +60,19 @@ JSValue JSMessageEvent::data(ExecState* exec) const
         Deprecated::ScriptValue scriptValue = event.dataAsScriptValue();
         if (scriptValue.hasNoValue())
             result = jsNull();
-        else
-            result = scriptValue.jsValue();
+        else {
+            JSValue dataValue = scriptValue.jsValue();
+            // We need to make sure MessageEvents do not leak objects in their state property across isolated DOM worlds.
+            // Ideally, we would check that the worlds have different privileges but that's not possible yet.
+            if (dataValue.isObject() && &worldForDOMObject(dataValue.getObject()) != &currentWorld(exec)) {
+                RefPtr<SerializedScriptValue> serializedValue = event.trySerializeData(exec);
+                if (serializedValue)
+                    result = serializedValue->deserialize(exec, globalObject(), nullptr);
+                else
+                    result = jsNull();
+            } else
+                result = dataValue;
+        }
         break;
     }
 
index 95aee34..502e48b 100644 (file)
@@ -49,14 +49,29 @@ static const JSValue& cacheState(ExecState* exec, JSPopStateEvent* event, const
 JSValue JSPopStateEvent::state(ExecState* exec) const
 {
     JSValue cachedValue = m_state.get();
-    if (!cachedValue.isEmpty())
-        return cachedValue;
+    if (!cachedValue.isEmpty()) {
+        // We cannot use a cached object if we are in a different world than the one it was created in.
+        if (!cachedValue.isObject() || &worldForDOMObject(cachedValue.getObject()) == &currentWorld(exec))
+            return cachedValue;
+        ASSERT_NOT_REACHED();
+    }
 
     PopStateEvent& event = impl();
 
-    if (!event.state().hasNoValue())
-        return cacheState(exec, const_cast<JSPopStateEvent*>(this), event.state().jsValue());
-
+    if (!event.state().hasNoValue()) {
+        // We need to make sure a PopStateEvent does not leak objects in its state property across isolated DOM worlds.
+        // Ideally, we would check that the worlds have different privileges but that's not possible yet.
+        JSValue state = event.state().jsValue();
+        if (state.isObject() && &worldForDOMObject(state.getObject()) != &currentWorld(exec)) {
+            if (RefPtr<SerializedScriptValue> serializedValue = event.trySerializeState(exec))
+                state = serializedValue->deserialize(exec, globalObject(), nullptr);
+            else
+                state = jsNull();
+        }
+        
+        return cacheState(exec, const_cast<JSPopStateEvent*>(this), state);
+    }
+    
     History* history = event.history();
     if (!history || !event.serializedState())
         return cacheState(exec, const_cast<JSPopStateEvent*>(this), jsNull());
index f2fce80..3e7f507 100644 (file)
@@ -51,13 +51,24 @@ CustomEvent::~CustomEvent()
 
 void CustomEvent::initCustomEvent(const AtomicString& type, bool canBubble, bool cancelable, const Deprecated::ScriptValue& detail)
 {
-    ASSERT(!m_serializedScriptValue.get());
     if (dispatched())
         return;
 
     initEvent(type, canBubble, cancelable);
 
     m_detail = detail;
+    m_serializedDetail = nullptr;
+    m_triedToSerialize = false;
+}
+
+RefPtr<SerializedScriptValue> CustomEvent::trySerializeDetail(JSC::ExecState* exec)
+{
+    if (!m_serializedDetail && !m_triedToSerialize) {
+        m_serializedDetail = SerializedScriptValue::create(exec, m_detail.jsValue(), nullptr, nullptr, NonThrowing);
+        m_triedToSerialize = true;
+    }
+    
+    return m_serializedDetail;
 }
 
 EventInterface CustomEvent::eventInterface() const
index 9940fb2..228f026 100644 (file)
@@ -57,14 +57,16 @@ public:
     virtual EventInterface eventInterface() const override;
 
     const Deprecated::ScriptValue& detail() const { return m_detail; }
-    PassRefPtr<SerializedScriptValue> serializedScriptValue() { return m_serializedScriptValue; }
+    
+    RefPtr<SerializedScriptValue> trySerializeDetail(JSC::ExecState*);
 
 private:
     CustomEvent();
     CustomEvent(const AtomicString& type, const CustomEventInit& initializer);
 
     Deprecated::ScriptValue m_detail;
-    RefPtr<SerializedScriptValue> m_serializedScriptValue;
+    RefPtr<SerializedScriptValue> m_serializedDetail;
+    bool m_triedToSerialize { false };
 };
 
 } // namespace WebCore
index af704b3..12e0ec3 100644 (file)
 [
     ConstructorTemplate=Event,
 ] interface CustomEvent : Event {
-    [InitializedByEventConstructor] readonly attribute any detail;
+    [InitializedByEventConstructor, CustomGetter] readonly attribute any detail;
 
-    void initCustomEvent([Default=Undefined] optional DOMString typeArg, 
-                         [Default=Undefined] optional boolean canBubbleArg, 
-                         [Default=Undefined] optional boolean cancelableArg, 
+    void initCustomEvent([Default=Undefined] optional DOMString typeArg,
+                         [Default=Undefined] optional boolean canBubbleArg,
+                         [Default=Undefined] optional boolean cancelableArg,
                          [Default=Undefined] optional any detailArg);
 };
 
index 11d2c52..d3c34bf 100644 (file)
@@ -118,6 +118,8 @@ void MessageEvent::initMessageEvent(const AtomicString& type, bool canBubble, bo
 
     m_dataType = DataTypeScriptValue;
     m_dataAsScriptValue = data;
+    m_dataAsSerializedScriptValue = nullptr;
+    m_triedToSerialize = false;
     m_origin = origin;
     m_lastEventId = lastEventId;
     m_source = source;
@@ -138,6 +140,18 @@ void MessageEvent::initMessageEvent(const AtomicString& type, bool canBubble, bo
     m_source = source;
     m_ports = WTF::move(ports);
 }
+    
+RefPtr<SerializedScriptValue> MessageEvent::trySerializeData(JSC::ExecState* exec)
+{
+    ASSERT(!m_dataAsScriptValue.hasNoValue());
+    
+    if (!m_dataAsSerializedScriptValue && !m_triedToSerialize) {
+        m_dataAsSerializedScriptValue = SerializedScriptValue::create(exec, m_dataAsScriptValue.jsValue(), nullptr, nullptr, NonThrowing);
+        m_triedToSerialize = true;
+    }
+    
+    return m_dataAsSerializedScriptValue;
+}
 
 // FIXME: Remove this when we have custom ObjC binding support.
 SerializedScriptValue* MessageEvent::data() const
index 9eb39dd..207a72e 100644 (file)
@@ -114,6 +114,8 @@ public:
     Blob* dataAsBlob() const { ASSERT(m_dataType == DataTypeBlob); return m_dataAsBlob.get(); }
     ArrayBuffer* dataAsArrayBuffer() const { ASSERT(m_dataType == DataTypeArrayBuffer); return m_dataAsArrayBuffer.get(); }
 
+    RefPtr<SerializedScriptValue> trySerializeData(JSC::ExecState*);
+    
 private:
     MessageEvent();
     MessageEvent(const AtomicString&, const MessageEventInit&);
@@ -127,6 +129,7 @@ private:
     DataType m_dataType;
     Deprecated::ScriptValue m_dataAsScriptValue;
     RefPtr<SerializedScriptValue> m_dataAsSerializedScriptValue;
+    bool m_triedToSerialize { false };
     String m_dataAsString;
     RefPtr<Blob> m_dataAsBlob;
     RefPtr<ArrayBuffer> m_dataAsArrayBuffer;
index dffa43d..5742a11 100644 (file)
@@ -29,7 +29,6 @@
 
 #include "EventNames.h"
 #include "History.h"
-#include "SerializedScriptValue.h"
 #include <runtime/JSCInlines.h>
 
 namespace WebCore {
@@ -79,6 +78,18 @@ Ref<PopStateEvent> PopStateEvent::create(const AtomicString& type, const PopStat
     return adoptRef(*new PopStateEvent(type, initializer));
 }
 
+RefPtr<SerializedScriptValue> PopStateEvent::trySerializeState(JSC::ExecState* exec)
+{
+    ASSERT(!m_state.hasNoValue());
+    
+    if (!m_serializedState && !m_triedToSerialize) {
+        m_serializedState = SerializedScriptValue::create(exec, m_state.jsValue(), nullptr, nullptr, NonThrowing);
+        m_triedToSerialize = true;
+    }
+    
+    return m_serializedState;
+}
+
 EventInterface PopStateEvent::eventInterface() const
 {
     return PopStateEventInterfaceType;
index a1e1940..c63b34a 100644 (file)
@@ -28,6 +28,7 @@
 #define PopStateEvent_h
 
 #include "Event.h"
+#include "SerializedScriptValue.h"
 #include <bindings/ScriptValue.h>
 
 namespace WebCore {
@@ -48,7 +49,10 @@ public:
     static Ref<PopStateEvent> create(PassRefPtr<SerializedScriptValue>, PassRefPtr<History>);
     static Ref<PopStateEvent> create(const AtomicString&, const PopStateEventInit&);
 
-    PassRefPtr<SerializedScriptValue> serializedState() const { return m_serializedState; }
+    PassRefPtr<SerializedScriptValue> serializedState() const { ASSERT(m_serializedState); return m_serializedState; }
+    
+    RefPtr<SerializedScriptValue> trySerializeState(JSC::ExecState*);
+    
     const Deprecated::ScriptValue& state() const { return m_state; }
     History* history() const { return m_history.get(); }
 
@@ -61,6 +65,7 @@ private:
 
     Deprecated::ScriptValue m_state;
     RefPtr<SerializedScriptValue> m_serializedState;
+    bool m_triedToSerialize { false };
     RefPtr<History> m_history;
 };
 
index 83a6792..5839524 100644 (file)
@@ -2357,6 +2357,16 @@ PassRefPtr<SerializedScriptValue> Internals::deserializeBuffer(PassRefPtr<ArrayB
     return SerializedScriptValue::adopt(bytes);
 }
 
+bool Internals::isFromCurrentWorld(Deprecated::ScriptValue value) const
+{
+    ASSERT(!value.hasNoValue());
+    
+    JSC::ExecState* exec = contextDocument()->vm().topCallFrame;
+    if (!value.isObject() || &worldForDOMObject(value.jsValue().getObject()) == &currentWorld(exec))
+        return true;
+    return false;
+}
+
 void Internals::setUsesOverlayScrollbars(bool enabled)
 {
     WebCore::Settings::setUsesOverlayScrollbars(enabled);
index 87bf6a9..4a48fa3 100644 (file)
@@ -324,6 +324,8 @@ public:
     PassRefPtr<ArrayBuffer> serializeObject(PassRefPtr<SerializedScriptValue>) const;
     PassRefPtr<SerializedScriptValue> deserializeBuffer(PassRefPtr<ArrayBuffer>) const;
 
+    bool isFromCurrentWorld(Deprecated::ScriptValue) const;
+
     void setUsesOverlayScrollbars(bool enabled);
 
     String getCurrentCursorInfo(ExceptionCode&);
index 60e637d..d07ebbf 100644 (file)
@@ -311,6 +311,8 @@ enum ResourceLoadPriority {
     SerializedScriptValue deserializeBuffer(ArrayBuffer buffer);
     ArrayBuffer serializeObject(SerializedScriptValue obj);
 
+    boolean isFromCurrentWorld(any obj);
+
     void setUsesOverlayScrollbars(boolean enabled);
 
     void forceReload(boolean endToEnd);