Commit 9f8b52ba authored by jamesr@google.com's avatar jamesr@google.com
Browse files

2011-02-15 James Robinson <jamesr@chromium.org>

        Reviewed by Alexey Proskuryakov.

        requestAnimationFrame callbacks should not fire within a modal dialog
        https://bugs.webkit.org/show_bug.cgi?id=53188

        Tests that requestAnimationFrame callbacks are suspended while a modal
        dialog is showing.

        * fast/animation/request-animation-frame-during-modal-expected.txt: Added.
        * fast/animation/request-animation-frame-during-modal.html: Added.
2011-02-15  James Robinson  <jamesr@chromium.org>

        Reviewed by Alexey Proskuryakov.

        requestAnimationFrame callbacks should not fire within a modal dialog
        https://bugs.webkit.org/show_bug.cgi?id=53188

        requestAnimationFrame callbacks shouldn't fire while a modal dialog is up (like a window.alert()).
        This matches Firefox and other async APIs.  This patch moves the callback servicing into its own
        controller class which receives notifications on suspend/resume.

        Test: fast/animation/request-animation-frame-during-modal.html

        * WebCore.gypi:
        * bindings/js/ScriptDebugServer.cpp:
        (WebCore::ScriptDebugServer::setJavaScriptPaused):
        * dom/Document.cpp:
        (WebCore::Document::Document):
        (WebCore::Document::suspendScriptedAnimationControllerCallbacks):
        (WebCore::Document::resumeScriptedAnimationControllerCallbacks):
        (WebCore::Document::webkitRequestAnimationFrame):
        (WebCore::Document::webkitCancelRequestAnimationFrame):
        (WebCore::Document::serviceScriptedAnimations):
        * dom/Document.h:
        * dom/ScriptExecutionContext.h:
        (WebCore::ScriptExecutionContext::suspendScriptedAnimationControllerCallbacks):
        (WebCore::ScriptExecutionContext::resumeScriptedAnimationControllerCallbacks):
        * dom/ScriptedAnimationController.cpp: Added.
        (WebCore::ScriptedAnimationController::ScriptedAnimationController):
        (WebCore::ScriptedAnimationController::suspend):
        (WebCore::ScriptedAnimationController::resume):
        (WebCore::ScriptedAnimationController::registerCallback):
        (WebCore::ScriptedAnimationController::cancelCallback):
        (WebCore::ScriptedAnimationController::serviceScriptedAnimations):
        * dom/ScriptedAnimationController.h: Added.
        (WebCore::ScriptedAnimationController::create):
        * history/CachedFrame.cpp:
        (WebCore::CachedFrameBase::restore):
        (WebCore::CachedFrame::CachedFrame):
        * page/PageGroupLoadDeferrer.cpp:
        (WebCore::PageGroupLoadDeferrer::PageGroupLoadDeferrer):
        (WebCore::PageGroupLoadDeferrer::~PageGroupLoadDeferrer):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@78648 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent e564e967
2011-02-15 James Robinson <jamesr@chromium.org>
Reviewed by Alexey Proskuryakov.
requestAnimationFrame callbacks should not fire within a modal dialog
https://bugs.webkit.org/show_bug.cgi?id=53188
Tests that requestAnimationFrame callbacks are suspended while a modal
dialog is showing.
* fast/animation/request-animation-frame-during-modal-expected.txt: Added.
* fast/animation/request-animation-frame-during-modal.html: Added.
2011-02-15 Benjamin Kalman <kalman@chromium.org>
 
