Added: trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents-expected.txt (0 => 152707)
--- trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents-expected.txt 2013-07-16 06:55:31 UTC (rev 152707)
@@ -0,0 +1,11 @@
+CONSOLE MESSAGE: line 12: InvalidStateError: DOM Exception 11: An attempt was made to use an object that is not, or is no longer, usable.
+Detaching a Range while deleteContents() is running should not cause a crash.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The browser did not crash.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents.html (0 => 152707)
--- trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents.html (rev 0)
+++ trunk/LayoutTests/fast/dom/Range/detach-range-during-deletecontents.html 2013-07-16 06:55:31 UTC (rev 152707)
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script>
+description('Detaching a Range while deleteContents() is running should not cause a crash.');
+
+window.jsTestIsAsync = true;
+
+window.addEventListener('message', function (event) {
+ if (event.data ="" 'done') {
+ testPassed('The browser did not crash.');
+ document.body.removeChild(iframe);
+ finishJSTest();
+ }
+}, false);
+
+var iframe = document.createElement('iframe');
+iframe.src = '';
+document.body.appendChild(iframe);
+</script>
+</body>
+<script src=""
+</html>
Added: trunk/LayoutTests/fast/dom/Range/resources/detach-range-during-deletecontents-iframe.xhtml (0 => 152707)
--- trunk/LayoutTests/fast/dom/Range/resources/detach-range-during-deletecontents-iframe.xhtml (rev 0)
+++ trunk/LayoutTests/fast/dom/Range/resources/detach-range-during-deletecontents-iframe.xhtml 2013-07-16 06:55:31 UTC (rev 152707)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!-- This file is intentionally malformed. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<script>
+<![CDATA[
+var done = false;
+var range = null;
+function expandAndDeleteRange()
+{
+ range = document.createRange();
+ range.expand("sentence");
+ range.deleteContents();
+}
+var repeat = 0;
+function detach()
+{
+ if (done)
+ return;
+ ++repeat;
+ if (repeat >= 2) {
+ done = true;
+ document.removeEventListener("DOMSubtreeModified", listener, true);
+ window.parent.postMessage("done", "*");
+ return;
+ }
+ range.detach();
+}
+
+var firstCall = true;
+function listener()
+{
+ if (firstCall) {
+ firstCall = false;
+ expandAndDeleteRange();
+ } else
+ detach();
+}
+document.addEventListener("DOMSubtreeModified", listener, true);
+
+document.addEventListener("DOMContentLoaded", expandAndDeleteRange);
+]]>
+</script>
Modified: trunk/Source/WebCore/dom/Range.cpp (152706 => 152707)
--- trunk/Source/WebCore/dom/Range.cpp 2013-07-16 06:32:43 UTC (rev 152706)
+++ trunk/Source/WebCore/dom/Range.cpp 2013-07-16 06:55:31 UTC (rev 152707)
@@ -693,9 +693,13 @@
return fragment;
}
+ // Since mutation events can modify the range during the process, the boundary points need to be saved.
+ RangeBoundaryPoint originalStart(m_start);
+ RangeBoundaryPoint originalEnd(m_end);
+
// what is the highest node that partially selects the start / end of the range?
- RefPtr<Node> partialStart = highestAncestorUnderCommonRoot(m_start.container(), commonRoot.get());
- RefPtr<Node> partialEnd = highestAncestorUnderCommonRoot(m_end.container(), commonRoot.get());
+ RefPtr<Node> partialStart = highestAncestorUnderCommonRoot(originalStart.container(), commonRoot.get());
+ RefPtr<Node> partialEnd = highestAncestorUnderCommonRoot(originalEnd.container(), commonRoot.get());
// Start and end containers are different.
// There are three possibilities here:
@@ -718,22 +722,22 @@
// after any DOM mutation event, at various stages below. See webkit bug 60350.
RefPtr<Node> leftContents;
- if (m_start.container() != commonRoot && commonRoot->contains(m_start.container())) {
- leftContents = processContentsBetweenOffsets(action, 0, m_start.container(), m_start.offset(), lengthOfContentsInNode(m_start.container()), ec);
- leftContents = processAncestorsAndTheirSiblings(action, m_start.container(), ProcessContentsForward, leftContents, commonRoot.get(), ec);
+ if (originalStart.container() != commonRoot && commonRoot->contains(originalStart.container())) {
+ leftContents = processContentsBetweenOffsets(action, 0, originalStart.container(), originalStart.offset(), lengthOfContentsInNode(originalStart.container()), ec);
+ leftContents = processAncestorsAndTheirSiblings(action, originalStart.container(), ProcessContentsForward, leftContents, commonRoot.get(), ec);
}
RefPtr<Node> rightContents;
- if (m_end.container() != commonRoot && commonRoot->contains(m_end.container())) {
- rightContents = processContentsBetweenOffsets(action, 0, m_end.container(), 0, m_end.offset(), ec);
- rightContents = processAncestorsAndTheirSiblings(action, m_end.container(), ProcessContentsBackward, rightContents, commonRoot.get(), ec);
+ if (m_end.container() != commonRoot && commonRoot->contains(originalEnd.container())) {
+ rightContents = processContentsBetweenOffsets(action, 0, originalEnd.container(), 0, originalEnd.offset(), ec);
+ rightContents = processAncestorsAndTheirSiblings(action, originalEnd.container(), ProcessContentsBackward, rightContents, commonRoot.get(), ec);
}
// delete all children of commonRoot between the start and end container
- RefPtr<Node> processStart = childOfCommonRootBeforeOffset(m_start.container(), m_start.offset(), commonRoot.get());
- if (processStart && m_start.container() != commonRoot) // processStart contains nodes before m_start.
+ RefPtr<Node> processStart = childOfCommonRootBeforeOffset(originalStart.container(), originalStart.offset(), commonRoot.get());
+ if (processStart && originalStart.container() != commonRoot) // processStart contains nodes before m_start.
processStart = processStart->nextSibling();
- RefPtr<Node> processEnd = childOfCommonRootBeforeOffset(m_end.container(), m_end.offset(), commonRoot.get());
+ RefPtr<Node> processEnd = childOfCommonRootBeforeOffset(originalEnd.container(), originalEnd.offset(), commonRoot.get());
// Collapse the range, making sure that the result is not within a node that was partially selected.
if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) {
Modified: trunk/Source/WebCore/dom/RangeBoundaryPoint.h (152706 => 152707)
--- trunk/Source/WebCore/dom/RangeBoundaryPoint.h 2013-07-16 06:32:43 UTC (rev 152706)
+++ trunk/Source/WebCore/dom/RangeBoundaryPoint.h 2013-07-16 06:55:31 UTC (rev 152707)
@@ -35,6 +35,8 @@
public:
explicit RangeBoundaryPoint(PassRefPtr<Node> container);
+ explicit RangeBoundaryPoint(const RangeBoundaryPoint&);
+
const Position toPosition() const;
Node* container() const;
@@ -56,7 +58,7 @@
private:
static const int invalidOffset = -1;
-
+
RefPtr<Node> m_containerNode;
mutable int m_offsetInContainer;
RefPtr<Node> m_childBeforeBoundary;
@@ -70,6 +72,13 @@
ASSERT(m_containerNode);
}
+inline RangeBoundaryPoint::RangeBoundaryPoint(const RangeBoundaryPoint& other)
+ : m_containerNode(other.container())
+ , m_offsetInContainer(other.offset())
+ , m_childBeforeBoundary(other.childBefore())
+{
+}
+
inline Node* RangeBoundaryPoint::container() const
{
return m_containerNode.get();