Commit 13f65f3f authored by ap@apple.com's avatar ap@apple.com

Reviewed by Darin Adler.

        https://bugs.webkit.org/show_bug.cgi?id=31690
        Make SocketStreamHandleCFNet work on Windows

        * WebCore.vcproj/WebCore.vcproj: Added LoaderRunLoopCF.

        * platform/network/ResourceHandle.h: Removed loaderRunLoop().

        * platform/network/cf/LoaderRunLoopCF.cpp: Added.
        (WebCore::emptyPerform):
        (WebCore::runLoaderThread):
        (WebCore::loaderRunLoop):
        * platform/network/cf/LoaderRunLoopCF.h: Added.
        Moved the run loop that we use for CFNetwork from ResourceHandle to its own file, because
        it's needed for more than just resource loading.

        * platform/network/cf/ResourceHandleCFNet.cpp: Use loaderRunLoop() from its new location.

        * platform/network/cf/DNSCFNet.cpp: (WebCore::DNSResolveQueue::resolve): Ditto.

        * platform/network/cf/SocketStreamHandle.h: Added static callbacks for forwarding events to
        main thread.
        * platform/network/cf/SocketStreamHandleCFNet.cpp:
        (WebCore::SocketStreamHandle::SocketStreamHandle): Use loaderRunLoop() on Windows instead of
        inoperable main run loop.

        (WebCore::MainThreadEventCallbackInfo::MainThreadEventCallbackInfo): 
        (WebCore::SocketStreamHandle::readStreamCallback):
        (WebCore::SocketStreamHandle::writeStreamCallback):
        (WebCore::SocketStreamHandle::readStreamCallbackMainThread):
        (WebCore::SocketStreamHandle::writeStreamCallbackMainThread):
        Forward stream events to main thread on Windows.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@51222 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent d6ddab4d
