2011-06-30 Sreeram Ramachandran <sreeram@chromium.org>
Reviewed by Adam Barth.
Suppress modal JavaScript/HTML dialogs during unload events
https://bugs.webkit.org/show_bug.cgi?id=56397
Adds Chromium-specific test expectations that show alerts in unload
handlers being replaced by console messages.
* fast/loader/page-dismissal-modal-dialogs-expected.txt: Added.
* fast/loader/page-dismissal-modal-dialogs.html: Added.
* fast/loader/resources/page-dismissal-modal-dialogs-iframe.html: Added.
* platform/chromium-mac/fast/history/timed-refresh-in-cached-frame-expected.txt:
* platform/chromium-win/fast/history/timed-refresh-in-cached-frame-expected.txt:
* platform/chromium/fast/dom/Geolocation/notimer-after-unload-expected.txt: Added.
* platform/chromium/fast/events/onbeforeunload-focused-iframe-expected.txt: Added.
* platform/chromium/fast/events/onunload-clears-onbeforeunload-expected.txt: Added.
* platform/chromium/fast/events/onunload-expected.txt: Added.
* platform/chromium/fast/events/onunload-not-on-body-expected.txt: Added.
* platform/chromium/fast/events/onunload-window-property-expected.txt: Added.
* platform/chromium/fast/events/pageshow-pagehide-on-back-uncached-expected.txt: Added.
* platform/chromium/fast/history/timed-refresh-in-cached-frame-expected.txt: Added.
* platform/chromium/fast/loader/frames-with-unload-handlers-in-page-cache-expected.txt: Added.
* platform/chromium/fast/loader/page-dismissal-modal-dialogs-expected.txt: Added.
* platform/chromium/fast/loader/recursive-before-unload-crash-expected.txt: Added.
2011-06-30 Sreeram Ramachandran <sreeram@chromium.org>
Reviewed by Adam Barth.
Suppress modal JavaScript/HTML dialogs during unload events
https://bugs.webkit.org/show_bug.cgi?id=56397
Allows clients to specify whether to show alerts during unload handlers.
When checking for whether a page dismissal event is being dispatched,
it's important to check all frames on the page (otherwise it becomes a
loophole easily exploited).
Test: fast/loader/page-dismissal-modal-dialogs.html
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::FrameLoader):
(WebCore::FrameLoader::stopLoading):
(WebCore::FrameLoader::loadURL):
(WebCore::FrameLoader::loadWithDocumentLoader):
(WebCore::FrameLoader::stopAllLoaders):
(WebCore::FrameLoader::fireBeforeUnloadEvent):
* loader/FrameLoader.h:
(WebCore::FrameLoader::pageDismissalEventBeingDispatched):
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::requestImage):
* page/Chrome.cpp:
(WebCore::canRunModalIfDuringPageDismissal):
(WebCore::Chrome::canRunModalNow):
(WebCore::Chrome::runJavaScriptAlert):
(WebCore::Chrome::runJavaScriptConfirm):
(WebCore::Chrome::runJavaScriptPrompt):
* page/Chrome.h:
* page/ChromeClient.h:
(WebCore::ChromeClient::shouldRunModalDialogDuringPageDismissal):
* page/DOMWindow.cpp:
(WebCore::DOMWindow::showModalDialog):
2011-06-30 Sreeram Ramachandran <sreeram@chromium.org>
Reviewed by Adam Barth.
Suppress modal JavaScript/HTML dialogs during unload events
https://bugs.webkit.org/show_bug.cgi?id=56397
Implementation of the new shouldRunModalDialogDuringPageDismissal() API
to block alerts during unload handlers. Logs such events to the console
and updates histograms.
* src/ChromeClientImpl.cpp:
(WebKit::ChromeClientImpl::shouldRunModalDialogDuringPageDismissal):
* src/ChromeClientImpl.h:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@90164 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index a47d6fc..147508a 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,40 @@
+2011-06-30 Sreeram Ramachandran <sreeram@chromium.org>
+
+ Reviewed by Adam Barth.
+
+ Suppress modal JavaScript/HTML dialogs during unload events
+ https://bugs.webkit.org/show_bug.cgi?id=56397
+
+ Allows clients to specify whether to show alerts during unload handlers.
+ When checking for whether a page dismissal event is being dispatched,
+ it's important to check all frames on the page (otherwise it becomes a
+ loophole easily exploited).
+
+ Test: fast/loader/page-dismissal-modal-dialogs.html
+
+ * loader/FrameLoader.cpp:
+ (WebCore::FrameLoader::FrameLoader):
+ (WebCore::FrameLoader::stopLoading):
+ (WebCore::FrameLoader::loadURL):
+ (WebCore::FrameLoader::loadWithDocumentLoader):
+ (WebCore::FrameLoader::stopAllLoaders):
+ (WebCore::FrameLoader::fireBeforeUnloadEvent):
+ * loader/FrameLoader.h:
+ (WebCore::FrameLoader::pageDismissalEventBeingDispatched):
+ * loader/cache/CachedResourceLoader.cpp:
+ (WebCore::CachedResourceLoader::requestImage):
+ * page/Chrome.cpp:
+ (WebCore::canRunModalIfDuringPageDismissal):
+ (WebCore::Chrome::canRunModalNow):
+ (WebCore::Chrome::runJavaScriptAlert):
+ (WebCore::Chrome::runJavaScriptConfirm):
+ (WebCore::Chrome::runJavaScriptPrompt):
+ * page/Chrome.h:
+ * page/ChromeClient.h:
+ (WebCore::ChromeClient::shouldRunModalDialogDuringPageDismissal):
+ * page/DOMWindow.cpp:
+ (WebCore::DOMWindow::showModalDialog):
+
2011-06-30 Julien Chaffraix <jchaffraix@webkit.org>
Reviewed by Nikolas Zimmermann.
diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp
index 0a62b06..4a1447a 100644
--- a/Source/WebCore/loader/FrameLoader.cpp
+++ b/Source/WebCore/loader/FrameLoader.cpp
@@ -183,7 +183,7 @@
, m_isExecutingJavaScriptFormAction(false)
, m_didCallImplicitClose(false)
, m_wasUnloadEventEmitted(false)
- , m_pageDismissalEventBeingDispatched(false)
+ , m_pageDismissalEventBeingDispatched(NoDismissal)
, m_isComplete(false)
, m_isLoadingMainResource(false)
, m_hasReceivedFirstData(false)
@@ -367,16 +367,18 @@
Node* currentFocusedNode = m_frame->document()->focusedNode();
if (currentFocusedNode)
currentFocusedNode->aboutToUnload();
- m_pageDismissalEventBeingDispatched = true;
if (m_frame->domWindow()) {
- if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide)
+ if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide) {
+ m_pageDismissalEventBeingDispatched = PageHideDismissal;
m_frame->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, m_frame->document()->inPageCache()), m_frame->document());
+ }
if (!m_frame->document()->inPageCache()) {
RefPtr<Event> unloadEvent(Event::create(eventNames().unloadEvent, false, false));
// The DocumentLoader (and thus its DocumentLoadTiming) might get destroyed
// while dispatching the event, so protect it to prevent writing the end
// time into freed memory.
RefPtr<DocumentLoader> documentLoader = m_provisionalDocumentLoader;
+ m_pageDismissalEventBeingDispatched = UnloadDismissal;
if (documentLoader && !documentLoader->timing()->unloadEventStart && !documentLoader->timing()->unloadEventEnd) {
DocumentLoadTiming* timing = documentLoader->timing();
ASSERT(timing->navigationStart);
@@ -385,7 +387,7 @@
m_frame->domWindow()->dispatchEvent(unloadEvent, m_frame->domWindow()->document());
}
}
- m_pageDismissalEventBeingDispatched = false;
+ m_pageDismissalEventBeingDispatched = NoDismissal;
if (m_frame->document())
m_frame->document()->updateStyleIfNeeded();
m_wasUnloadEventEmitted = true;
@@ -1182,7 +1184,7 @@
return;
}
- if (m_pageDismissalEventBeingDispatched)
+ if (m_pageDismissalEventBeingDispatched != NoDismissal)
return;
NavigationAction action(newURL, newLoadType, isFormSubmission, event);
@@ -1317,7 +1319,7 @@
ASSERT(m_frame->view());
- if (m_pageDismissalEventBeingDispatched)
+ if (m_pageDismissalEventBeingDispatched != NoDismissal)
return;
if (m_frame->document())
@@ -1552,7 +1554,7 @@
void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItemPolicy)
{
ASSERT(!m_frame->document() || !m_frame->document()->inPageCache());
- if (m_pageDismissalEventBeingDispatched)
+ if (m_pageDismissalEventBeingDispatched != NoDismissal)
return;
// If this method is called from within this method, infinite recursion can occur (3442218). Avoid this.
@@ -2721,9 +2723,9 @@
return true;
RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create();
- m_pageDismissalEventBeingDispatched = true;
+ m_pageDismissalEventBeingDispatched = BeforeUnloadDismissal;
domWindow->dispatchEvent(beforeUnloadEvent.get(), domWindow->document());
- m_pageDismissalEventBeingDispatched = false;
+ m_pageDismissalEventBeingDispatched = NoDismissal;
if (!beforeUnloadEvent->defaultPrevented())
document->defaultEventHandler(beforeUnloadEvent.get());
diff --git a/Source/WebCore/loader/FrameLoader.h b/Source/WebCore/loader/FrameLoader.h
index d73b5aa..fddaa08 100644
--- a/Source/WebCore/loader/FrameLoader.h
+++ b/Source/WebCore/loader/FrameLoader.h
@@ -272,7 +272,13 @@
void started();
- bool pageDismissalEventBeingDispatched() const { return m_pageDismissalEventBeingDispatched; }
+ enum PageDismissalType {
+ NoDismissal = 0,
+ BeforeUnloadDismissal = 1,
+ PageHideDismissal = 2,
+ UnloadDismissal = 3
+ };
+ PageDismissalType pageDismissalEventBeingDispatched() const { return m_pageDismissalEventBeingDispatched; }
NetworkingContext* networkingContext() const;
@@ -395,7 +401,7 @@
bool m_didCallImplicitClose;
bool m_wasUnloadEventEmitted;
- bool m_pageDismissalEventBeingDispatched;
+ PageDismissalType m_pageDismissalEventBeingDispatched;
bool m_isComplete;
bool m_isLoadingMainResource;
diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp
index deda239..f613156 100644
--- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp
+++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp
@@ -129,7 +129,7 @@
if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled()))
return 0;
- if (f->loader()->pageDismissalEventBeingDispatched()) {
+ if (f->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) {
KURL requestURL = request.url();
if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL))
PingLoader::loadImage(f, requestURL);
diff --git a/Source/WebCore/page/Chrome.cpp b/Source/WebCore/page/Chrome.cpp
index 394d5e5..7ede62c 100644
--- a/Source/WebCore/page/Chrome.cpp
+++ b/Source/WebCore/page/Chrome.cpp
@@ -206,11 +206,22 @@
return m_client->canRunModal();
}
+static bool canRunModalIfDuringPageDismissal(Page* page, ChromeClient::DialogType dialog, const String& message)
+{
+ for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
+ FrameLoader::PageDismissalType dismissal = frame->loader()->pageDismissalEventBeingDispatched();
+ if (dismissal != FrameLoader::NoDismissal)
+ return page->chrome()->client()->shouldRunModalDialogDuringPageDismissal(dialog, message, dismissal);
+ }
+ return true;
+}
+
bool Chrome::canRunModalNow() const
{
// If loads are blocked, we can't run modal because the contents
// of the modal dialog will never show up!
- return canRunModal() && !ResourceHandle::loadsBlocked();
+ return canRunModal() && !ResourceHandle::loadsBlocked()
+ && canRunModalIfDuringPageDismissal(m_page, ChromeClient::HTMLDialog, String());
}
void Chrome::runModal() const
@@ -287,15 +298,10 @@
m_client->closeWindowSoon();
}
-static inline void willRunModalDialog(const Frame* frame, const ChromeClient::DialogType& dialogType, const ChromeClient* client)
-{
- if (frame->loader()->pageDismissalEventBeingDispatched())
- client->willRunModalDialogDuringPageDismissal(dialogType);
-}
-
void Chrome::runJavaScriptAlert(Frame* frame, const String& message)
{
- willRunModalDialog(frame, ChromeClient::AlertDialog, m_client);
+ if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::AlertDialog, message))
+ return;
// Defer loads in case the client method runs a new event loop that would
// otherwise cause the load to continue while we're in the middle of executing JavaScript.
@@ -307,7 +313,8 @@
bool Chrome::runJavaScriptConfirm(Frame* frame, const String& message)
{
- willRunModalDialog(frame, ChromeClient::ConfirmDialog, m_client);
+ if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::ConfirmDialog, message))
+ return false;
// Defer loads in case the client method runs a new event loop that would
// otherwise cause the load to continue while we're in the middle of executing JavaScript.
@@ -319,7 +326,8 @@
bool Chrome::runJavaScriptPrompt(Frame* frame, const String& prompt, const String& defaultValue, String& result)
{
- willRunModalDialog(frame, ChromeClient::PromptDialog, m_client);
+ if (!canRunModalIfDuringPageDismissal(m_page, ChromeClient::PromptDialog, prompt))
+ return false;
// Defer loads in case the client method runs a new event loop that would
// otherwise cause the load to continue while we're in the middle of executing JavaScript.
@@ -574,9 +582,4 @@
return m_client->requiresFullscreenForVideoPlayback();
}
-void Chrome::willRunModalHTMLDialog(const Frame* frame) const
-{
- willRunModalDialog(frame, ChromeClient::HTMLDialog, m_client);
-}
-
} // namespace WebCore
diff --git a/Source/WebCore/page/Chrome.h b/Source/WebCore/page/Chrome.h
index 69389ff..d9ae0de 100644
--- a/Source/WebCore/page/Chrome.h
+++ b/Source/WebCore/page/Chrome.h
@@ -179,8 +179,6 @@
void showContextMenu();
#endif
- void willRunModalHTMLDialog(const Frame*) const;
-
private:
Page* m_page;
ChromeClient* m_client;
diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h
index 9c59c23..a27c766 100644
--- a/Source/WebCore/page/ChromeClient.h
+++ b/Source/WebCore/page/ChromeClient.h
@@ -25,6 +25,7 @@
#include "ConsoleTypes.h"
#include "Cursor.h"
#include "FocusDirection.h"
+#include "FrameLoader.h"
#include "GraphicsContext.h"
#include "HostWindow.h"
#include "PopupMenu.h"
@@ -34,6 +35,7 @@
#include "WebCoreKeyboardUIMode.h"
#include <wtf/Forward.h>
#include <wtf/PassOwnPtr.h>
+#include <wtf/UnusedParam.h>
#include <wtf/Vector.h>
#ifndef __OBJC__
@@ -323,10 +325,9 @@
AlertDialog = 0,
ConfirmDialog = 1,
PromptDialog = 2,
- HTMLDialog = 3,
- NumDialogTypes = 4
+ HTMLDialog = 3
};
- virtual void willRunModalDialogDuringPageDismissal(const DialogType&) const { }
+ virtual bool shouldRunModalDialogDuringPageDismissal(const DialogType&, const String& dialogMessage, FrameLoader::PageDismissalType) const { UNUSED_PARAM(dialogMessage); return true; }
virtual void numWheelEventHandlersChanged(unsigned) = 0;
diff --git a/Source/WebCore/page/DOMWindow.cpp b/Source/WebCore/page/DOMWindow.cpp
index d7f2ae4..da78596 100644
--- a/Source/WebCore/page/DOMWindow.cpp
+++ b/Source/WebCore/page/DOMWindow.cpp
@@ -1851,9 +1851,6 @@
if (!firstFrame)
return;
- if (m_frame->page())
- m_frame->page()->chrome()->willRunModalHTMLDialog(m_frame);
-
if (!canShowModalDialogNow(m_frame) || !firstWindow->allowPopUp())
return;
diff --git a/Source/WebKit/chromium/ChangeLog b/Source/WebKit/chromium/ChangeLog
index 5be0b60..45eafc3 100644
--- a/Source/WebKit/chromium/ChangeLog
+++ b/Source/WebKit/chromium/ChangeLog
@@ -1,3 +1,18 @@
+2011-06-30 Sreeram Ramachandran <sreeram@chromium.org>
+
+ Reviewed by Adam Barth.
+
+ Suppress modal JavaScript/HTML dialogs during unload events
+ https://bugs.webkit.org/show_bug.cgi?id=56397
+
+ Implementation of the new shouldRunModalDialogDuringPageDismissal() API
+ to block alerts during unload handlers. Logs such events to the console
+ and updates histograms.
+
+ * src/ChromeClientImpl.cpp:
+ (WebKit::ChromeClientImpl::shouldRunModalDialogDuringPageDismissal):
+ * src/ChromeClientImpl.h:
+
2011-06-30 Anders Carlsson <andersca@apple.com>
Reviewed by Dan Bernstein.
diff --git a/Source/WebKit/chromium/src/ChromeClientImpl.cpp b/Source/WebKit/chromium/src/ChromeClientImpl.cpp
index 5a180bd..bbb340c 100644
--- a/Source/WebKit/chromium/src/ChromeClientImpl.cpp
+++ b/Source/WebKit/chromium/src/ChromeClientImpl.cpp
@@ -90,6 +90,7 @@
#include "WebWindowFeatures.h"
#include "WindowFeatures.h"
#include "WrappedResourceRequest.h"
+#include <wtf/text/StringConcatenate.h>
#include <wtf/unicode/CharacterNames.h>
using namespace WebCore;
@@ -945,9 +946,21 @@
return adoptRef(new SearchPopupMenuChromium(client));
}
-void ChromeClientImpl::willRunModalDialogDuringPageDismissal(const DialogType& dialogType) const
+bool ChromeClientImpl::shouldRunModalDialogDuringPageDismissal(const DialogType& dialogType, const String& dialogMessage, FrameLoader::PageDismissalType dismissalType) const
{
- PlatformBridge::histogramEnumeration("Renderer.ModalDialogsDuringPageDismissal", static_cast<int>(dialogType), static_cast<int>(NumDialogTypes));
+ const char* kDialogs[] = {"alert", "confirm", "prompt", "showModalDialog"};
+ int dialog = static_cast<int>(dialogType);
+ ASSERT(0 <= dialog && dialog < static_cast<int>(arraysize(kDialogs)));
+
+ const char* kDismissals[] = {"beforeunload", "pagehide", "unload"};
+ int dismissal = static_cast<int>(dismissalType) - 1; // Exclude NoDismissal.
+ ASSERT(0 <= dismissal && dismissal < static_cast<int>(arraysize(kDismissals)));
+
+ PlatformBridge::histogramEnumeration("Renderer.ModalDialogsDuringPageDismissal", dismissal * arraysize(kDialogs) + dialog, arraysize(kDialogs) * arraysize(kDismissals));
+
+ m_webView->mainFrame()->addMessageToConsole(WebConsoleMessage(WebConsoleMessage::LevelError, makeString("Blocked ", kDialogs[dialog], "('", dialogMessage, "') during ", kDismissals[dismissal], ".")));
+
+ return false;
}
} // namespace WebKit
diff --git a/Source/WebKit/chromium/src/ChromeClientImpl.h b/Source/WebKit/chromium/src/ChromeClientImpl.h
index a09d4c5..2f79c05 100644
--- a/Source/WebKit/chromium/src/ChromeClientImpl.h
+++ b/Source/WebKit/chromium/src/ChromeClientImpl.h
@@ -195,7 +195,7 @@
virtual void showContextMenu() { }
#endif
- virtual void willRunModalDialogDuringPageDismissal(const DialogType&) const;
+ virtual bool shouldRunModalDialogDuringPageDismissal(const DialogType&, const String& dialogMessage, WebCore::FrameLoader::PageDismissalType) const;
virtual bool shouldRubberBandInDirection(WebCore::ScrollDirection) const { return true; }
virtual void numWheelEventHandlersChanged(unsigned) { }