2011-01-06 Ryosuke Niwa <rniwa@webkit.org>
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Jan 2011 02:15:10 +0000 (02:15 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Jan 2011 02:15:10 +0000 (02:15 +0000)
        Reviewed by Adam Barth.

        onbeforeunload is broken for framesets
        https://bugs.webkit.org/show_bug.cgi?id=19418

        Added beforeunload event support for sub frames. WebKit's implementation tries to match
        that of Internet Explorer as much as possible. beforeunload event is fired for each and
        every descendent of a frame that is about to navigate.

        When a value other than null is returned by a beforeunload handler, a confirmation dialog
        is shown for each handler (calls chrome's runBeforeUnloadConfirmPanel) just like it is done
        for main frames.

        In addition, navigation is forbidden while beforeunload handlers are being called.
        Setting values to location.href, location.reload, and other means of navigations are thus
        ignored while beforeunload event handler is being ran, matching Internet Explorer's behavior.

        Because navigation needs to prevented globally, NavigationDisablerForBeforeUnload is added to
        NavigationScheduler.h, which is instantiated as a RAII object in FrameLoader::shouldClose.

        Tests: fast/events/before-unload-adopt-subframe-to-outside.html
               fast/events/before-unload-adopt-within-subframes.html
               fast/events/before-unload-forbidden-navigation.html
               fast/events/before-unload-in-multiple-subframes.html
               fast/events/before-unload-in-subframe.html
               fast/events/before-unload-javascript-navigation.html
               fast/events/before-unload-remove-and-add-subframe.html
               fact/events/before-unload-remove-itself.html
               fast/events/before-unload-with-subframes.html

       * loader/FrameLoader.cpp:
       (WebCore::FrameLoader::shouldClose): Calls fireBeforeUnloadEvent on m_frame and m_frame's
       descendents. Returns true only if every call to fireBeforeUnloadEvent returned true.
       (WebCore::FrameLoader::fireBeforeUnloadEvent): Fires a beforeunload event and calls
       chrome's runBeforeUnloadConfirmPanel as needed.
       (WebCore::FrameLoader::continueLoadAfterNavigationPolicy): Calls shouldClose for all frames.
       * loader/FrameLoader.h:
       * loader/NavigationScheduler.cpp:
       (WebCore::NavigationScheduler::shouldScheduleNavigation): Checks the nullity of Page and calls
       NavigationDisablerForBeforeUnload::isNavigationAllowed when url is not javascript scheme.
       (WebCore::NavigationScheduler::scheduleRedirect): Calls shouldScheduleNavigation.
       (WebCore::NavigationScheduler::scheduleLocationChange): Ditto.
       (WebCore::NavigationScheduler::scheduleRefresh): Ditto.
       (WebCore::NavigationScheduler::scheduleHistoryNavigation): Ditto.
       * loader/NavigationScheduler.h:
       (WebCore::NavigationDisablerForBeforeUnload::NavigationDisablerForBeforeUnload): Disables navigation.
       (WebCore::NavigationDisablerForBeforeUnload::~NavigationDisablerForBeforeUnload): Enables navigation
       when called on the last instance of NavigationDisablerForBeforeUnload.
       (WebCore::NavigationDisablerForBeforeUnload::isNavigationAllowed): Returns true if there are no instance
       of NavigationDisablerForBeforeUnload left on the stack.
2011-01-06  Ryosuke Niwa  <rniwa@webkit.org>

        Reviewed by Adam Barth.

        onbeforeunload is broken for framesets
        https://bugs.webkit.org/show_bug.cgi?id=19418

        Added tests to ensure WebKit fires beforeunload events for subframes,
        and disallows navigation except that of javascript scheme while beforeunload event
        handlers are being called.

        Also added a test to ensure WebKit fires beforeunload event for subframes exactly
        once even if a subframe was moved around within a beforeunload event handler.

        A test that ensures beforeunload event is not fired for an iframe if the iframe
        was added or removed within a beforeunload event handler is also added.

        Furthermore, a test to ensure WebKit does not fire a beforeunload event to an iframe
        that has been adopted by a document outside of the unloading document is added.

        * fast/events/before-unload-adopt-subframe-to-outside-expected.txt: Added.
        * fast/events/before-unload-adopt-subframe-to-outside.html: Added.
        * fast/events/before-unload-adopt-within-subframes-expected.txt: Added.
        * fast/events/before-unload-adopt-within-subframes.html: Added.
        * fast/events/before-unload-forbidden-navigation-expected.txt: Added.
        * fast/events/before-unload-forbidden-navigation.html: Added.
        * fast/events/before-unload-in-multiple-subframes-expected.txt: Added.
        * fast/events/before-unload-in-multiple-subframes.html: Added.
        * fast/events/before-unload-in-subframe-expected.txt: Added.
        * fast/events/before-unload-in-subframe.html: Added.
        * fast/events/before-unload-javascript-navigation-expected.txt: Added.
        * fast/events/before-unload-javascript-navigation.html: Added.
        * fast/events/before-unload-remove-and-add-subframe-expected.txt: Added.
        * fast/events/before-unload-remove-and-add-subframe.html: Added.
        * fact/events/before-unload-remove-itself-expected.txt: Added.
        * fact/events/before-unload-remove-itself.html: Added.
        * fast/events/before-unload-with-subframes-expected.txt: Added.
        * fast/events/before-unload-with-subframes.html: Added.
        * fast/events/resources/before-unload-in-subframe-child.html: Added.
        * fast/events/resources/before-unload-in-subframe-destination.html: Added.
        * fast/events/resources/before-unload-in-subframe-fail.html: Added.
        * fast/events/resources/before-unload-with-subframes-parent.html: Added.

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

29 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/before-unload-adopt-subframe-to-outside-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-adopt-subframe-to-outside.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-adopt-within-subframes-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-adopt-within-subframes.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-forbidden-navigation-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-forbidden-navigation.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-in-multiple-subframes-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-in-multiple-subframes.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-in-subframe-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-in-subframe.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-javascript-navigation-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-javascript-navigation.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-remove-and-add-subframe-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-remove-and-add-subframe.html [new file with mode: 0755]
LayoutTests/fast/events/before-unload-remove-itself-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-remove-itself.html [new file with mode: 0644]
LayoutTests/fast/events/before-unload-with-subframes-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/before-unload-with-subframes.html [new file with mode: 0644]
LayoutTests/fast/events/resources/before-unload-in-subframe-child.html [new file with mode: 0644]
LayoutTests/fast/events/resources/before-unload-in-subframe-destination.html [new file with mode: 0644]
LayoutTests/fast/events/resources/before-unload-in-subframe-fail.html [new file with mode: 0644]
LayoutTests/fast/events/resources/before-unload-with-subframes-parent.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/loader/FrameLoader.cpp
WebCore/loader/FrameLoader.h
WebCore/loader/NavigationScheduler.cpp
WebCore/loader/NavigationScheduler.h

index efdc335..41bb61d 100644 (file)
@@ -1,3 +1,46 @@
+2011-01-06  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Adam Barth.
+
+        onbeforeunload is broken for framesets
+        https://bugs.webkit.org/show_bug.cgi?id=19418
+
+        Added tests to ensure WebKit fires beforeunload events for subframes,
+        and disallows navigation except that of javascript scheme while beforeunload event
+        handlers are being called.
+
+        Also added a test to ensure WebKit fires beforeunload event for subframes exactly
+        once even if a subframe was moved around within a beforeunload event handler.
+
+        A test that ensures beforeunload event is not fired for an iframe if the iframe
+        was added or removed within a beforeunload event handler is also added.
+
+        Furthermore, a test to ensure WebKit does not fire a beforeunload event to an iframe
+        that has been adopted by a document outside of the unloading document is added.
+
+        * fast/events/before-unload-adopt-subframe-to-outside-expected.txt: Added.
+        * fast/events/before-unload-adopt-subframe-to-outside.html: Added.
+        * fast/events/before-unload-adopt-within-subframes-expected.txt: Added.
+        * fast/events/before-unload-adopt-within-subframes.html: Added.
+        * fast/events/before-unload-forbidden-navigation-expected.txt: Added.
+        * fast/events/before-unload-forbidden-navigation.html: Added.
+        * fast/events/before-unload-in-multiple-subframes-expected.txt: Added.
+        * fast/events/before-unload-in-multiple-subframes.html: Added.
+        * fast/events/before-unload-in-subframe-expected.txt: Added.
+        * fast/events/before-unload-in-subframe.html: Added.
+        * fast/events/before-unload-javascript-navigation-expected.txt: Added.
+        * fast/events/before-unload-javascript-navigation.html: Added.
+        * fast/events/before-unload-remove-and-add-subframe-expected.txt: Added.
+        * fast/events/before-unload-remove-and-add-subframe.html: Added.
+        * fact/events/before-unload-remove-itself-expected.txt: Added.
+        * fact/events/before-unload-remove-itself.html: Added.
+        * fast/events/before-unload-with-subframes-expected.txt: Added.
+        * fast/events/before-unload-with-subframes.html: Added.
+        * fast/events/resources/before-unload-in-subframe-child.html: Added.
+        * fast/events/resources/before-unload-in-subframe-destination.html: Added.
+        * fast/events/resources/before-unload-in-subframe-fail.html: Added.
+        * fast/events/resources/before-unload-with-subframes-parent.html: Added.
+
 2011-01-07  Martin Robinson  <mrobinson@igalia.com>
 
         Reviewed by Mihai Parparita.
diff --git a/LayoutTests/fast/events/before-unload-adopt-subframe-to-outside-expected.txt b/LayoutTests/fast/events/before-unload-adopt-subframe-to-outside-expected.txt
new file mode 100644 (file)
index 0000000..b2bf428
--- /dev/null
@@ -0,0 +1,9 @@
+This test ensures beforeunload event does NOT fire for a subframe that has been adopted by a document outside of the frame hierarchy that is about to unload.
+
+PASS: fired on parent
+PASS: fired on a
+adopting
+adopted
+PASS: fired on b
+DONE
diff --git a/LayoutTests/fast/events/before-unload-adopt-subframe-to-outside.html b/LayoutTests/fast/events/before-unload-adopt-subframe-to-outside.html
new file mode 100644 (file)
index 0000000..957cb51
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures beforeunload event does NOT fire for a subframe that has been adopted by a document outside of the frame hierarchy that is about to unload.</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function createFrame(id, parent) {
+    var iframe = document.createElement('iframe');
+    if (parent)
+        parent.contentDocument.body.appendChild(iframe);
+    else
+        document.body.appendChild(iframe);
+    if (!iframe.contentDocument.body)
+        iframe.contentDocument.appendChild(iframe.contentDocument.createElement('body'));
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createTextNode(id));    
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createElement('br'));
+    iframe.contentWindow.onbeforeunload = function () { fired(iframe.contentWindow, id); return null; }
+    iframe.style.width = '70%';
+    iframe.style.height = '40%';
+    return iframe;
+}
+
+function log(message) {
+    var log = document.getElementById('log');
+    log.innerHTML += message + '\n';
+}
+
+var expectedOrder = ['parent', 'a', 'b'];
+var i = 0;
+
+function fired(contentWindow, id) {
+    if (expectedOrder[i] == id)
+        log('PASS: fired on ' + id);
+    else
+        log('FAIL: fired on ' + id + ' but expected on ' + expectedOrder[i]);
+    i++;
+
+    if (contentWindow == a.contentWindow) {
+        log('adopting');
+        document.body.appendChild(document.adoptNode(adoptee));
+        log('adopted');
+    }
+}
+
+var container = createFrame('parent');
+var a = createFrame('a', container);
+var adoptee = createFrame('adoptee', a);
+var b = createFrame('b', container);
+
+container.onload = function () {
+    if (i == expectedOrder.length)
+        log('DONE');
+    else
+        log('Received ' + i + ' events but expected ' + expectedOrder.length);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+container.src = 'resources/before-unload-in-subframe-destination.html';
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-adopt-within-subframes-expected.txt b/LayoutTests/fast/events/before-unload-adopt-within-subframes-expected.txt
new file mode 100644 (file)
index 0000000..d4bc9a5
--- /dev/null
@@ -0,0 +1,10 @@
+This test ensures beforeunload event fires exactly once in a subframe even if the frame was adopted to a frame that appears later in the tree.
+
+PASS: fired on parent
+PASS: fired on a
+PASS: fired on adoptee
+adopting
+adopted
+PASS: fired on b
+DONE
+
diff --git a/LayoutTests/fast/events/before-unload-adopt-within-subframes.html b/LayoutTests/fast/events/before-unload-adopt-within-subframes.html
new file mode 100644 (file)
index 0000000..2b86f9d
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures beforeunload event fires exactly once in a subframe even if the frame was adopted to a frame that appears later in the tree.</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function createFrame(id, parent) {
+    var iframe = document.createElement('iframe');
+    if (parent)
+        parent.contentDocument.body.appendChild(iframe);
+    else
+        document.body.appendChild(iframe);
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createTextNode(id));    
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createElement('br'));
+    iframe.contentWindow.onbeforeunload = function () { fired(iframe.contentWindow, id); return null; }
+    iframe.style.width = '70%';
+    iframe.style.height = '40%';
+    return iframe;
+}
+
+function log(message) {
+    var log = document.getElementById('log');
+    log.innerHTML += message + '\n';
+}
+
+var expectedOrder = ['parent', 'a', 'adoptee', 'b'];
+var i = 0;
+
+function fired(contentWindow, id) {
+    if (expectedOrder[i] == id)
+        log('PASS: fired on ' + id);
+    else
+        log('FAIL: fired on ' + id + ' but expected on ' + expectedOrder[i]);
+    i++;
+
+    if (contentWindow == adoptee.contentWindow) {
+        log('adopting');
+        b.contentDocument.body.appendChild(b.contentDocument.adoptNode(adoptee));
+        log('adopted');
+    }
+}
+
+var container = createFrame('parent');
+var a = createFrame('a', container);
+var adoptee = createFrame('adoptee', a);
+var b = createFrame('b', container);
+
+container.onload = function () {
+    if (i == expectedOrder.length)
+        log('DONE');
+    else
+        log('Received ' + i + ' events but expected ' + expectedOrder.length);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+container.src = 'resources/before-unload-in-subframe-destination.html';
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-forbidden-navigation-expected.txt b/LayoutTests/fast/events/before-unload-forbidden-navigation-expected.txt
new file mode 100644 (file)
index 0000000..4f1a788
--- /dev/null
@@ -0,0 +1,9 @@
+This test ensures navigation is forbidden while beforeunload event is being fired. You should see PASS 1/2 and PASS 2/2 below:
+
+PASS 1/2
+
+
+--------
+Frame: '<!--framePath //<!--frame0-->-->'
+--------
+PASS: 2/2
diff --git a/LayoutTests/fast/events/before-unload-forbidden-navigation.html b/LayoutTests/fast/events/before-unload-forbidden-navigation.html
new file mode 100644 (file)
index 0000000..460db30
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures navigation is forbidden while beforeunload event is being fired. You should see PASS 1/2 and PASS 2/2 below:</p>
+<pre id="log">FAIL</pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.dumpChildFramesAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+
+function test(iframe) {
+    if (iframe.done) {
+        if (iframe.halfPassed) {
+            iframe.contentWindow.location.href = 'resources/before-unload-in-subframe-destination.html';            
+            iframe.halfPassed = false;
+        }
+        return;
+    }
+    iframe.done = true;
+    iframe.contentWindow.location.href = 'resources/before-unload-in-subframe-child.html';
+}
+
+function fired(contentWindow) {
+    location.href = 'resources/before-unload-in-subframe-fail.html';
+    contentWindow.location.href = 'resources/before-unload-in-subframe-fail.html';
+    log.innerHTML = 'PASS 1/2';
+    contentWindow.frameElement.halfPassed = true;
+}
+
+</script>
+<iframe onload="test(this);" src="resources/before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-in-multiple-subframes-expected.txt b/LayoutTests/fast/events/before-unload-in-multiple-subframes-expected.txt
new file mode 100644 (file)
index 0000000..bc3407f
--- /dev/null
@@ -0,0 +1,4 @@
+This tests beforeunload event in multiple subframes. You should see PASS below:
+
+PASS
+  
diff --git a/LayoutTests/fast/events/before-unload-in-multiple-subframes.html b/LayoutTests/fast/events/before-unload-in-multiple-subframes.html
new file mode 100644 (file)
index 0000000..055b4fa
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests beforeunload event in multiple subframes. You should see PASS below:</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+
+function test(iframe) {
+    if (iframe.done)
+        return;
+    iframe.done = true;
+    iframe.contentWindow.location.href = 'resources/before-unload-in-subframe-child.html';
+}
+
+var numberOfBeforeUnloadInSubframes = 0;
+var numberOfFrames = 3;
+
+function logError() {
+    log.innerHTML = 'FAIL: ' + numberOfBeforeUnloadInSubframes + ' beforeunload events are fired but expected ' + numberOfFrames + ' events';
+}
+
+logError();
+
+function fired(contentWindow) {
+    numberOfBeforeUnloadInSubframes++;
+    if (numberOfBeforeUnloadInSubframes == numberOfFrames) {
+        log.innerHTML = 'PASS';
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+    }
+    else
+        logError();
+    contentWindow.frameElement.fired = true;
+}
+
+</script>
+<iframe onload="test(this)" src="resources/before-unload-in-subframe-child.html"></iframe>
+<iframe onload="test(this)" src="resources/before-unload-in-subframe-child.html"></iframe>
+<iframe onload="test(this)" src="resources/before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-in-subframe-expected.txt b/LayoutTests/fast/events/before-unload-in-subframe-expected.txt
new file mode 100644 (file)
index 0000000..d16afd4
--- /dev/null
@@ -0,0 +1,9 @@
+This tests beforeunload event in subframes. You should see PASS below:
+
+PASS
+
+
+--------
+Frame: '<!--framePath //<!--frame0-->-->'
+--------
+Child loaded.
diff --git a/LayoutTests/fast/events/before-unload-in-subframe.html b/LayoutTests/fast/events/before-unload-in-subframe.html
new file mode 100644 (file)
index 0000000..b868a50
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests beforeunload event in subframes. You should see PASS below:</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.dumpChildFramesAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+var done = false;
+
+function test() {
+    if (done) {
+        // log's not having any content implies that load event was dispatched for the second time before beforeunload is dispatched.
+        if (!log.innerHTML.length)
+            log.innerHTML = 'FAIL: beforeunload event was never dispatched.\n';
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+        return;
+    }
+    done = true;
+    document.getElementsByTagName('iframe')[0].contentWindow.location.href = 'resources/before-unload-in-subframe-child.html';
+}
+
+function fired() {
+    if (!log.innerHTML.length)
+        log.innerHTML = 'PASS\n';
+    else
+        log.innerHTML = 'FAIL: beforeunload event was dispatched after the second load event.'
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+</script>
+<iframe onload="test()" src="resources/before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-javascript-navigation-expected.txt b/LayoutTests/fast/events/before-unload-javascript-navigation-expected.txt
new file mode 100644 (file)
index 0000000..c21a4c3
--- /dev/null
@@ -0,0 +1,4 @@
+This test ensures setting location.href with javascript scheme properly executes the script even while beforeunload event is being fired. You should see PASS 1/2 and PASS 2/2 below:
+
+PASS
+
diff --git a/LayoutTests/fast/events/before-unload-javascript-navigation.html b/LayoutTests/fast/events/before-unload-javascript-navigation.html
new file mode 100644 (file)
index 0000000..eed8b75
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures setting location.href with javascript scheme  properly executes the script even while beforeunload event is being fired. You should see PASS 1/2 and PASS 2/2 below:</p>
+<pre id="log">FAIL</pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+
+function test(iframe) {
+    if (iframe.done)
+        return;
+    iframe.done = true;
+    iframe.contentWindow.location.href = 'resources/before-unload-in-subframe-child.html';
+}
+
+function done() {
+    log.innerHTML = 'PASS';
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+function fired(contentWindow) {
+    location.href = 'javascript:top.done()';
+}
+
+</script>
+<iframe onload="test(this);" src="resources/before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-remove-and-add-subframe-expected.txt b/LayoutTests/fast/events/before-unload-remove-and-add-subframe-expected.txt
new file mode 100644 (file)
index 0000000..b21dd8c
--- /dev/null
@@ -0,0 +1,7 @@
+This test ensures beforeunload event does not fire for subframes that has been removed from the DOM within a beforeunload event handler. Also ensures the event doesn't fire for subframes added within a beforeunload event handler. The latter behavior matches MSIE.
+
+PASS: fired on parent
+PASS: fired on a
+PASS: fired on c
+DONE
+
diff --git a/LayoutTests/fast/events/before-unload-remove-and-add-subframe.html b/LayoutTests/fast/events/before-unload-remove-and-add-subframe.html
new file mode 100755 (executable)
index 0000000..82c8dcd
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures beforeunload event does not fire for subframes that has been removed from the DOM within a beforeunload event handler. Also ensures the event doesn't fire for subframes added within a beforeunload event handler. The latter behavior matches MSIE.</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+function createFrame(id, parent) {
+    var iframe = document.createElement('iframe');
+    iframe.id = id;
+    if (parent)
+        parent.contentDocument.body.appendChild(iframe);
+    else
+        document.body.appendChild(iframe);
+    if (!iframe.contentDocument.body)
+        iframe.contentDocument.appendChild(iframe.contentDocument.createElement('body'));
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createTextNode(id));    
+    iframe.contentDocument.body.appendChild(iframe.contentDocument.createElement('br'));
+    iframe.contentWindow.onbeforeunload = function () { fired(iframe.contentWindow, id); return null; }
+    iframe.style.width = '70%';
+    iframe.style.height = '40%';
+    return iframe;
+}
+
+function log(message) {
+    var log = document.getElementById('log');
+    log.innerHTML += message + '\n';
+}
+
+var expectedOrder = ['parent', 'a', 'c'];
+var i = 0;
+
+function fired(contentWindow, id) {
+    if (expectedOrder[i] == id)
+        log('PASS: fired on ' + id);
+    else
+        log('FAIL: fired on ' + id + ' but expected on ' + expectedOrder[i]);
+    i++;
+
+    if (contentWindow == a.contentWindow) {
+        b.parentNode.removeChild(b);
+        createFrame('d', container);
+    }
+}
+
+var container = createFrame('parent');
+var a = createFrame('a', container);
+var b = createFrame('b', container);
+var c = createFrame('c', container);
+
+container.onload = function () {
+    if (i == expectedOrder.length)
+        log('DONE');
+    else
+        log('Received ' + i + ' events but expected ' + expectedOrder.length);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+container.src = 'resources/before-unload-in-subframe-destination.html';
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-remove-itself-expected.txt b/LayoutTests/fast/events/before-unload-remove-itself-expected.txt
new file mode 100644 (file)
index 0000000..8fd1170
--- /dev/null
@@ -0,0 +1,4 @@
+This test ensures a beforeunload event handler can safely remove the frame to which the event is fired. You should see PASS below:
+
+PASS
+
diff --git a/LayoutTests/fast/events/before-unload-remove-itself.html b/LayoutTests/fast/events/before-unload-remove-itself.html
new file mode 100644 (file)
index 0000000..23a23e2
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures a beforeunload event handler can safely remove the frame to which the event is fired. You should see PASS below:</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.dumpChildFramesAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+var done = false;
+
+function test() {
+    if (done) {
+        // log's not having any content implies that load event was dispatched for the second time before beforeunload is dispatched.
+        if (!log.innerHTML.length)
+            log.innerHTML = 'FAIL: beforeunload event was never dispatched.\n';
+        else
+            log.innerHTML = 'FAIL: beforeunload event handler did not remove the frame.\n';
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+        return;
+    }
+    done = true;
+    document.getElementsByTagName('iframe')[0].contentWindow.location.href = 'resources/before-unload-in-subframe-child.html';
+}
+
+function fired() {
+    document.body.removeChild(document.body.getElementsByTagName('iframe')[0]);
+    log.innerHTML = 'PASS\n';
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+</script>
+<iframe onload="test()" src="resources/before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/before-unload-with-subframes-expected.txt b/LayoutTests/fast/events/before-unload-with-subframes-expected.txt
new file mode 100644 (file)
index 0000000..7212540
--- /dev/null
@@ -0,0 +1,9 @@
+This test ensures beforeunload event fires in all subframes when a parent frame is navigated. You should see PASS 1/2 and PASS 2/2 below:
+
+PASS 1/2
+
+
+--------
+Frame: '<!--framePath //<!--frame0-->-->'
+--------
+PASS: 2/2
diff --git a/LayoutTests/fast/events/before-unload-with-subframes.html b/LayoutTests/fast/events/before-unload-with-subframes.html
new file mode 100644 (file)
index 0000000..5dad945
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures beforeunload event fires in all subframes when a parent frame is navigated. You should see PASS 1/2 and PASS 2/2 below:</p>
+<pre id="log"></pre>
+<script>
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.dumpChildFramesAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var log = document.getElementById('log');
+var numberOfBeforeUnloadInSubframes = 0;
+var numberOfFrames = 3;
+
+function test(iframe) {
+    if (iframe.done) {
+        if (numberOfBeforeUnloadInSubframes == numberOfFrames)
+            layoutTestController.notifyDone();
+        return;
+    }
+    iframe.done = true;
+    iframe.contentWindow.location.href = 'resources/before-unload-in-subframe-destination.html';
+}
+
+function logError() {
+    log.innerHTML = 'FAIL: ' + numberOfBeforeUnloadInSubframes + ' beforeunload events are fired but expected ' + numberOfFrames + ' events';
+}
+
+logError();
+
+function fired(contentWindow) {
+    numberOfBeforeUnloadInSubframes++;
+    if (numberOfBeforeUnloadInSubframes == numberOfFrames)
+        log.innerHTML = 'PASS 1/2\n';
+    else
+        logError();
+    contentWindow.frameElement.fired = true;
+}
+
+</script>
+<iframe onload="test(this)" src="resources/before-unload-with-subframes-parent.html"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/resources/before-unload-in-subframe-child.html b/LayoutTests/fast/events/resources/before-unload-in-subframe-child.html
new file mode 100644 (file)
index 0000000..b78dee1
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body onbeforeunload="top.fired(window); return null;">
+Child loaded.
+<script>
+
+if (top.loaded)
+    top.loaded();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/resources/before-unload-in-subframe-destination.html b/LayoutTests/fast/events/resources/before-unload-in-subframe-destination.html
new file mode 100644 (file)
index 0000000..7c409cc
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+PASS: 2/2
+<script>
+
+if (window.layoutTestController)
+    layoutTestController.notifyDone();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/resources/before-unload-in-subframe-fail.html b/LayoutTests/fast/events/resources/before-unload-in-subframe-fail.html
new file mode 100644 (file)
index 0000000..bf339c8
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+FAIL
+<script>
+
+if (window.layoutTestController)
+    layoutTestController.notifyDone();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/events/resources/before-unload-with-subframes-parent.html b/LayoutTests/fast/events/resources/before-unload-with-subframes-parent.html
new file mode 100644 (file)
index 0000000..f1a2542
--- /dev/null
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="before-unload-in-subframe-child.html"></iframe>
+<iframe src="before-unload-in-subframe-child.html"></iframe>
+<iframe src="before-unload-in-subframe-child.html"></iframe>
+</body>
+</html>
index 30d219d..2722ce3 100644 (file)
@@ -1,3 +1,56 @@
+2011-01-06  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Adam Barth.
+
+        onbeforeunload is broken for framesets
+        https://bugs.webkit.org/show_bug.cgi?id=19418
+
+        Added beforeunload event support for sub frames. WebKit's implementation tries to match
+        that of Internet Explorer as much as possible. beforeunload event is fired for each and
+        every descendent of a frame that is about to navigate.
+
+        When a value other than null is returned by a beforeunload handler, a confirmation dialog
+        is shown for each handler (calls chrome's runBeforeUnloadConfirmPanel) just like it is done
+        for main frames.
+
+        In addition, navigation is forbidden while beforeunload handlers are being called.
+        Setting values to location.href, location.reload, and other means of navigations are thus
+        ignored while beforeunload event handler is being ran, matching Internet Explorer's behavior.
+
+        Because navigation needs to prevented globally, NavigationDisablerForBeforeUnload is added to
+        NavigationScheduler.h, which is instantiated as a RAII object in FrameLoader::shouldClose.
+
+        Tests: fast/events/before-unload-adopt-subframe-to-outside.html
+               fast/events/before-unload-adopt-within-subframes.html
+               fast/events/before-unload-forbidden-navigation.html
+               fast/events/before-unload-in-multiple-subframes.html
+               fast/events/before-unload-in-subframe.html
+               fast/events/before-unload-javascript-navigation.html
+               fast/events/before-unload-remove-and-add-subframe.html
+               fact/events/before-unload-remove-itself.html
+               fast/events/before-unload-with-subframes.html
+
+       * loader/FrameLoader.cpp:
+       (WebCore::FrameLoader::shouldClose): Calls fireBeforeUnloadEvent on m_frame and m_frame's
+       descendents. Returns true only if every call to fireBeforeUnloadEvent returned true.
+       (WebCore::FrameLoader::fireBeforeUnloadEvent): Fires a beforeunload event and calls
+       chrome's runBeforeUnloadConfirmPanel as needed.
+       (WebCore::FrameLoader::continueLoadAfterNavigationPolicy): Calls shouldClose for all frames.
+       * loader/FrameLoader.h:
+       * loader/NavigationScheduler.cpp:
+       (WebCore::NavigationScheduler::shouldScheduleNavigation): Checks the nullity of Page and calls
+       NavigationDisablerForBeforeUnload::isNavigationAllowed when url is not javascript scheme.
+       (WebCore::NavigationScheduler::scheduleRedirect): Calls shouldScheduleNavigation.
+       (WebCore::NavigationScheduler::scheduleLocationChange): Ditto.
+       (WebCore::NavigationScheduler::scheduleRefresh): Ditto.
+       (WebCore::NavigationScheduler::scheduleHistoryNavigation): Ditto.
+       * loader/NavigationScheduler.h:
+       (WebCore::NavigationDisablerForBeforeUnload::NavigationDisablerForBeforeUnload): Disables navigation.
+       (WebCore::NavigationDisablerForBeforeUnload::~NavigationDisablerForBeforeUnload): Enables navigation
+       when called on the last instance of NavigationDisablerForBeforeUnload.
+       (WebCore::NavigationDisablerForBeforeUnload::isNavigationAllowed): Returns true if there are no instance
+       of NavigationDisablerForBeforeUnload left on the stack.
+
 2011-01-07  Martin Robinson  <mrobinson@igalia.com>
 
         Build fix for GTK+.
index be10fe9..1d07372 100644 (file)
                6EBF0E5312A8929800DB1709 /* WebGLExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebGLExtension.h; path = canvas/WebGLExtension.h; sourceTree = "<group>"; };
                6EBF0E7412A9868800DB1709 /* JSOESTextureFloat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSOESTextureFloat.cpp; sourceTree = "<group>"; };
                6EBF0E7512A9868800DB1709 /* JSOESTextureFloat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSOESTextureFloat.h; sourceTree = "<group>"; };
-               93F1D5BE12D5335600832BEC /* JSWebKitLoseContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = JSWebKitLoseContext.cpp; path = JSWebKitLoseContext.cpp; sourceTree = "<group>"; };
-               93F1D5BF12D5335600832BEC /* JSWebKitLoseContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSWebKitLoseContext.h; path = JSWebKitLoseContext.h; sourceTree = "<group>"; };
                6EE8A77010F803F3005A4A24 /* JSWebGLContextAttributes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebGLContextAttributes.cpp; sourceTree = "<group>"; };
                6EE8A77110F803F3005A4A24 /* JSWebGLContextAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWebGLContextAttributes.h; sourceTree = "<group>"; };
                72626E010EF022FE00A07E20 /* FontFastPath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FontFastPath.cpp; sourceTree = "<group>"; };
                93F1D5B712D532C400832BEC /* WebKitLoseContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebKitLoseContext.cpp; path = canvas/WebKitLoseContext.cpp; sourceTree = "<group>"; };
                93F1D5B812D532C400832BEC /* WebKitLoseContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebKitLoseContext.h; path = canvas/WebKitLoseContext.h; sourceTree = "<group>"; };
                93F1D5B912D532C400832BEC /* WebKitLoseContext.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = WebKitLoseContext.idl; path = canvas/WebKitLoseContext.idl; sourceTree = "<group>"; };
+               93F1D5BE12D5335600832BEC /* JSWebKitLoseContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebKitLoseContext.cpp; sourceTree = "<group>"; };
+               93F1D5BF12D5335600832BEC /* JSWebKitLoseContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWebKitLoseContext.h; sourceTree = "<group>"; };
                93F6F1EA127F70B10055CB06 /* WebGLContextEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebGLContextEvent.cpp; path = canvas/WebGLContextEvent.cpp; sourceTree = "<group>"; };
                93F6F1EB127F70B10055CB06 /* WebGLContextEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebGLContextEvent.h; path = canvas/WebGLContextEvent.h; sourceTree = "<group>"; };
                93F6F1EC127F70B10055CB06 /* WebGLContextEvent.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = WebGLContextEvent.idl; path = canvas/WebGLContextEvent.idl; sourceTree = "<group>"; };
index 8a8061e..6dcd054 100644 (file)
@@ -2862,13 +2862,39 @@ bool FrameLoader::shouldClose()
     if (!chrome || !chrome->canRunBeforeUnloadConfirmPanel())
         return true;
 
+    // Store all references to each subframe in advance since beforeunload's event handler may modify frame
+    Vector<RefPtr<Frame> > targetFrames;
+    targetFrames.append(m_frame);
+    for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->traverseNext(m_frame))
+        targetFrames.append(child);
+
+    bool shouldClose = false;
+    {
+        NavigationDisablerForBeforeUnload navigationDisabler;
+        size_t i;
+
+        for (i = 0; i < targetFrames.size(); i++) {
+            if (!targetFrames[i]->tree()->isDescendantOf(m_frame))
+                continue;
+            if (!targetFrames[i]->loader()->fireBeforeUnloadEvent(chrome))
+                break;
+        }
+
+        if (i == targetFrames.size())
+            shouldClose = true;
+    }
+
+    return shouldClose;
+}
+
+bool FrameLoader::fireBeforeUnloadEvent(Chrome* chrome)
+{
     DOMWindow* domWindow = m_frame->existingDOMWindow();
     if (!domWindow)
         return true;
 
     RefPtr<Document> document = m_frame->document();
-    HTMLElement* body = document->body();
-    if (!body)
+    if (!document->body())
         return true;
 
     RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create();
@@ -2898,8 +2924,7 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, Pass
     //    1) Navigation policy delegate said we can't so request is nil. A primary case of this 
     //       is the user responding Cancel to the form repost nag sheet.
     //    2) User responded Cancel to an alert popped up by the before unload event handler.
-    // The "before unload" event handler runs only for the main frame.
-    bool canContinue = shouldContinue && (!isLoadingMainFrame() || shouldClose());
+    bool canContinue = shouldContinue && shouldClose();
 
     if (!canContinue) {
         // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we 
index ca78603..d843bb3 100644 (file)
@@ -53,6 +53,7 @@ class AuthenticationChallenge;
 class CachedFrameBase;
 class CachedPage;
 class CachedResource;
+class Chrome;
 class DOMWrapperWorld;
 class Document;
 class DocumentLoader;
@@ -361,6 +362,8 @@ private:
     static void callContinueLoadAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue);
     static void callContinueLoadAfterNewWindowPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, bool shouldContinue);
     static void callContinueFragmentScrollAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue);
+    
+    bool fireBeforeUnloadEvent(Chrome*);
 
     void continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue);
     void continueLoadAfterNewWindowPolicy(const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, bool shouldContinue);
index d2d0f7f..175219c 100644 (file)
@@ -51,6 +51,8 @@
 
 namespace WebCore {
 
+unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0;
+
 class ScheduledNavigation : public Noncopyable {
 public:
     ScheduledNavigation(double delay, bool lockHistory, bool lockBackForwardList, bool wasDuringLoad, bool isLocationChange)
@@ -263,9 +265,19 @@ void NavigationScheduler::clear()
     m_redirect.clear();
 }
 
+inline bool NavigationScheduler::shouldScheduleNavigation() const
+{
+    return m_frame->page();
+}
+
+inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const
+{
+    return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed());
+}
+
 void NavigationScheduler::scheduleRedirect(double delay, const String& url)
 {
-    if (!m_frame->page())
+    if (!shouldScheduleNavigation(url))
         return;
     if (delay < 0 || delay > INT_MAX / 1000)
         return;
@@ -297,7 +309,7 @@ bool NavigationScheduler::mustLockBackForwardList(Frame* targetFrame)
 
 void NavigationScheduler::scheduleLocationChange(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList)
 {
-    if (!m_frame->page())
+    if (!shouldScheduleNavigation(url))
         return;
     if (url.isEmpty())
         return;
@@ -344,7 +356,7 @@ void NavigationScheduler::scheduleFormSubmission(PassRefPtr<FormSubmission> subm
 
 void NavigationScheduler::scheduleRefresh()
 {
-    if (!m_frame->page())
+    if (!shouldScheduleNavigation())
         return;
     const KURL& url = m_frame->loader()->url();
     if (url.isEmpty())
@@ -355,7 +367,7 @@ void NavigationScheduler::scheduleRefresh()
 
 void NavigationScheduler::scheduleHistoryNavigation(int steps)
 {
-    if (!m_frame->page())
+    if (!shouldScheduleNavigation())
         return;
 
     // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled
index 4f3bebc..2014ff2 100644 (file)
@@ -34,6 +34,7 @@
 #include "Event.h"
 #include "Timer.h"
 #include <wtf/Forward.h>
+#include <wtf/Noncopyable.h>
 #include <wtf/OwnPtr.h>
 #include <wtf/PassOwnPtr.h>
 #include <wtf/PassRefPtr.h>
@@ -48,7 +49,28 @@ class SecurityOrigin;
 
 struct FrameLoadRequest;
 
-class NavigationScheduler : public Noncopyable {
+class NavigationDisablerForBeforeUnload {
+    WTF_MAKE_NONCOPYABLE(NavigationDisablerForBeforeUnload);
+
+public:
+    NavigationDisablerForBeforeUnload()
+    {
+        s_navigationDisableCount++;
+    }
+    ~NavigationDisablerForBeforeUnload()
+    {
+        ASSERT(s_navigationDisableCount);
+        s_navigationDisableCount--;
+    }
+    static bool isNavigationAllowed() { return !s_navigationDisableCount; }
+
+private:
+    static unsigned s_navigationDisableCount;
+};
+
+class NavigationScheduler {
+    WTF_MAKE_NONCOPYABLE(NavigationScheduler);
+
 public:
     NavigationScheduler(Frame*);
     ~NavigationScheduler();
@@ -68,6 +90,9 @@ public:
     void clear();
 
 private:
+    bool shouldScheduleNavigation() const;
+    bool shouldScheduleNavigation(const String& url) const;
+
     void timerFired(Timer<NavigationScheduler>*);
     void schedule(PassOwnPtr<ScheduledNavigation>);