2009-11-19 Alexey Proskuryakov <ap@apple.com>
Reviewed by Darin Adler.
https://bugs.webkit.org/show_bug.cgi?id=31690
Make SocketStreamHandleCFNet work on Windows
* JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def:
* wtf/MainThread.cpp:
(WTF::FunctionWithContext::FunctionWithContext):
(WTF::dispatchFunctionsFromMainThread):
(WTF::callOnMainThreadAndWait):
* wtf/MainThread.h:
Re-add callOnMainThreadAndWait(), which was removed in bug 23926.
2009-11-19 Dmitry Titov <dimich@chromium.org>
Reviewed by David Levin.
......
......@@ -52,6 +52,7 @@ EXPORTS
?calculatedFunctionName@DebuggerCallFrame@JSC@@QBE?AVUString@2@XZ
?call@JSC@@YA?AVJSValue@1@PAVExecState@1@V21@W4CallType@1@ABTCallData@1@1ABVArgList@1@@Z
?callOnMainThread@WTF@@YAXP6AXPAX@Z0@Z
?callOnMainThreadAndWait@WTF@@YAXP6AXPAX@Z0@Z
?changePrototypeTransition@Structure@JSC@@SA?AV?$PassRefPtr@VStructure@JSC@@@WTF@@PAV12@VJSValue@2@@Z
?checkSameIdentifierTable@Identifier@JSC@@CAXPAVExecState@2@PAURep@UString@2@@Z
?checkSameIdentifierTable@Identifier@JSC@@CAXPAVJSGlobalData@2@PAURep@UString@2@@Z
......
......@@ -39,10 +39,12 @@ namespace WTF {
struct FunctionWithContext {
MainThreadFunction* function;
void* context;
ThreadCondition* syncFlag;
FunctionWithContext(MainThreadFunction* function = 0, void* context = 0)
FunctionWithContext(MainThreadFunction* function = 0, void* context = 0, ThreadCondition* syncFlag = 0)
: function(function)
, context(context)
, syncFlag(syncFlag)
{
}
};
......@@ -92,6 +94,8 @@ void dispatchFunctionsFromMainThread()
}
invocation.function(invocation.context);
if (invocation.syncFlag)
invocation.syncFlag->signal();
// If we are running accumulated functions for too long so UI may become unresponsive, we need to
// yield so the user input can be processed. Otherwise user may not be able to even close the window.
......@@ -117,6 +121,24 @@ void callOnMainThread(MainThreadFunction* function, void* context)
scheduleDispatchFunctionsOnMainThread();
}
void callOnMainThreadAndWait(MainThreadFunction* function, void* context)
{
ASSERT(function);
if (isMainThread()) {
function(context);
return;
}
ThreadCondition syncFlag;
Mutex& functionQueueMutex = mainThreadFunctionQueueMutex();
MutexLocker locker(functionQueueMutex);
functionQueue().append(FunctionWithContext(function, context, &syncFlag));
if (functionQueue().size() == 1)
scheduleDispatchFunctionsOnMainThread();
syncFlag.wait(functionQueueMutex);
}
void setMainThreadCallbacksPaused(bool paused)
{
ASSERT(isMainThread());
......
......@@ -38,6 +38,9 @@ typedef void MainThreadFunction(void*);
void callOnMainThread(MainThreadFunction*, void* context);
// Blocks the thread until the call finishes on the main thread. Misusing this can easily cause deadlocks.
void callOnMainThreadAndWait(MainThreadFunction*, void* context);
void setMainThreadCallbacksPaused(bool paused);
// Must be called from the main thread (Darwin is an exception to this rule).
......@@ -52,6 +55,7 @@ void dispatchFunctionsFromMainThread();
} // namespace WTF
using WTF::callOnMainThread;
using WTF::callOnMainThreadAndWait;
using WTF::setMainThreadCallbacksPaused;
#endif // MainThread_h
2009-11-19 Alexey Proskuryakov <ap@apple.com>
Reviewed by Darin Adler.
https://bugs.webkit.org/show_bug.cgi?id=31690
Make SocketStreamHandleCFNet work on Windows
* WebCore.vcproj/WebCore.vcproj: Added LoaderRunLoopCF.
* platform/network/ResourceHandle.h: Removed loaderRunLoop().
* platform/network/cf/LoaderRunLoopCF.cpp: Added.
(WebCore::emptyPerform):
(WebCore::runLoaderThread):
(WebCore::loaderRunLoop):
* platform/network/cf/LoaderRunLoopCF.h: Added.
Moved the run loop that we use for CFNetwork from ResourceHandle to its own file, because
it's needed for more than just resource loading.
* platform/network/cf/ResourceHandleCFNet.cpp: Use loaderRunLoop() from its new location.
* platform/network/cf/DNSCFNet.cpp: (WebCore::DNSResolveQueue::resolve): Ditto.
* platform/network/cf/SocketStreamHandle.h: Added static callbacks for forwarding events to
main thread.
* platform/network/cf/SocketStreamHandleCFNet.cpp:
(WebCore::SocketStreamHandle::SocketStreamHandle): Use loaderRunLoop() on Windows instead of
inoperable main run loop.
(WebCore::MainThreadEventCallbackInfo::MainThreadEventCallbackInfo):
(WebCore::SocketStreamHandle::readStreamCallback):
(WebCore::SocketStreamHandle::writeStreamCallback):
(WebCore::SocketStreamHandle::readStreamCallbackMainThread):
(WebCore::SocketStreamHandle::writeStreamCallbackMainThread):
Forward stream events to main thread on Windows.
2009-11-19 Avi Drissman <avi@chromium.org>
Reviewed by Darin Adler.
......@@ -7776,6 +7776,10 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="$(WebKitOutputDir)\obj\$(ProjectName)\DerivedSources\JSInspectorBackend.cpp"
>
</File>
<File
RelativePath="$(WebKitOutputDir)\obj\$(ProjectName)\DerivedSources\JSInspectorBackend.cpp"
>
......@@ -7828,10 +7832,6 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="$(WebKitOutputDir)\obj\$(ProjectName)\DerivedSources\JSInspectorBackend.cpp"
>
</File>
<File
RelativePath="$(WebKitOutputDir)\obj\$(ProjectName)\DerivedSources\JSInspectorBackend.h"
>
......@@ -24288,6 +24288,14 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="..\platform\network\cf\LoaderRunLoopCF.cpp"
>
</File>
<File
RelativePath="..\platform\network\cf\LoaderRunLoopCF.h"
>
</File>
<File
RelativePath="..\platform\network\cf\ResourceError.h"
>
......@@ -25174,11 +25182,11 @@
>
</File>
<File
RelativePath="..\platform\text\TextBoundaries.h"
RelativePath="..\platform\text\TextBoundaries.cpp"
>
</File>
<File
RelativePath="..\platform\text\TextBoundaries.cpp"
RelativePath="..\platform\text\TextBoundaries.h"
>
</File>
<File
......@@ -33795,19 +33803,19 @@
>
</File>
<File
RelativePath="..\html\ISODateTime.cpp"
RelativePath="..\html\ImageData.cpp"
>
</File>
<File
RelativePath="..\html\ISODateTime.h"
RelativePath="..\html\ImageData.h"
>
</File>
<File
RelativePath="..\html\ImageData.cpp"
RelativePath="..\html\ISODateTime.cpp"
>
</File>
<File
RelativePath="..\html\ImageData.h"
RelativePath="..\html\ISODateTime.h"
>
</File>
<File
......
......@@ -138,7 +138,6 @@ public:
void schedule(SchedulePair*);
void unschedule(SchedulePair*);
#elif USE(CFNETWORK)
static CFRunLoopRef loaderRunLoop();
CFURLConnectionRef connection() const;
CFURLConnectionRef releaseConnectionForDownload();
static void setHostAllowsAnyHTTPSCertificate(const String&);
......
......@@ -33,7 +33,7 @@
#include <wtf/StdLibExtras.h>
#if PLATFORM(WIN)
#include "ResourceHandle.h" // for loaderRunLoop()
#include "LoaderRunLoopCF.h"
#endif
#ifdef BUILDING_ON_TIGER
......@@ -137,7 +137,7 @@ void DNSResolveQueue::resolve(const String& hostname)
CFHostScheduleWithRunLoop(host.get(), CFRunLoopGetMain(), kCFRunLoopCommonModes);
#else
// On Windows, we run a separate thread with CFRunLoop, which is where clientCallback will be called.
CFHostScheduleWithRunLoop(host.get(), ResourceHandle::loaderRunLoop(), kCFRunLoopDefaultMode);
CFHostScheduleWithRunLoop(host.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
#endif
CFHostStartInfoResolution(host.get(), kCFHostAddresses, 0);
host.releaseRef(); // The host will be released from clientCallback().
......
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 "LoaderRunLoopCF.h"
#include <wtf/Threading.h>
namespace WebCore {
static CFRunLoopRef loaderRunLoopObject = 0;
static void emptyPerform(void*)
{
}
static void* runLoaderThread(void*)
{
loaderRunLoopObject = CFRunLoopGetCurrent();
// Must add a source to the run loop to prevent CFRunLoopRun() from exiting.
CFRunLoopSourceContext ctxt = {0, (void*)1 /*must be non-NULL*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform};
CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
CFRunLoopAddSource(loaderRunLoopObject, bogusSource, kCFRunLoopDefaultMode);
CFRunLoopRun();
return 0;
}
CFRunLoopRef loaderRunLoop()
{
ASSERT(isMainThread());
if (!loaderRunLoopObject) {
createThread(runLoaderThread, 0, "WebCore: CFNetwork Loader");
while (!loaderRunLoopObject) {
// FIXME: Sleep 10? that can't be right...
Sleep(10);
}
}
return loaderRunLoopObject;
}
}
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 LoaderRunLoopCF_h
#define LoaderRunLoopCF_h
#if !PLATFORM(WIN)
#error This code is not needed on platforms other than Windows, because main thread's CFRunLoop can be used.
#endif
namespace WebCore {
CFRunLoopRef loaderRunLoop();
}
#endif // LoaderRunLoop_h
......@@ -39,6 +39,7 @@
#include "FormDataStreamCFNet.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "LoaderRunLoopCF.h"
#include "Logging.h"
#include "MIMETypeRegistry.h"
#include "ResourceError.h"
......@@ -322,37 +323,6 @@ CFArrayRef arrayFromFormData(const FormData& d)
return a;
}
void emptyPerform(void* unused)
{
}
static CFRunLoopRef loaderRL = 0;
void* runLoaderThread(void *unused)
{
loaderRL = CFRunLoopGetCurrent();
// Must add a source to the run loop to prevent CFRunLoopRun() from exiting
CFRunLoopSourceContext ctxt = {0, (void *)1 /*must be non-NULL*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform};
CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
CFRunLoopAddSource(loaderRL, bogusSource,kCFRunLoopDefaultMode);
CFRunLoopRun();
return 0;
}
CFRunLoopRef ResourceHandle::loaderRunLoop()
{
if (!loaderRL) {
createThread(runLoaderThread, 0, "WebCore: CFNetwork Loader");
while (loaderRL == 0) {
// FIXME: sleep 10? that can't be right...
Sleep(10);
}
}
return loaderRL;
}
static CFURLRequestRef makeFinalRequest(const ResourceRequest& request, bool shouldContentSniff)
{
CFMutableURLRequestRef newRequest = CFURLRequestCreateMutableCopy(kCFAllocatorDefault, request.cfURLRequest());
......
......@@ -64,7 +64,10 @@ namespace WebCore {
static CFStringRef copyCFStreamDescription(void* streamInfo);
static void readStreamCallback(CFReadStreamRef, CFStreamEventType, void* clientCallBackInfo);
static void writeStreamCallback(CFWriteStreamRef, CFStreamEventType, void* clientCallBackInfo);
#if PLATFORM(WIN)
static void readStreamCallbackMainThread(void* invocation);
static void writeStreamCallbackMainThread(void* invocation);
#endif
void readStreamCallback(CFStreamEventType);
void writeStreamCallback(CFStreamEventType);
......
......@@ -35,11 +35,16 @@
#include "Logging.h"
#include "SocketStreamError.h"
#include "SocketStreamHandleClient.h"
#include <wtf/MainThread.h>
#if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD)
#include <SystemConfiguration/SystemConfiguration.h>
#endif
#if PLATFORM(WIN)
#include "LoaderRunLoopCF.h"
#endif
#ifdef BUILDING_ON_TIGER
#define CFN_EXPORT extern
#endif
......@@ -77,9 +82,14 @@ SocketStreamHandle::SocketStreamHandle(const KURL& url, SocketStreamHandleClient
CFReadStreamSetClient(m_readStream.get(), static_cast<CFOptionFlags>(-1), readStreamCallback, &clientContext);
CFWriteStreamSetClient(m_writeStream.get(), static_cast<CFOptionFlags>(-1), writeStreamCallback, &clientContext);
#if PLATFORM(WIN)
CFReadStreamScheduleWithRunLoop(m_readStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
#else
CFReadStreamScheduleWithRunLoop(m_readStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFWriteStreamScheduleWithRunLoop(m_writeStream.get(), CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
#endif
CFReadStreamOpen(m_readStream.get());
CFWriteStreamOpen(m_writeStream.get());
......@@ -240,20 +250,52 @@ CFStringRef SocketStreamHandle::copyCFStreamDescription(void* info)
return ("WebKit socket stream, " + handle->m_url.string()).createCFString();
}
struct MainThreadEventCallbackInfo {
MainThreadEventCallbackInfo(CFStreamEventType type, SocketStreamHandle* handle) : type(type), handle(handle) { }
CFStreamEventType type;
SocketStreamHandle* handle;
};
void SocketStreamHandle::readStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
{
SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
ASSERT_UNUSED(stream, stream == handle->m_readStream.get());
#if PLATFORM(WIN)
MainThreadEventCallbackInfo info(type, handle);
callOnMainThreadAndWait(readStreamCallbackMainThread, &info);
#else
ASSERT(isMainThread());
handle->readStreamCallback(type);
#endif
}
void SocketStreamHandle::writeStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void* clientCallBackInfo)
{
SocketStreamHandle* handle = static_cast<SocketStreamHandle*>(clientCallBackInfo);
ASSERT_UNUSED(stream, stream == handle->m_writeStream.get());
#if PLATFORM(WIN)
MainThreadEventCallbackInfo info(type, handle);
callOnMainThreadAndWait(writeStreamCallbackMainThread, &info);
#else
ASSERT(isMainThread());
handle->writeStreamCallback(type);
#endif
}
#if PLATFORM(WIN)
void SocketStreamHandle::readStreamCallbackMainThread(void* invocation)
{
MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
info->handle->readStreamCallback(info->type);
}
void SocketStreamHandle::writeStreamCallbackMainThread(void* invocation)
{
MainThreadEventCallbackInfo* info = static_cast<MainThreadEventCallbackInfo*>(invocation);
info->handle->writeStreamCallback(info->type);
}
#endif // PLATFORM(WIN)
void SocketStreamHandle::readStreamCallback(CFStreamEventType type)
{
switch(type) {
......
2009-11-19 Alexey Proskuryakov <ap@apple.com>
Reviewed by Darin Adler.
https://bugs.webkit.org/show_bug.cgi?id=31690
Make SocketStreamHandleCFNet work on Windows
* WebDownloadCFNet.cpp:
(WebDownload::init):
(WebDownload::initWithRequest):
(WebDownload::initToResumeWithBundle):
Update for loaderRunLoop() now being in its own header.
2009-11-19 Eric Carlson <eric.carlson@apple.com>
Reviewed by Dan Bernstein.
......
......@@ -48,6 +48,7 @@
#include <WebCore/AuthenticationCF.h>
#include <WebCore/BString.h>
#include <WebCore/CredentialStorage.h>
#include <WebCore/LoaderRunLoopCF.h>
#include <WebCore/ResourceError.h>
#include <WebCore/ResourceHandle.h>
#include <WebCore/ResourceRequest.h>
......@@ -116,7 +117,7 @@ void WebDownload::init(const KURL& url, IWebDownloadDelegate* delegate)
m_download.adoptCF(CFURLDownloadCreate(0, cfRequest, &client));
CFURLDownloadScheduleWithCurrentMessageQueue(m_download.get());
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), ResourceHandle::loaderRunLoop(), kCFRunLoopDefaultMode);
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
LOG(Download, "WebDownload - Initialized download of url %s in WebDownload %p", url.string().utf8().data(), this);
}
......@@ -154,7 +155,7 @@ HRESULT STDMETHODCALLTYPE WebDownload::initWithRequest(
}
CFURLDownloadScheduleWithCurrentMessageQueue(m_download.get());
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), ResourceHandle::loaderRunLoop(), kCFRunLoopDefaultMode);
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
LOG(Download, "WebDownload - initWithRequest complete, started download of url %s", webRequest->resourceRequest().url().string().utf8().data());
return S_OK;
......@@ -200,7 +201,7 @@ HRESULT STDMETHODCALLTYPE WebDownload::initToResumeWithBundle(
m_destination = String();
CFURLDownloadScheduleWithCurrentMessageQueue(m_download.get());
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), ResourceHandle::loaderRunLoop(), kCFRunLoopDefaultMode);
CFURLDownloadScheduleDownloadWithRunLoop(m_download.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
LOG(Download, "WebDownload - initWithRequest complete, resumed download of bundle %s", String(bundlePath, SysStringLen(bundlePath)).ascii().data());
return S_OK;
......
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