Reviewed by Darin Adler.
Tests that requestAnimationFrame callbacks are not invoked while modal dialogs are displayed.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS successfullyParsed is true
TEST COMPLETE
Setting callback
Showing modal dialog
Returned from modal dialog
Callback fired
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="../js/resources/js-test-style.css">
<script src="../js/resources/js-test-pre.js"></script>
<body>
<p id="description"></p>
<div id="console"></div>
<script type="text/javascript">
description('Tests that requestAnimationFrame callbacks are not invoked while modal dialogs are displayed.');
onload = function()
{
debug('Setting callback');
window.webkitRequestAnimationFrame(function() { debug('Callback fired'); }, document.body);
debug('Showing modal dialog');
var src = 'if (window.layoutTestController) {' +
' layoutTestController.display();' +
' window.close();' +
'} else {'+
' window.setTimeout(window.close, 10);'+
'}';
showModalDialog('data:text/html,<script>' + src + '</' + 'script>');
debug('Returned from modal dialog');
if (window.layoutTestController) {;
layoutTestController.display();
}
}
var successfullyParsed = true;
</script>
<script src="../js/resources/js-test-post.js"></script>
</body>
</html>
2011-02-15 James Robinson <jamesr@chromium.org>
Reviewed by Alexey Proskuryakov.
requestAnimationFrame callbacks should not fire within a modal dialog
https://bugs.webkit.org/show_bug.cgi?id=53188
requestAnimationFrame callbacks shouldn't fire while a modal dialog is up (like a window.alert()).
This matches Firefox and other async APIs. This patch moves the callback servicing into its own
controller class which receives notifications on suspend/resume.
Test: fast/animation/request-animation-frame-during-modal.html
* WebCore.gypi:
* bindings/js/ScriptDebugServer.cpp:
(WebCore::ScriptDebugServer::setJavaScriptPaused):
* dom/Document.cpp:
(WebCore::Document::Document):
(WebCore::Document::suspendScriptedAnimationControllerCallbacks):
(WebCore::Document::resumeScriptedAnimationControllerCallbacks):
(WebCore::Document::webkitRequestAnimationFrame):
(WebCore::Document::webkitCancelRequestAnimationFrame):
(WebCore::Document::serviceScriptedAnimations):
* dom/Document.h:
* dom/ScriptExecutionContext.h:
(WebCore::ScriptExecutionContext::suspendScriptedAnimationControllerCallbacks):
(WebCore::ScriptExecutionContext::resumeScriptedAnimationControllerCallbacks):
* dom/ScriptedAnimationController.cpp: Added.
(WebCore::ScriptedAnimationController::ScriptedAnimationController):
(WebCore::ScriptedAnimationController::suspend):
(WebCore::ScriptedAnimationController::resume):
(WebCore::ScriptedAnimationController::registerCallback):
(WebCore::ScriptedAnimationController::cancelCallback):
(WebCore::ScriptedAnimationController::serviceScriptedAnimations):
* dom/ScriptedAnimationController.h: Added.
(WebCore::ScriptedAnimationController::create):
* history/CachedFrame.cpp:
(WebCore::CachedFrameBase::restore):
(WebCore::CachedFrame::CachedFrame):
* page/PageGroupLoadDeferrer.cpp:
(WebCore::PageGroupLoadDeferrer::PageGroupLoadDeferrer):
(WebCore::PageGroupLoadDeferrer::~PageGroupLoadDeferrer):
2011-02-14 Jeremy Orlow <jorlow@chromium.org>
 
