/* * Copyright (C) 2006, 2007 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 COMPUTER, INC. ``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 COMPUTER, INC. OR * 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 "PluginViewWin.h" #include "Document.h" #include "Element.h" #include "EventNames.h" #include "FrameLoader.h" #include "FrameLoadRequest.h" #include "FrameTree.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsContext.h" #include "Image.h" #include "HTMLNames.h" #include "HTMLPlugInElement.h" #include "KeyboardEvent.h" #include "MouseEvent.h" #include "NotImplemented.h" #include "Page.h" #include "PlatformMouseEvent.h" #include "PluginPackageWin.h" #include "kjs_binding.h" #include "kjs_proxy.h" #include "kjs_window.h" #include "PluginDebug.h" #include "PluginPackageWin.h" #include "PluginStreamWin.h" #include "npruntime_impl.h" #include "runtime_root.h" #include "Settings.h" #include #include using KJS::ExecState; using KJS::Interpreter; using KJS::JSLock; using KJS::JSObject; using KJS::JSValue; using KJS::UString; using KJS::Window; using std::min; namespace WebCore { using namespace EventNames; using namespace HTMLNames; class PluginRequestWin { public: PluginRequestWin(const FrameLoadRequest& frameLoadRequest, bool sendNotification, void* notifyData) : m_frameLoadRequest(frameLoadRequest) , m_notifyData(notifyData) , m_sendNotification(sendNotification) { } public: const FrameLoadRequest& frameLoadRequest() const { return m_frameLoadRequest; } void* notifyData() const { return m_notifyData; } bool sendNotification() const { return m_sendNotification; } private: FrameLoadRequest m_frameLoadRequest; void* m_notifyData; bool m_sendNotification; // FIXME: user gesture }; static String scriptStringIfJavaScriptURL(const KURL& url) { if (!url.url().startsWith("javascript:", false)) return String(); // This returns an unescaped string return KURL::decode_string(url.url().mid(11)); } PluginViewWin* PluginViewWin::s_currentPluginView = 0; const LPCWSTR kWebPluginViewWindowClassName = L"WebPluginView"; const LPCWSTR kWebPluginViewProperty = L"WebPluginViewProperty"; static const char* MozillaUserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0"; static LRESULT CALLBACK PluginViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static bool registerPluginView() { static bool haveRegisteredWindowClass = false; if (haveRegisteredWindowClass) return true; ASSERT(Page::instanceHandle()); WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_DBLCLKS; wcex.lpfnWndProc = PluginViewWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = Page::instanceHandle(); wcex.hIcon = 0; wcex.hCursor = LoadCursor(0, IDC_ARROW); wcex.hbrBackground = (HBRUSH)COLOR_WINDOW; wcex.lpszMenuName = 0; wcex.lpszClassName = kWebPluginViewWindowClassName; wcex.hIconSm = 0; return RegisterClassEx(&wcex); } static LRESULT CALLBACK PluginViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PluginViewWin* pluginView = reinterpret_cast(GetProp(hWnd, kWebPluginViewProperty)); if (pluginView && (pluginView->quirks() & PluginQuirkWantsAsciiWindowProc)) return DefWindowProcA(hWnd, message, wParam, lParam); else return DefWindowProcW(hWnd, message, wParam, lParam); } void PluginViewWin::updateWindow() const { ASSERT(parent()->isFrameView()); FrameView* frameView = static_cast(parent()); IntRect oldWindowRect = m_windowRect; IntRect oldClipRect = m_clipRect; m_windowRect = IntRect(frameView->contentsToWindow(frameGeometry().location()), frameGeometry().size()); m_clipRect = windowClipRect(); m_clipRect.move(-m_windowRect.x(), -m_windowRect.y()); if (m_window && (m_windowRect != oldWindowRect || m_clipRect != oldClipRect)) { HRGN rgn; // To prevent flashes while scrolling, we disable drawing during the window // update process by clipping the window to the zero rect. // FIXME: Setting the window region to an empty region causes bad scrolling // repaint problems with at least the WMP and Java plugins. // Come up with a better way of handling plug-in scrolling // is the bug that tracks the work of fixing this. //rgn = ::CreateRectRgn(0, 0, 0, 0); //::SetWindowRgn(m_window, rgn, false); if (m_windowRect != oldWindowRect) ::MoveWindow(m_window, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), true); // Re-enable drawing. (This serves the double purpose of updating the clip rect if it has changed.) rgn = ::CreateRectRgn(m_clipRect.x(), m_clipRect.y(), m_clipRect.right(), m_clipRect.bottom()); ::SetWindowRgn(m_window, rgn, true); ::UpdateWindow(m_window); } } IntRect PluginViewWin::windowClipRect() const { // Start by clipping to our bounds. IntRect clipRect(m_windowRect); // Take our element and get the clip rect from the enclosing layer and frame view. RenderLayer* layer = m_element->renderer()->enclosingLayer(); FrameView* parentView = m_element->document()->view(); clipRect.intersect(parentView->windowClipRectForLayer(layer, true)); return clipRect; } void PluginViewWin::setFrameGeometry(const IntRect& rect) { if (m_element->document()->printing()) return; if (rect == frameGeometry()) return; Widget::setFrameGeometry(rect); updateWindow(); setNPWindowRect(rect); } void PluginViewWin::geometryChanged() const { updateWindow(); } void PluginViewWin::setFocus() { if (HWND window = m_window) SetFocus(window); Widget::setFocus(); } void PluginViewWin::show() { m_isVisible = true; if (HWND window = m_window) ShowWindow(window, SW_SHOWNA); Widget::show(); } void PluginViewWin::hide() { m_isVisible = false; if (HWND window = m_window) ShowWindow(window, SW_HIDE); Widget::hide(); } void PluginViewWin::paintMissingPluginIcon(GraphicsContext* context, const IntRect& rect) { static Image* nullPluginImage; if (!nullPluginImage) nullPluginImage = Image::loadPlatformResource("nullPlugin"); IntRect imageRect(frameGeometry().x(), frameGeometry().y(), nullPluginImage->width(), nullPluginImage->height()); int xOffset = (frameGeometry().width() - imageRect.width()) / 2; int yOffset = (frameGeometry().height() - imageRect.height()) / 2; imageRect.move(xOffset, yOffset); if (!rect.intersects(imageRect)) return; context->save(); context->clip(windowClipRect()); context->drawImage(nullPluginImage, imageRect.location()); context->restore(); } void PluginViewWin::paint(GraphicsContext* context, const IntRect& rect) { if (!m_isStarted) { // Draw the "missing plugin" image paintMissingPluginIcon(context, rect); return; } if (m_isWindowed || context->paintingDisabled()) return; HDC hdc = context->getWindowsContext(); // The plugin expects that the passed in DC has window coordinates. // (This probably breaks funky SVG transform stuff) XFORM transform; GetWorldTransform(hdc, &transform); transform.eDx = 0; transform.eDy = 0; SetWorldTransform(hdc, &transform); NPEvent npEvent; m_npWindow.type = NPWindowTypeDrawable; m_npWindow.window = hdc; ASSERT(parent()->isFrameView()); IntPoint p = static_cast(parent())->contentsToWindow(frameGeometry().location()); WINDOWPOS windowpos; memset(&windowpos, 0, sizeof(windowpos)); windowpos.x = p.x(); windowpos.y = p.y(); windowpos.cx = frameGeometry().width(); windowpos.cy = frameGeometry().height(); npEvent.event = WM_WINDOWPOSCHANGED; npEvent.lParam = reinterpret_cast(&windowpos); npEvent.wParam = 0; if (m_plugin->pluginFuncs()->event) { KJS::JSLock::DropAllLocks dropAllLocks; m_plugin->pluginFuncs()->event(m_instance, &npEvent); } setNPWindowRect(frameGeometry()); npEvent.event = WM_PAINT; npEvent.wParam = reinterpret_cast(hdc); // This is supposed to be a pointer to the dirty rect, but it seems that the Flash plugin // ignores it so we just pass null. npEvent.lParam = 0; if (m_plugin->pluginFuncs()->event) { KJS::JSLock::DropAllLocks dropAllLocks; m_plugin->pluginFuncs()->event(m_instance, &npEvent); } context->releaseWindowsContext(hdc); } void PluginViewWin::handleKeyboardEvent(KeyboardEvent* event) { NPEvent npEvent; npEvent.wParam = event->keyCode(); if (event->type() == keydownEvent) { npEvent.event = WM_KEYDOWN; npEvent.lParam = 0; } else if (event->type() == keyupEvent) { npEvent.event = WM_KEYUP; npEvent.lParam = 0x8000; } KJS::JSLock::DropAllLocks; if (!m_plugin->pluginFuncs()->event(m_instance, &npEvent)) event->setDefaultHandled(); } extern HCURSOR lastSetCursor; extern bool ignoreNextSetCursor; void PluginViewWin::handleMouseEvent(MouseEvent* event) { NPEvent npEvent; IntPoint p = static_cast(parent())->contentsToWindow(IntPoint(event->pageX(), event->pageY())); npEvent.lParam = MAKELPARAM(p.x(), p.y()); npEvent.wParam = 0; if (event->ctrlKey()) npEvent.wParam |= MK_CONTROL; if (event->shiftKey()) npEvent.wParam |= MK_SHIFT; if (event->type() == mousemoveEvent) { npEvent.event = WM_MOUSEMOVE; if (event->buttonDown()) switch (event->button()) { case LeftButton: npEvent.wParam |= MK_LBUTTON; break; case MiddleButton: npEvent.wParam |= MK_MBUTTON; break; case RightButton: npEvent.wParam |= MK_RBUTTON; break; } } else if (event->type() == mousedownEvent) { switch (event->button()) { case 0: npEvent.event = WM_LBUTTONDOWN; // Focus the plugin m_parentFrame->document()->setFocusedNode(m_element); break; case 1: npEvent.event = WM_MBUTTONDOWN; break; case 2: npEvent.event = WM_RBUTTONDOWN; break; } } else if (event->type() == mouseupEvent) { switch (event->button()) { case 0: npEvent.event = WM_LBUTTONUP; break; case 1: npEvent.event = WM_MBUTTONUP; break; case 2: npEvent.event = WM_RBUTTONUP; break; } } else return; HCURSOR currentCursor = ::GetCursor(); KJS::JSLock::DropAllLocks; if (!m_plugin->pluginFuncs()->event(m_instance, &npEvent)) event->setDefaultHandled(); // Currently, Widget::setCursor is always called after this function in EventHandler.cpp // and since we don't want that we set ignoreNextSetCursor to true here to prevent that. ignoreNextSetCursor = true; lastSetCursor = ::GetCursor(); } void PluginViewWin::handleEvent(Event* event) { if (!m_plugin || m_isWindowed) return; if (event->isMouseEvent()) handleMouseEvent(static_cast(event)); else if (event->isKeyboardEvent()) handleKeyboardEvent(static_cast(event)); } void PluginViewWin::setParent(ScrollView* parent) { Widget::setParent(parent); init(); } void PluginViewWin::setNPWindowRect(const IntRect& rect) { if (!m_isStarted) return; IntPoint p = static_cast(parent())->contentsToWindow(rect.location()); m_npWindow.x = p.x(); m_npWindow.y = p.y(); m_npWindow.width = rect.width(); m_npWindow.height = rect.height(); m_npWindow.clipRect.left = 0; m_npWindow.clipRect.top = 0; m_npWindow.clipRect.right = rect.width(); m_npWindow.clipRect.bottom = rect.height(); if (m_plugin->pluginFuncs()->setwindow) { KJS::JSLock::DropAllLocks dropAllLocks; m_plugin->pluginFuncs()->setwindow(m_instance, &m_npWindow); } } bool PluginViewWin::start() { if (m_isStarted) return false; ASSERT(m_plugin); ASSERT(m_plugin->pluginFuncs()->newp); NPError npErr; PluginViewWin::setCurrentPluginView(this); { KJS::JSLock::DropAllLocks dropAllLocks; npErr = m_plugin->pluginFuncs()->newp((NPMIMEType)m_mimeType.data(), m_instance, m_mode, m_paramCount, m_paramNames, m_paramValues, NULL); LOG_NPERROR(npErr); } PluginViewWin::setCurrentPluginView(0); if (npErr != NPERR_NO_ERROR) return false; m_isStarted = true; if (m_url.isValid()) { FrameLoadRequest frameLoadRequest; frameLoadRequest.resourceRequest().setHTTPMethod("GET"); frameLoadRequest.resourceRequest().setURL(m_url); load(frameLoadRequest, false, 0); } return true; } void PluginViewWin::stop() { if (!m_isStarted) return; HashSet > streams = m_streams; HashSet >::iterator end = streams.end(); for (HashSet >::iterator it = streams.begin(); it != end; ++it) { (*it)->stop(); disconnectStream((*it).get()); } ASSERT(m_streams.isEmpty()); m_isStarted = false; KJS::JSLock::DropAllLocks; // Clear the window m_npWindow.window = 0; if (m_plugin->pluginFuncs()->setwindow) m_plugin->pluginFuncs()->setwindow(m_instance, &m_npWindow); // Destroy the plugin NPError npErr = m_plugin->pluginFuncs()->destroy(m_instance, 0); LOG_NPERROR(npErr); m_instance->pdata = 0; } void PluginViewWin::setCurrentPluginView(PluginViewWin* pluginView) { s_currentPluginView = pluginView; } PluginViewWin* PluginViewWin::currentPluginView() { return s_currentPluginView; } static char* createUTF8String(const String& str) { CString cstr = str.utf8(); char* result = reinterpret_cast(fastMalloc(cstr.length() + 1)); strncpy(result, cstr.data(), cstr.length() + 1); return result; } static void freeStringArray(char** stringArray, int length) { if (!stringArray) return; for (int i = 0; i < length; i++) fastFree(stringArray[i]); fastFree(stringArray); } static bool getString(KJSProxy* proxy, JSValue* result, String& string) { if (!proxy || !result || result->isUndefined()) return false; JSLock lock; ExecState* exec = proxy->interpreter()->globalExec(); UString ustring = result->toString(exec); exec->clearException(); string = ustring; return true; } void PluginViewWin::performRequest(PluginRequestWin* request) { KURL requestURL = request->frameLoadRequest().resourceRequest().url(); String jsString = scriptStringIfJavaScriptURL(requestURL); if (jsString.isNull()) { m_parentFrame->loader()->urlSelected(request->frameLoadRequest(), 0, false, true); // FIXME: This should be sent when the document has finished loading if (request->sendNotification()) { KJS::JSLock::DropAllLocks dropAllLocks; m_plugin->pluginFuncs()->urlnotify(m_instance, requestURL.url().utf8(), NPRES_DONE, request->notifyData()); } return; } // Targeted JavaScript requests are only allowed on the frame that contains the JavaScript plugin // and this has been made sure in ::load. ASSERT(request->frameLoadRequest().frameName().isEmpty() || m_parentFrame->tree()->find(request->frameLoadRequest().frameName()) == m_parentFrame); // Executing a script can cause the plugin view to be destroyed, so we keep a reference to the parent frame. RefPtr parentFrame = m_parentFrame; JSValue* result = m_parentFrame->loader()->executeScript(jsString.deprecatedString(), true); String resultString; if (!getString(parentFrame->scriptProxy(), result, resultString)) return; if (!request->frameLoadRequest().frameName().isNull()) { parentFrame->loader()->begin(); parentFrame->loader()->write(resultString); parentFrame->loader()->end(); } else { CString cstr = resultString.utf8(); RefPtr stream = new PluginStreamWin(this, parentFrame.get(), request->frameLoadRequest().resourceRequest(), request->sendNotification(), request->notifyData()); m_streams.add(stream); stream->sendJavaScriptStream(requestURL, cstr); } } void PluginViewWin::requestTimerFired(Timer* timer) { ASSERT(timer == &m_requestTimer); ASSERT(m_requests.size() > 0); PluginRequestWin* request = m_requests[0]; m_requests.remove(0); // Schedule a new request before calling performRequest since the call to // performRequest can cause the plugin view to be deleted. if (m_requests.size() > 0) m_requestTimer.startOneShot(0); performRequest(request); } void PluginViewWin::scheduleRequest(PluginRequestWin* request) { m_requests.append(request); m_requestTimer.startOneShot(0); } NPError PluginViewWin::load(const FrameLoadRequest& frameLoadRequest, bool sendNotification, void* notifyData) { ASSERT(frameLoadRequest.resourceRequest().httpMethod() == "GET" || frameLoadRequest.resourceRequest().httpMethod() == "POST"); KURL url = frameLoadRequest.resourceRequest().url(); if (!url.isValid()) return NPERR_INVALID_URL; // FIXME: don't let a plugin start any loads if it is no longer part of a document that is being // displayed String target = frameLoadRequest.frameName(); String jsString = scriptStringIfJavaScriptURL(url); if (!jsString.isNull()) { Settings* settings = m_parentFrame->settings(); if (!settings || !settings->isJavaScriptEnabled()) { // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does. return NPERR_GENERIC_ERROR; } else if (target.isNull() && m_mode == NP_FULL) { // Don't allow a JavaScript request from a standalone plug-in that is self-targetted // because this can cause the user to be redirected to a blank page (3424039). return NPERR_INVALID_PARAM; } } if (!jsString.isNull() || !target.isNull()) { if (!jsString.isNull() && !target.isNull() && m_parentFrame->tree()->find(target) != m_parentFrame) { // For security reasons, only allow JS requests to be made on the frame that contains the plug-in. return NPERR_INVALID_PARAM; } PluginRequestWin* request = new PluginRequestWin(frameLoadRequest, sendNotification, notifyData); scheduleRequest(request); } else { PluginStreamWin* stream = new PluginStreamWin(this, m_parentFrame, frameLoadRequest.resourceRequest(), sendNotification, notifyData); m_streams.add(stream); stream->start(); } return NPERR_NO_ERROR; } static KURL makeURL(const KURL& baseURL, const char* relativeURLString) { DeprecatedString urlString = DeprecatedString::fromLatin1(relativeURLString); // Strip return characters urlString.replace('\n', ""); urlString.replace('\r', ""); return KURL(baseURL, urlString); } NPError PluginViewWin::getURLNotify(const char* url, const char* target, void* notifyData) { FrameLoadRequest frameLoadRequest; frameLoadRequest.setFrameName(target); frameLoadRequest.resourceRequest().setHTTPMethod("GET"); frameLoadRequest.resourceRequest().setURL(makeURL(m_baseURL, url)); return load(frameLoadRequest, true, notifyData); } NPError PluginViewWin::getURL(const char* url, const char* target) { FrameLoadRequest frameLoadRequest; frameLoadRequest.setFrameName(target); frameLoadRequest.resourceRequest().setHTTPMethod("GET"); frameLoadRequest.resourceRequest().setURL(makeURL(m_baseURL, url)); return load(frameLoadRequest, false, 0); } static inline bool startsWithBlankLine(const Vector& buffer) { return buffer.size() > 0 && buffer[0] == '\n'; } static inline int locationAfterFirstBlankLine(const Vector& buffer) { const char* bytes = buffer.data(); unsigned length = buffer.size(); for (unsigned i = 0; i < length - 4; i++) { // Support for Acrobat. It sends "\n\n". if (bytes[i] == '\n' && bytes[i + 1] == '\n') return i + 2; // Returns the position after 2 CRLF's or 1 CRLF if it is the first line. if (bytes[i] == '\r' && bytes[i + 1] == '\n') { i += 2; if (i == 2) return i; else if (bytes[i] == '\n') // Support for Director. It sends "\r\n\n" (3880387). return i + 1; else if (bytes[i] == '\r' && bytes[i + 1] == '\n') // Support for Flash. It sends "\r\n\r\n" (3758113). return i + 2; } } return -1; } static inline const char* findEOL(const char* bytes, unsigned length) { // According to the HTTP specification EOL is defined as // a CRLF pair. Unfortunately, some servers will use LF // instead. Worse yet, some servers will use a combination // of both (e.g.
CRLFLF), so findEOL needs // to be more forgiving. It will now accept CRLF, LF or // CR. // // It returns NULL if EOLF is not found or it will return // a pointer to the first terminating character. for (unsigned i = 0; i < length; i++) { if (bytes[i] == '\n') return bytes + i; if (bytes[i] == '\r') { // Check to see if spanning buffer bounds // (CRLF is across reads). If so, wait for // next read. if (i + 1 == length) break; return bytes + i; } } return 0; } static inline String capitalizeRFC822HeaderFieldName(const String& name) { bool capitalizeCharacter = true; String result; for (unsigned i = 0; i < name.length(); i++) { UChar c; if (capitalizeCharacter && name[i] >= 'a' && name[i] <= 'z') c = toupper(name[i]); else if (!capitalizeCharacter && name[i] >= 'A' && name[i] <= 'Z') c = tolower(name[i]); else c = name[i]; if (name[i] == '-') capitalizeCharacter = true; else capitalizeCharacter = false; result.append(c); } return result; } static inline HTTPHeaderMap parseRFC822HeaderFields(const Vector& buffer, unsigned length) { const char* bytes = buffer.data(); const char* eol; String lastKey; HTTPHeaderMap headerFields; // Loop ove rlines until we're past the header, or we can't find any more end-of-lines while ((eol = findEOL(bytes, length))) { const char* line = bytes; int lineLength = eol - bytes; // Move bytes to the character after the terminator as returned by findEOL. bytes = eol + 1; if ((*eol == '\r') && (*bytes == '\n')) bytes++; // Safe since findEOL won't return a spanning CRLF. length -= (bytes - line); if (lineLength == 0) // Blank line; we're at the end of the header break; else if (*line == ' ' || *line == '\t') { // Continuation of the previous header if (lastKey.isNull()) { // malformed header; ignore it and continue continue; } else { // Merge the continuation of the previous header String currentValue = headerFields.get(lastKey); String newValue = DeprecatedString::fromLatin1(line, lineLength); headerFields.set(lastKey, currentValue + newValue); } } else { // Brand new header const char* colon; for (colon = line; *colon != ':' && colon != eol; colon++) { // empty loop } if (colon == eol) // malformed header; ignore it and continue continue; else { lastKey = capitalizeRFC822HeaderFieldName(DeprecatedString::fromLatin1(line, colon - line)); String value; for (colon++; colon != eol; colon++) { if (*colon != ' ' && *colon != '\t') break; } if (colon == eol) value = ""; else value = DeprecatedString::fromLatin1(colon, eol - colon); String oldValue = headerFields.get(lastKey); if (!oldValue.isNull()) { String tmp = oldValue; tmp += ", "; tmp += value; value = tmp; } headerFields.set(lastKey, value); } } } return headerFields; } NPError PluginViewWin::handlePost(const char* url, const char* target, uint32 len, const char* buf, bool file, void* notifyData, bool sendNotification, bool allowHeaders) { if (!url || !len || !buf) return NPERR_INVALID_PARAM; FrameLoadRequest frameLoadRequest; HTTPHeaderMap headerFields; Vector buffer; if (file) { String filename = DeprecatedString::fromLatin1(buf, len); if (filename.startsWith("file:///")) filename = filename.substring(8); // Get file info WIN32_FILE_ATTRIBUTE_DATA attrs; if (GetFileAttributesExW(filename.charactersWithNullTermination(), GetFileExInfoStandard, &attrs) == 0) return NPERR_FILE_NOT_FOUND; if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) return NPERR_FILE_NOT_FOUND; HANDLE fileHandle = CreateFileW(filename.charactersWithNullTermination(), FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if (fileHandle == INVALID_HANDLE_VALUE) return NPERR_FILE_NOT_FOUND; buffer.resize(attrs.nFileSizeLow); DWORD bytesRead; int retval = ReadFile(fileHandle, buffer.data(), attrs.nFileSizeLow, &bytesRead, 0); CloseHandle(fileHandle); if (retval == 0 || bytesRead != attrs.nFileSizeLow) return NPERR_FILE_NOT_FOUND; } else { buffer.resize(len); memcpy(buffer.data(), buf, len); } const char* postData = buffer.data(); int postDataLength = buffer.size(); if (allowHeaders) { if (startsWithBlankLine(buffer)) { postData++; postDataLength--; } else { int location = locationAfterFirstBlankLine(buffer); if (location != -1) { // If the blank line is somewhere in the middle of the buffer, everything before is the header headerFields = parseRFC822HeaderFields(buffer, location); unsigned dataLength = buffer.size() - location; // Sometimes plugins like to set Content-Length themselves when they post, // but WebFoundation does not like that. So we will remove the header // and instead truncate the data to the requested length. String contentLength = headerFields.get("Content-Length"); if (!contentLength.isNull()) dataLength = min(contentLength.toInt(), (int)dataLength); headerFields.remove("Content-Length"); postData += location; postDataLength = dataLength; } } } frameLoadRequest.resourceRequest().setHTTPMethod("POST"); frameLoadRequest.resourceRequest().setURL(makeURL(m_baseURL, url)); frameLoadRequest.resourceRequest().addHTTPHeaderFields(headerFields); frameLoadRequest.resourceRequest().setHTTPBody(PassRefPtr(new FormData(postData, postDataLength))); frameLoadRequest.setFrameName(target); return load(frameLoadRequest, sendNotification, notifyData); } NPError PluginViewWin::postURLNotify(const char* url, const char* target, uint32 len, const char* buf, NPBool file, void* notifyData) { return handlePost(url, target, len, buf, file, notifyData, true, true); } NPError PluginViewWin::postURL(const char* url, const char* target, uint32 len, const char* buf, NPBool file) { // As documented, only allow headers to be specified via NPP_PostURL when using a file. return handlePost(url, target, len, buf, file, 0, false, file); } NPError PluginViewWin::newStream(NPMIMEType type, const char* target, NPStream** stream) { notImplemented(); // Unsupported return NPERR_GENERIC_ERROR; } int32 PluginViewWin::write(NPStream* stream, int32 len, void* buffer) { notImplemented(); // Unsupported return -1; } NPError PluginViewWin::destroyStream(NPStream* stream, NPReason reason) { PluginStreamWin* browserStream = static_cast(stream->ndata); if (!stream || PluginStreamWin::ownerForStream(stream) != m_instance) return NPERR_INVALID_INSTANCE_ERROR; browserStream->cancelAndDestroyStream(reason); return NPERR_NO_ERROR; } const char* PluginViewWin::userAgent() { if (m_quirks & PluginQuirkWantsMozillaUserAgent) return MozillaUserAgent; if (m_userAgent.isNull()) m_userAgent = m_parentFrame->loader()->userAgent(m_url).utf8(); return m_userAgent.data(); } void PluginViewWin::status(const char* message) { String s = DeprecatedString::fromLatin1(message); if (Page* page = m_parentFrame->page()) page->chrome()->setStatusbarText(m_parentFrame, s); } NPError PluginViewWin::getValue(NPNVariable variable, void* value) { switch (variable) { case NPNVWindowNPObject: { NPObject* windowScriptObject = m_parentFrame->windowScriptNPObject(); // Return value is expected to be retained, as described here: if (windowScriptObject) _NPN_RetainObject(windowScriptObject); void** v = (void**)value; *v = windowScriptObject; return NPERR_NO_ERROR; } case NPNVPluginElementNPObject: { NPObject* pluginScriptObject = 0; if (m_element->hasTagName(appletTag) || m_element->hasTagName(embedTag) || m_element->hasTagName(objectTag)) pluginScriptObject = static_cast(m_element)->getNPObject(); // Return value is expected to be retained, as described here: if (pluginScriptObject) _NPN_RetainObject(pluginScriptObject); void** v = (void**)value; *v = pluginScriptObject; return NPERR_NO_ERROR; } case NPNVnetscapeWindow: { HWND* w = reinterpret_cast(value); *w = containingWindow(); return NPERR_NO_ERROR; } default: return NPERR_GENERIC_ERROR; } } NPError PluginViewWin::setValue(NPPVariable variable, void* value) { switch (variable) { case NPPVpluginWindowBool: m_isWindowed = value; return NPERR_NO_ERROR; case NPPVpluginTransparentBool: m_isTransparent = value; return NPERR_NO_ERROR; default: notImplemented(); return NPERR_GENERIC_ERROR; } } void PluginViewWin::invalidateTimerFired(Timer* timer) { ASSERT(timer == &m_invalidateTimer); for (unsigned i = 0; i < m_invalidRects.size(); i++) Widget::invalidateRect(m_invalidRects[i]); m_invalidRects.clear(); } void PluginViewWin::invalidateRect(NPRect* rect) { if (!rect) { invalidate(); return; } IntRect r(rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top); if (m_isWindowed) { RECT invalidRect(r); InvalidateRect(m_window, &invalidRect, FALSE); } else { if (m_quirks & PluginQuirkThrottleInvalidate) { m_invalidRects.append(r); if (!m_invalidateTimer.isActive()) m_invalidateTimer.startOneShot(0.0); } else Widget::invalidateRect(r); } } void PluginViewWin::invalidateRegion(NPRegion region) { if (m_isWindowed) return; RECT r; if (GetRgnBox(region, &r) == 0) { invalidate(); return; } Widget::invalidateRect(r); } void PluginViewWin::forceRedraw() { if (m_isWindowed) ::UpdateWindow(m_window); else ::UpdateWindow(containingWindow()); } KJS::Bindings::Instance* PluginViewWin::bindingInstance() { NPObject* object = 0; if (!m_plugin || !m_plugin->pluginFuncs()->getvalue) return 0; NPError npErr; { KJS::JSLock::DropAllLocks dropAllLocks; npErr = m_plugin->pluginFuncs()->getvalue(m_instance, NPPVpluginScriptableNPObject, &object); } if (npErr != NPERR_NO_ERROR || !object) return 0; RefPtr root = m_parentFrame->createRootObject(this, m_parentFrame->scriptProxy()->interpreter()); KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance(KJS::Bindings::Instance::CLanguage, object, root.release()); _NPN_ReleaseObject(object); return instance; } PluginViewWin::~PluginViewWin() { stop(); deleteAllValues(m_requests); freeStringArray(m_paramNames, m_paramCount); freeStringArray(m_paramValues, m_paramCount); if (m_window) DestroyWindow(m_window); m_parentFrame->cleanupScriptObjectsForPlugin(this); if (m_plugin) m_plugin->unload(); } void PluginViewWin::disconnectStream(PluginStreamWin* stream) { ASSERT(m_streams.contains(stream)); m_streams.remove(stream); } void PluginViewWin::determineQuirks(const String& mimeType) { // The flash plugin only requests windowless plugins if we return a mozilla user agent if (mimeType == "application/x-shockwave-flash") { m_quirks |= PluginQuirkWantsMozillaUserAgent; m_quirks |= PluginQuirkThrottleInvalidate; } // The WMP plugin sets its size on the first NPP_SetWindow call and never updates its size, so // call SetWindow when the plugin view has a correct size if (m_plugin->name().contains("Microsoft") && m_plugin->name().contains("Windows Media")) { m_quirks |= PluginQuirkDeferFirstSetWindowCall; // Windowless mode does not work at all with the WMP plugin so just remove that parameter // and don't pass it to the plug-in. m_quirks |= PluginQuirkRemoveWindowlessVideoParam; } // The DivX plugin sets its size on the first NPP_SetWindow call and never updates its size, so // call SetWindow when the plugin view has a correct size if (mimeType == "video/divx") m_quirks |= PluginQuirkDeferFirstSetWindowCall; // Shockwave calls SetWindowLongA to set a new WNDPROC on its plugin window. The value returned from SetWindowLongA is the old WNDPROC. // If the previous WNDPROC was an Unicode WNDPROC, the address of the WNDPROC will not be returned. Instead, a special // value that indicates that the messages coming to the WNDPROC need to be translated to Unicode. If CallWndProc is used to // call the WNDPROC, it knows when the value is a real function pointer or not. If it's not, the message should be translated. // The Shockwave plugin however blindly treats the WNDPROC as a function pointer and tries to call it. Because of this, we set an ASCII // WNDPROC on the plugin window so that the value returned to Shockwave will be a real function pointer. // For more info on this, see http://blogs.msdn.com/oldnewthing/archive/2003/12/01/55900.aspx if (mimeType == "application/x-director") m_quirks |= PluginQuirkWantsAsciiWindowProc; } void PluginViewWin::setParameters(const Vector& paramNames, const Vector& paramValues) { ASSERT(paramNames.size() == paramValues.size()); unsigned size = paramNames.size(); unsigned paramCount = 0; m_paramNames = reinterpret_cast(fastMalloc(sizeof(char*) * size)); m_paramValues = reinterpret_cast(fastMalloc(sizeof(char*) * size)); for (unsigned i = 0; i < size; i++) { if ((m_quirks & PluginQuirkRemoveWindowlessVideoParam) && equalIgnoringCase(paramNames[i], "windowlessvideo")) continue; m_paramNames[paramCount] = createUTF8String(paramNames[i]); m_paramValues[paramCount] = createUTF8String(paramValues[i]); paramCount++; } m_paramCount = paramCount; } PluginViewWin::PluginViewWin(Frame* parentFrame, PluginPackageWin* plugin, Element* element, const KURL& url, const Vector& paramNames, const Vector& paramValues, const String& mimeType) : m_parentFrame(parentFrame) , m_plugin(plugin) , m_element(element) , m_isStarted(false) , m_url(url) , m_baseURL(m_parentFrame->loader()->completeURL(m_parentFrame->document()->baseURL())) , m_status(PluginStatusLoadedSuccessfully) , m_requestTimer(this, &PluginViewWin::requestTimerFired) , m_invalidateTimer(this, &PluginViewWin::invalidateTimerFired) , m_paramNames(0) , m_paramValues(0) , m_window(0) , m_quirks(0) , m_isWindowed(true) , m_isTransparent(false) , m_isVisible(false) , m_haveInitialized(false) { if (!m_plugin) { m_status = PluginStatusCanNotFindPlugin; return; } m_instance = &m_instanceStruct; m_instance->ndata = this; m_mimeType = mimeType.utf8(); determineQuirks(mimeType); setParameters(paramNames, paramValues); m_mode = element->document()->isPluginDocument() ? NP_FULL : NP_EMBED; } void PluginViewWin::init() { if (m_haveInitialized) return; m_haveInitialized = true; if (!m_plugin) { ASSERT(m_status == PluginStatusCanNotFindPlugin); return; } if (!m_plugin->load()) { m_plugin = 0; m_status = PluginStatusCanNotLoadPlugin; return; } if (!start()) { m_status = PluginStatusCanNotLoadPlugin; return; } if (m_isWindowed) { registerPluginView(); DWORD flags = WS_CHILD; if (m_isVisible) flags |= WS_VISIBLE; m_window = CreateWindowEx(0, kWebPluginViewWindowClassName, 0, flags, 0, 0, 0, 0, m_parentFrame->view()->containingWindow(), 0, Page::instanceHandle(), 0); if (m_quirks & PluginQuirkWantsAsciiWindowProc) ::SetWindowLongPtrA(m_window, GWL_WNDPROC, (LONG)PluginViewWndProc); SetProp(m_window, kWebPluginViewProperty, this); m_npWindow.type = NPWindowTypeWindow; m_npWindow.window = m_window; } else { m_npWindow.type = NPWindowTypeDrawable; m_npWindow.window = 0; } if (!(m_quirks & PluginQuirkDeferFirstSetWindowCall)) setNPWindowRect(frameGeometry()); m_status = PluginStatusLoadedSuccessfully; } } // namespace WebCore