WebKit2/WebProcess/Plugins/Netscape/NetscapePluginStream.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit2/WebProcess/Plugins/Netscape/NetscapePluginStream.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2010 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 "NetscapePluginStream.h"
+
+#include "NetscapePlugin.h"
+#include <utility>
+
+using namespace WebCore;
+using namespace std;
+
+namespace WebKit {
+
+NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, bool sendNotification, void* notificationData)
+    : m_plugin(plugin)
+    , m_streamID(streamID)
+    , m_sendNotification(sendNotification)
+    , m_notificationData(notificationData)
+    , m_npStream()
+    , m_transferMode(NP_NORMAL)
+    , m_offset(0)
+    , m_fileHandle(invalidPlatformFileHandle)
+    , m_isStarted(false)
+#if !ASSERT_DISABLED
+    , m_urlNotifyHasBeenCalled(false)
+#endif    
+    , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin)
+    , m_stopStreamWhenDoneDelivering(false)
+{
+}
+
+NetscapePluginStream::~NetscapePluginStream()
+{
+    ASSERT(!m_isStarted);
+    ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled);
+    ASSERT(m_fileHandle == invalidPlatformFileHandle);
+}
+
+void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
+{
+    // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here.
+    RefPtr<NetscapePluginStream> protect(this);
+
+    start(responseURL, streamLength, lastModifiedTime, mimeType, headers);
+}
+
+void NetscapePluginStream::didReceiveData(const char* bytes, int length)
+{
+    // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here.
+    RefPtr<NetscapePluginStream> protect(this);
+
+    deliverData(bytes, length);
+}
+
+void NetscapePluginStream::didFinishLoading()
+{
+    // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
+    RefPtr<NetscapePluginStream> protect(this);
+
+    stop(NPRES_DONE);
+}
+
+void NetscapePluginStream::didFail(bool wasCancelled)
+{
+    // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
+    RefPtr<NetscapePluginStream> protect(this);
+
+    stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR);
+}
+    
+void NetscapePluginStream::sendJavaScriptStream(const String& requestURLString, const String& result)
+{
+    // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep
+    // a reference to it here.
+    RefPtr<NetscapePluginStream> protect(this);
+
+    CString resultCString = requestURLString.utf8();
+    if (resultCString.isNull()) {
+        // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream.
+        notifyAndDestroyStream(NPRES_NETWORK_ERR);
+        return;
+    }
+
+    if (!start(requestURLString, resultCString.length(), 0, "text/plain", ""))
+        return;
+
+    deliverData(resultCString.data(), resultCString.length());
+    stop(NPRES_DONE);
+}
+
+NPError NetscapePluginStream::destroy(NPReason reason)
+{
+    // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet.
+    if (!m_isStarted)
+        return NPERR_GENERIC_ERROR;
+
+    // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE.
+    // (At least not for browser initiated streams, and we don't support plug-in initiated streams).
+    if (reason == NPRES_DONE)
+        return NPERR_INVALID_PARAM;
+
+    cancel();
+    stop(reason);
+    return NPERR_NO_ERROR;
+}
+
+static bool isSupportedTransferMode(uint16_t transferMode)
+{
+    switch (transferMode) {
+    case NP_ASFILEONLY:
+    case NP_ASFILE:
+    case NP_NORMAL:
+        return true;
+    // FIXME: We don't support seekable streams.
+    case NP_SEEK:
+        return false;
+    }
+
+    ASSERT_NOT_REACHED();
+    return false;
+}
+    
+bool NetscapePluginStream::start(const WebCore::String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
+{
+    m_responseURL = responseURLString.utf8();
+    m_mimeType = mimeType.utf8();
+    m_headers = headers.utf8();
+
+    m_npStream.ndata = this;
+    m_npStream.url = m_responseURL.data();
+    m_npStream.end = streamLength;
+    m_npStream.lastmodified = lastModifiedTime;
+    m_npStream.notifyData = m_notificationData;
+    m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data();
+
+    NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode);
+    if (error != NPERR_NO_ERROR) {
+        // We failed to start the stream, cancel the load and destroy it.
+        cancel();
+        notifyAndDestroyStream(NPRES_NETWORK_ERR);
+        return false;
+    }
+
+    // We successfully started the stream.
+    m_isStarted = true;
+
+    if (!isSupportedTransferMode(m_transferMode)) {
+        // Cancel the load and stop the stream.
+        cancel();
+        stop(NPRES_NETWORK_ERR);
+        return false;
+    }
+
+    return true;
+}
+
+void NetscapePluginStream::deliverData(const char* bytes, int length)
+{
+    ASSERT(m_isStarted);
+
+    if (m_transferMode != NP_ASFILEONLY) {
+        if (!m_deliveryData)
+            m_deliveryData.set(new Vector<char>);
+
+        m_deliveryData->reserveCapacity(m_deliveryData->size() + length);
+        m_deliveryData->append(bytes, length);
+        
+        deliverDataToPlugin();
+    }
+
+    if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
+        deliverDataToFile(bytes, length);
+}
+
+void NetscapePluginStream::deliverDataToPlugin()
+{
+    ASSERT(m_isStarted);
+
+    int32_t numBytesToDeliver = m_deliveryData->size();
+    int32_t numBytesDelivered = 0;
+
+    while (numBytesDelivered < numBytesToDeliver) {
+        int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream);
+        
+        // NPP_WriteReady could call NPN_DestroyStream and destroy the stream.
+        if (!m_isStarted)
+            return;
+
+        if (numBytesPluginCanHandle <= 0) {
+            // The plug-in can't handle more data, we'll send the rest later
+            m_deliveryDataTimer.startOneShot(0);
+            break;
+        }
+
+        // Figure out how much data to send to the plug-in.
+        int32_t dataLength = min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered);
+        char* data = m_deliveryData->data() + numBytesDelivered;
+
+        int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data);
+        if (numBytesWritten < 0) {
+            stop(NPRES_NETWORK_ERR);
+            return;
+        }
+
+        // NPP_Write could call NPN_DestroyStream and destroy the stream.
+        if (!m_isStarted)
+            return;
+
+        numBytesWritten = min(numBytesWritten, dataLength);
+        m_offset += numBytesWritten;
+        numBytesDelivered += numBytesWritten;
+    }
+
+    // We didn't write anything.
+    if (!numBytesDelivered)
+        return;
+
+    if (numBytesDelivered < numBytesToDeliver) {
+        // Remove the bytes that we actually delivered.
+        m_deliveryData->remove(0, numBytesDelivered);
+    } else {
+        m_deliveryData->clear();
+
+        if (m_stopStreamWhenDoneDelivering)
+            stop(NPRES_DONE);
+    }
+}
+
+void NetscapePluginStream::deliverDataToFile(const char* bytes, int length)
+{
+    if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) {
+        // Create a temporary file.
+        m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle);
+
+        // We failed to open the file, stop the stream.
+        if (m_fileHandle == invalidPlatformFileHandle) {
+            stop(NPRES_NETWORK_ERR);
+            return;
+        }
+    }
+
+    if (!length)
+        return;
+
+    int byteCount = writeToFile(m_fileHandle, bytes, length);
+    if (byteCount != length) {
+        // This happens only rarely, when we are out of disk space or have a disk I/O error.
+        closeFile(m_fileHandle);
+
+        stop(NPRES_NETWORK_ERR);
+    }
+}
+
+void NetscapePluginStream::stop(NPReason reason)
+{
+    ASSERT(m_isStarted);
+
+    if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) {
+        // There is still data left that the plug-in hasn't been able to consume yet.
+        ASSERT(m_deliveryDataTimer.isActive());
+        
+        // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires
+        // and calls deliverDataToPlugin the stream will be closed if all the remaining data was
+        // successfully delivered.
+        m_stopStreamWhenDoneDelivering = true;
+        return;
+    }
+
+    m_deliveryData = 0;
+    m_deliveryDataTimer.stop();
+
+    if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
+        if (reason == NPRES_DONE) {
+            // Ensure that the file is created.
+            deliverDataToFile(0, 0);
+            if (m_fileHandle != invalidPlatformFileHandle)
+                closeFile(m_fileHandle);
+            
+            ASSERT(!m_filePath.isNull());
+            
+            m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.data());
+        } else {
+            // Just close the file.
+            if (m_fileHandle != invalidPlatformFileHandle)
+                closeFile(m_fileHandle);
+        }
+
+        // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor.  It should be OK
+        // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
+        // (the stream destruction function), so there can be no expectation that a plugin will read the stream
+        // file asynchronously after NPP_StreamAsFile() is called.
+        deleteFile(String::fromUTF8(m_filePath.data()));
+        m_filePath = CString();
+
+        // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream.
+        if (!m_isStarted)
+            return;
+    }
+
+    // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream.
+    m_isStarted = false;
+
+    m_plugin->NPP_DestroyStream(&m_npStream, reason);
+
+    notifyAndDestroyStream(reason);
+}
+
+void NetscapePluginStream::cancel()
+{
+    m_plugin->cancelStreamLoad(this);
+}
+
+void NetscapePluginStream::notifyAndDestroyStream(NPReason reason)
+{
+    ASSERT(!m_isStarted);
+    ASSERT(!m_deliveryDataTimer.isActive());
+    ASSERT(!m_urlNotifyHasBeenCalled);
+    
+    if (m_sendNotification) {
+        m_plugin->NPP_URLNotify(m_responseURL.data(), reason, m_notificationData);
+    
+#if !ASSERT_DISABLED
+        m_urlNotifyHasBeenCalled = true;
+#endif    
+    }
+
+    m_plugin->removePluginStream(this);
+}
+
+} // namespace WebKit