Reviewed by Darin Fisher.
......@@ -1321,6 +1321,8 @@
'dom/RangeBoundaryPoint.h',
'dom/RangeException.h',
'dom/RequestAnimationFrameCallback.h',
'dom/ScriptedAnimationController.cpp',
'dom/ScriptedAnimationController.h',
'dom/RawDataDocumentParser.h',
'dom/RegisteredEventListener.cpp',
'dom/RegisteredEventListener.h',
......
......@@ -408,10 +408,13 @@ void ScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused)
frame->script()->setPaused(paused);
Document* document = frame->document();
if (paused)
if (paused) {
document->suspendScriptedAnimationControllerCallbacks();
document->suspendActiveDOMObjects(ActiveDOMObject::JavaScriptDebuggerPaused);
else
} else {
document->resumeActiveDOMObjects();
document->resumeScriptedAnimationControllerCallbacks();
}
setJavaScriptPaused(frame->view(), paused);
}
......
......@@ -210,6 +210,7 @@
#if ENABLE(REQUEST_ANIMATION_FRAME)
#include "RequestAnimationFrameCallback.h"
#include "ScriptedAnimationController.h"
#endif
using namespace std;
......@@ -429,9 +430,6 @@ Document::Document(Frame* frame, const KURL& url, bool isXHTML, bool isHTML)
, m_writingModeSetOnDocumentElement(false)
, m_writeRecursionIsTooDeep(false)
, m_writeRecursionDepth(0)
#if ENABLE(REQUEST_ANIMATION_FRAME)
, m_nextRequestAnimationFrameCallbackId(0)
#endif
{
m_document = this;
......@@ -4672,6 +4670,22 @@ void Document::postTask(PassOwnPtr<Task> task)
callOnMainThread(performTask, new PerformTaskContext(m_weakReference, task));
}
void Document::suspendScriptedAnimationControllerCallbacks()
{
#if ENABLE(REQUEST_ANIMATION_FRAME)
if (m_scriptedAnimationController)
m_scriptedAnimationController->suspend();
#endif
}
void Document::resumeScriptedAnimationControllerCallbacks()
{
#if ENABLE(REQUEST_ANIMATION_FRAME)
if (m_scriptedAnimationController)
m_scriptedAnimationController->resume();
#endif
}
Element* Document::findAnchor(const String& name)
{
if (name.isEmpty())
......@@ -4902,82 +4916,26 @@ void Document::loadEventDelayTimerFired(Timer<Document>*)
}
#if ENABLE(REQUEST_ANIMATION_FRAME)
int Document::webkitRequestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback, Element* e)
int Document::webkitRequestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback, Element* animationElement)
{
if (!m_requestAnimationFrameCallbacks)
m_requestAnimationFrameCallbacks = new RequestAnimationFrameCallbackList;
int id = m_nextRequestAnimationFrameCallbackId++;
callback->m_firedOrCancelled = false;
callback->m_id = id;
callback->m_element = e;
m_requestAnimationFrameCallbacks->append(callback);
if (FrameView* v = view())
v->scheduleAnimation();
return id;
if (!m_scriptedAnimationController)
m_scriptedAnimationController = ScriptedAnimationController::create(this);
return m_scriptedAnimationController->registerCallback(callback, animationElement);
}
void Document::webkitCancelRequestAnimationFrame(int id)
{
if (!m_requestAnimationFrameCallbacks)
if (!m_scriptedAnimationController)
return;
for (size_t i = 0; i < m_requestAnimationFrameCallbacks->size(); ++i) {
if (m_requestAnimationFrameCallbacks->at(i)->m_id == id) {
m_requestAnimationFrameCallbacks->at(i)->m_firedOrCancelled = true;
m_requestAnimationFrameCallbacks->remove(i);
return;
}
}
m_scriptedAnimationController->cancelCallback(id);
}
void Document::serviceScriptedAnimations(DOMTimeStamp time)
{
if (!m_requestAnimationFrameCallbacks)
if (!m_scriptedAnimationController)
return;
// We want to run the callback for all elements in the document that have registered
// for a callback and that are visible. Running the callbacks can cause new callbacks
// to be registered, existing callbacks to be cancelled, and elements to gain or lose
// visibility so this code has to iterate carefully.
// FIXME: Currently, this code doesn't do any visibility tests beyond checking display:
// First, generate a list of callbacks to consider. Callbacks registered from this point
// on are considered only for the "next" frame, not this one.
RequestAnimationFrameCallbackList callbacks(*m_requestAnimationFrameCallbacks);
// Firing the callback may cause the visibility of other elements to change. To avoid
// missing any callbacks, we keep iterating through the list of candiate callbacks and firing
// them until nothing new becomes visible.
bool firedCallback;
do {
firedCallback = false;
// A previous iteration may have invalidated style (or layout). Update styles for each iteration
// for now since all we check is the existence of a renderer.
updateStyleIfNeeded();
for (size_t i = 0; i < callbacks.size(); ++i) {
RequestAnimationFrameCallback* callback = callbacks[i].get();
if (!callback->m_firedOrCancelled && (!callback->m_element || callback->m_element->renderer())) {
callback->m_firedOrCancelled = true;
callback->handleEvent(time);
firedCallback = true;
callbacks.remove(i);
break;
}
}
} while (firedCallback);
// Remove any callbacks we fired from the list of pending callbacks.
for (size_t i = 0; i < m_requestAnimationFrameCallbacks->size();) {
if (m_requestAnimationFrameCallbacks->at(i)->m_firedOrCancelled)
m_requestAnimationFrameCallbacks->remove(i);
else
++i;
}
// In most cases we expect this list to be empty, so no need to keep around the vector's inline buffer.
if (!m_requestAnimationFrameCallbacks->size())
m_requestAnimationFrameCallbacks.clear();
else if (FrameView* v = view())
v->scheduleAnimation();
m_scriptedAnimationController->serviceScriptedAnimations(time);
}
#endif
......
......@@ -152,6 +152,7 @@ class TouchList;
#if ENABLE(REQUEST_ANIMATION_FRAME)
class RequestAnimationFrameCallback;
class ScriptedAnimationController;
#endif
typedef int ExceptionCode;
......@@ -953,6 +954,9 @@ public:
virtual void addMessage(MessageSource, MessageType, MessageLevel, const String& message, unsigned lineNumber, const String& sourceURL, PassRefPtr<ScriptCallStack>);
virtual void postTask(PassOwnPtr<Task>); // Executes the task on context's thread asynchronously.
virtual void suspendScriptedAnimationControllerCallbacks();
virtual void resumeScriptedAnimationControllerCallbacks();
#if USE(JSC)
typedef JSC::WeakGCMap<WebCore::Node*, JSNode> JSWrapperCache;
typedef HashMap<DOMWrapperWorld*, JSWrapperCache*> JSWrapperCacheMap;
......@@ -1406,9 +1410,7 @@ private:
unsigned m_writeRecursionDepth;
#if ENABLE(REQUEST_ANIMATION_FRAME)
typedef Vector<RefPtr<RequestAnimationFrameCallback> > RequestAnimationFrameCallbackList;
OwnPtr<RequestAnimationFrameCallbackList> m_requestAnimationFrameCallbacks;
int m_nextRequestAnimationFrameCallbackId;
OwnPtr<ScriptedAnimationController> m_scriptedAnimationController;
#endif
ContentSecurityPolicy m_contentSecurityPolicy;
......
......@@ -107,6 +107,9 @@ namespace WebCore {
typedef const HashMap<ActiveDOMObject*, void*> ActiveDOMObjectsMap;
ActiveDOMObjectsMap& activeDOMObjects() const { return m_activeDOMObjects; }
virtual void suspendScriptedAnimationControllerCallbacks() { }
virtual void resumeScriptedAnimationControllerCallbacks() { }
// MessagePort is conceptually a kind of ActiveDOMObject, but it needs to be tracked separately for message dispatch.
void processMessagePortMessagesSoon();
void dispatchMessagePortEvents();
......
/*
* Copyright (C) 2011 Google 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. AND ITS CONTRIBUTORS ``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 ITS 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 "ScriptedAnimationController.h"
#if ENABLE(REQUEST_ANIMATION_FRAME)
#include "Document.h"
#include "Element.h"
#include "FrameView.h"
#include "RequestAnimationFrameCallback.h"
namespace WebCore {
ScriptedAnimationController::ScriptedAnimationController(Document* document)
: m_document(document)
, m_nextCallbackId(0)
, m_suspendCount(0)
{
}
void ScriptedAnimationController::suspend()
{
++m_suspendCount;
}
void ScriptedAnimationController::resume()
{
--m_suspendCount;
if (!m_suspendCount && m_callbacks.size())
if (FrameView* fv = m_document->view())
fv->scheduleAnimation();
}
ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(PassRefPtr<RequestAnimationFrameCallback> callback, Element* animationElement)
{
ScriptedAnimationController::CallbackId id = m_nextCallbackId++;
callback->m_firedOrCancelled = false;
callback->m_id = id;
callback->m_element = animationElement;
m_callbacks.append(callback);
if (!m_suspendCount)
if (FrameView* view = m_document->view())
view->scheduleAnimation();
return id;
}
void ScriptedAnimationController::cancelCallback(CallbackId id)
{
for (size_t i = 0; i < m_callbacks.size(); ++i) {
if (m_callbacks[i]->m_id == id) {
m_callbacks[i]->m_firedOrCancelled = true;
m_callbacks.remove(i);
return;
}
}
}
void ScriptedAnimationController::serviceScriptedAnimations(DOMTimeStamp time)
{
if (!m_callbacks.size() || m_suspendCount)
return;
// We want to run the callback for all elements in the document that have registered
// for a callback and that are visible. Running the callbacks can cause new callbacks
// to be registered, existing callbacks to be cancelled, and elements to gain or lose
// visibility so this code has to iterate carefully.
// FIXME: Currently, this code doesn't do any visibility tests beyond checking display:
// First, generate a list of callbacks to consider. Callbacks registered from this point
// on are considered only for the "next" frame, not this one.
CallbackList callbacks(m_callbacks);
// Firing the callback may cause the visibility of other elements to change. To avoid
// missing any callbacks, we keep iterating through the list of candiate callbacks and firing
// them until nothing new becomes visible.
bool firedCallback;
do {
firedCallback = false;
// A previous iteration may have invalidated style (or layout). Update styles for each iteration
// for now since all we check is the existence of a renderer.
m_document->updateStyleIfNeeded();
for (size_t i = 0; i < callbacks.size(); ++i) {
RequestAnimationFrameCallback* callback = callbacks[i].get();
if (!callback->m_firedOrCancelled && (!callback->m_element || callback->m_element->renderer())) {
callback->m_firedOrCancelled = true;
callback->handleEvent(time);
firedCallback = true;
callbacks.remove(i);
break;
}
}
} while (firedCallback);
// Remove any callbacks we fired from the list of pending callbacks.
for (size_t i = 0; i < m_callbacks.size();) {
if (m_callbacks[i]->m_firedOrCancelled)
m_callbacks.remove(i);
else
++i;
}
if (m_callbacks.size())
if (FrameView* view = m_document->view())
view->scheduleAnimation();
}
}
#endif
/*
* Copyright (C) 2011 Google 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. AND ITS CONTRIBUTORS ``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 ITS 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.
*
*/
#ifndef ScriptedAnimationController_h
#define ScriptedAnimationController_h
#include "DOMTimeStamp.h"
#include <wtf/Noncopyable.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/RefPtr.h>
#include <wtf/Vector.h>
namespace WebCore {
class Document;
class Element;
class RequestAnimationFrameCallback;
class ScriptedAnimationController {
WTF_MAKE_NONCOPYABLE(ScriptedAnimationController);
public:
static PassOwnPtr<ScriptedAnimationController> create(Document* document)
{
return adoptPtr(new ScriptedAnimationController(document));
}
typedef int CallbackId;
CallbackId registerCallback(PassRefPtr<RequestAnimationFrameCallback>, Element*);
void cancelCallback(CallbackId);
void serviceScriptedAnimations(DOMTimeStamp);
void suspend();
void resume();
private:
explicit ScriptedAnimationController(Document*);
typedef Vector<RefPtr<RequestAnimationFrameCallback> > CallbackList;
CallbackList m_callbacks;
Document* m_document;
CallbackId m_nextCallbackId;
int m_suspendCount;
};
}
#endif // ScriptedAnimationController_h
......@@ -94,6 +94,7 @@ void CachedFrameBase::restore()
frame->animation()->resumeAnimationsForDocument(m_document.get());
frame->eventHandler()->setMousePressNode(m_mousePressNode.get());
m_document->resumeActiveDOMObjects();
m_document->resumeScriptedAnimationControllerCallbacks();
// It is necessary to update any platform script objects after restoring the
// cached page.
......@@ -152,6 +153,7 @@ CachedFrame::CachedFrame(Frame* frame)
// Suspending must also happen after we've recursed over child frames, in case
// those create more objects.
// FIXME: It's still possible to have objects created after suspending in some cases, see http://webkit.org/b/53733 for more details.
m_document->suspendScriptedAnimationControllerCallbacks();
m_document->suspendActiveDOMObjects(ActiveDOMObject::DocumentWillBecomeInactive);
m_cachedFrameScriptData = adoptPtr(new ScriptCachedFrameData(frame));
......
......@@ -48,6 +48,7 @@ PageGroupLoadDeferrer::PageGroupLoadDeferrer(Page* page, bool deferSelf)
// NOTE: if PageGroupLoadDeferrer is ever used for tasks other than showing a modal window or sheet,
// the constructor will need to take a ActiveDOMObject::ReasonForSuspension.
for (Frame* frame = otherPage->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
frame->document()->suspendScriptedAnimationControllerCallbacks();
frame->document()->suspendActiveDOMObjects(ActiveDOMObject::WillShowDialog);
frame->document()->asyncScriptRunner()->suspend();
if (DocumentParser* parser = frame->document()->parser())
......@@ -71,6 +72,7 @@ PageGroupLoadDeferrer::~PageGroupLoadDeferrer()
for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
frame->document()->resumeActiveDOMObjects();
frame->document()->resumeScriptedAnimationControllerCallbacks();
frame->document()->asyncScriptRunner()->resume();
if (DocumentParser* parser = frame->document()->parser())
parser->resumeScheduledTasks();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment