| /* |
| * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 Collabora, Ltd. 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 "PluginView.h" |
| |
| #include "Document.h" |
| #include "DocumentLoader.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 "MIMETypeRegistry.h" |
| #include "MouseEvent.h" |
| #include "NotImplemented.h" |
| #include "Page.h" |
| #include "FocusController.h" |
| #include "PlatformMouseEvent.h" |
| #include "PluginPackage.h" |
| #include "kjs_binding.h" |
| #include "kjs_proxy.h" |
| #include "kjs_window.h" |
| #include "PluginDebug.h" |
| #include "PluginPackage.h" |
| #include "npruntime_impl.h" |
| #include "runtime_root.h" |
| #include "Settings.h" |
| #include <kjs/JSLock.h> |
| #include <kjs/value.h> |
| #include <wtf/ASCIICType.h> |
| |
| using KJS::ExecState; |
| using KJS::JSLock; |
| using KJS::JSObject; |
| using KJS::JSValue; |
| using KJS::UString; |
| using KJS::Window; |
| |
| using std::min; |
| |
| using namespace WTF; |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| class PluginRequest { |
| public: |
| PluginRequest(const FrameLoadRequest& frameLoadRequest, bool sendNotification, void* notifyData, bool shouldAllowPopups) |
| : m_frameLoadRequest(frameLoadRequest) |
| , m_notifyData(notifyData) |
| , m_sendNotification(sendNotification) |
| , m_shouldAllowPopups(shouldAllowPopups) { } |
| public: |
| const FrameLoadRequest& frameLoadRequest() const { return m_frameLoadRequest; } |
| void* notifyData() const { return m_notifyData; } |
| bool sendNotification() const { return m_sendNotification; } |
| bool shouldAllowPopups() const { return m_shouldAllowPopups; } |
| private: |
| FrameLoadRequest m_frameLoadRequest; |
| void* m_notifyData; |
| bool m_sendNotification; |
| bool m_shouldAllowPopups; |
| }; |
| |
| static const double MessageThrottleTimeInterval = 0.001; |
| static int s_callingPlugin; |
| |
| class PluginMessageThrottlerWin { |
| public: |
| PluginMessageThrottlerWin(PluginView* pluginView) |
| : m_back(0), m_front(0) |
| , m_pluginView(pluginView) |
| , m_messageThrottleTimer(this, &PluginMessageThrottlerWin::messageThrottleTimerFired) |
| { |
| // Initialize the free list with our inline messages |
| for (unsigned i = 0; i < NumInlineMessages - 1; i++) |
| m_inlineMessages[i].next = &m_inlineMessages[i + 1]; |
| m_inlineMessages[NumInlineMessages - 1].next = 0; |
| m_freeInlineMessages = &m_inlineMessages[0]; |
| } |
| |
| ~PluginMessageThrottlerWin() |
| { |
| PluginMessage* next; |
| |
| for (PluginMessage* message = m_front; message; message = next) { |
| next = message->next; |
| freeMessage(message); |
| } |
| } |
| |
| void appendMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| PluginMessage* message = allocateMessage(); |
| |
| message->hWnd = hWnd; |
| message->msg = msg; |
| message->wParam = wParam; |
| message->lParam = lParam; |
| message->next = 0; |
| |
| if (m_back) |
| m_back->next = message; |
| m_back = message; |
| if (!m_front) |
| m_front = message; |
| |
| if (!m_messageThrottleTimer.isActive()) |
| m_messageThrottleTimer.startOneShot(MessageThrottleTimeInterval); |
| } |
| |
| private: |
| struct PluginMessage { |
| HWND hWnd; |
| UINT msg; |
| WPARAM wParam; |
| LPARAM lParam; |
| |
| struct PluginMessage* next; |
| }; |
| |
| void messageThrottleTimerFired(Timer<PluginMessageThrottlerWin>*) |
| { |
| PluginMessage* message = m_front; |
| m_front = m_front->next; |
| if (message == m_back) |
| m_back = 0; |
| |
| ::CallWindowProc(m_pluginView->pluginWndProc(), message->hWnd, message->msg, message->wParam, message->lParam); |
| |
| freeMessage(message); |
| |
| if (m_front) |
| m_messageThrottleTimer.startOneShot(MessageThrottleTimeInterval); |
| } |
| |
| PluginMessage* allocateMessage() |
| { |
| PluginMessage *message; |
| |
| if (m_freeInlineMessages) { |
| message = m_freeInlineMessages; |
| m_freeInlineMessages = message->next; |
| } else |
| message = new PluginMessage; |
| |
| return message; |
| } |
| |
| bool isInlineMessage(PluginMessage* message) |
| { |
| return message >= &m_inlineMessages[0] && message <= &m_inlineMessages[NumInlineMessages - 1]; |
| } |
| |
| void freeMessage(PluginMessage* message) |
| { |
| if (isInlineMessage(message)) { |
| message->next = m_freeInlineMessages; |
| m_freeInlineMessages = message; |
| } else |
| delete message; |
| } |
| |
| PluginView* m_pluginView; |
| PluginMessage* m_back; |
| PluginMessage* m_front; |
| |
| static const int NumInlineMessages = 4; |
| PluginMessage m_inlineMessages[NumInlineMessages]; |
| PluginMessage* m_freeInlineMessages; |
| |
| Timer<PluginMessageThrottlerWin> m_messageThrottleTimer; |
| }; |
| |
| static String scriptStringIfJavaScriptURL(const KURL& url) |
| { |
| if (!url.string().startsWith("javascript:", false)) |
| return String(); |
| |
| // This returns an unescaped string |
| return KURL::decode_string(url.deprecatedString().mid(11)); |
| } |
| |
| PluginView* PluginView::s_currentPluginView = 0; |
| |
| const LPCWSTR kWebPluginViewdowClassName = 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; |
| |
| haveRegisteredWindowClass = true; |
| |
| ASSERT(Page::instanceHandle()); |
| |
| WNDCLASSEX wcex; |
| |
| wcex.cbSize = sizeof(WNDCLASSEX); |
| |
| wcex.style = CS_DBLCLKS; |
| wcex.lpfnWndProc = DefWindowProc; |
| 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 = kWebPluginViewdowClassName; |
| wcex.hIconSm = 0; |
| |
| return !!RegisterClassEx(&wcex); |
| } |
| |
| static LRESULT CALLBACK PluginViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) |
| { |
| PluginView* pluginView = reinterpret_cast<PluginView*>(GetProp(hWnd, kWebPluginViewProperty)); |
| |
| return pluginView->wndProc(hWnd, message, wParam, lParam); |
| } |
| |
| void PluginView::popPopupsStateTimerFired(Timer<PluginView>*) |
| { |
| popPopupsEnabledState(); |
| } |
| |
| |
| static bool isWindowsMessageUserGesture(UINT message) |
| { |
| switch (message) { |
| case WM_LBUTTONUP: |
| case WM_MBUTTONUP: |
| case WM_RBUTTONUP: |
| case WM_KEYUP: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| LRESULT |
| PluginView::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) |
| { |
| // <rdar://5711136> Sometimes Flash will call SetCapture before creating |
| // a full-screen window and will not release it, which causes the |
| // full-screen window to never receive mouse events. We set/release capture |
| // on mouse down/up before sending the event to the plug-in to prevent that. |
| switch (message) { |
| case WM_LBUTTONDOWN: |
| case WM_MBUTTONDOWN: |
| case WM_RBUTTONDOWN: |
| ::SetCapture(hWnd); |
| break; |
| case WM_LBUTTONUP: |
| case WM_MBUTTONUP: |
| case WM_RBUTTONUP: |
| ::ReleaseCapture(); |
| break; |
| } |
| |
| if (message == m_lastMessage && |
| m_quirks.contains(PluginQuirkDontCallWndProcForSameMessageRecursively) && |
| m_isCallingPluginWndProc) |
| return 1; |
| |
| if (message == WM_USER + 1 && |
| m_quirks.contains(PluginQuirkThrottleWMUserPlusOneMessages)) { |
| if (!m_messageThrottler) |
| m_messageThrottler.set(new PluginMessageThrottlerWin(this)); |
| |
| m_messageThrottler->appendMessage(hWnd, message, wParam, lParam); |
| return 0; |
| } |
| |
| m_lastMessage = message; |
| m_isCallingPluginWndProc = true; |
| |
| // If the plug-in doesn't explicitly support changing the pop-up state, we enable |
| // popups for all user gestures. |
| // Note that we need to pop the state in a timer, because the Flash plug-in |
| // pops up windows in response to a posted message. |
| if (m_plugin->pluginFuncs()->version < NPVERS_HAS_POPUPS_ENABLED_STATE && |
| isWindowsMessageUserGesture(message) && !m_popPopupsStateTimer.isActive()) { |
| |
| pushPopupsEnabledState(true); |
| |
| m_popPopupsStateTimer.startOneShot(0); |
| } |
| |
| // Call the plug-in's window proc. |
| LRESULT result = ::CallWindowProc(m_pluginWndProc, hWnd, message, wParam, lParam); |
| |
| m_isCallingPluginWndProc = false; |
| |
| return result; |
| } |
| |
| void PluginView::updateWindow() const |
| { |
| if (!parent()) |
| return; |
| |
| ASSERT(parent()->isFrameView()); |
| FrameView* frameView = static_cast<FrameView*>(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; |
| |
| setCallingPlugin(true); |
| |
| // To prevent flashes while scrolling, we disable drawing during the window |
| // update process by clipping the window to the zero rect. |
| |
| bool clipToZeroRect = !m_quirks.contains(PluginQuirkDontClipToZeroRectWhenScrolling); |
| |
| if (clipToZeroRect) { |
| rgn = ::CreateRectRgn(0, 0, 0, 0); |
| ::SetWindowRgn(m_window, rgn, FALSE); |
| } else { |
| rgn = ::CreateRectRgn(m_clipRect.x(), m_clipRect.y(), m_clipRect.right(), m_clipRect.bottom()); |
| ::SetWindowRgn(m_window, rgn, TRUE); |
| } |
| |
| if (m_windowRect != oldWindowRect) |
| ::MoveWindow(m_window, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), TRUE); |
| |
| if (clipToZeroRect) { |
| rgn = ::CreateRectRgn(m_clipRect.x(), m_clipRect.y(), m_clipRect.right(), m_clipRect.bottom()); |
| ::SetWindowRgn(m_window, rgn, TRUE); |
| } |
| |
| setCallingPlugin(false); |
| } |
| } |
| |
| IntRect PluginView::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 PluginView::setFrameGeometry(const IntRect& rect) |
| { |
| if (m_element->document()->printing()) |
| return; |
| |
| if (rect != frameGeometry()) |
| Widget::setFrameGeometry(rect); |
| |
| updateWindow(); |
| setNPWindowRect(rect); |
| } |
| |
| void PluginView::geometryChanged() const |
| { |
| updateWindow(); |
| } |
| |
| void PluginView::setFocus() |
| { |
| if (m_window) |
| SetFocus(m_window); |
| |
| Widget::setFocus(); |
| } |
| |
| void PluginView::show() |
| { |
| m_isVisible = true; |
| |
| if (m_attachedToWindow && m_window) |
| ShowWindow(m_window, SW_SHOWNA); |
| |
| Widget::show(); |
| } |
| |
| void PluginView::hide() |
| { |
| m_isVisible = false; |
| |
| if (m_attachedToWindow && m_window) |
| ShowWindow(m_window, SW_HIDE); |
| |
| Widget::hide(); |
| } |
| |
| void PluginView::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(); |
| } |
| |
| bool PluginView::dispatchNPEvent(NPEvent& npEvent) |
| { |
| if (!m_plugin->pluginFuncs()->event) |
| return true; |
| |
| bool shouldPop = false; |
| |
| if (m_plugin->pluginFuncs()->version < NPVERS_HAS_POPUPS_ENABLED_STATE && isWindowsMessageUserGesture(npEvent.event)) { |
| pushPopupsEnabledState(true); |
| shouldPop = true; |
| } |
| |
| KJS::JSLock::DropAllLocks dropAllLocks; |
| setCallingPlugin(true); |
| bool result = m_plugin->pluginFuncs()->event(m_instance, &npEvent); |
| setCallingPlugin(false); |
| |
| if (shouldPop) |
| popPopupsEnabledState(); |
| |
| return result; |
| } |
| |
| void PluginView::paint(GraphicsContext* context, const IntRect& rect) |
| { |
| if (!m_isStarted) { |
| // Draw the "missing plugin" image |
| paintMissingPluginIcon(context, rect); |
| return; |
| } |
| |
| if (m_isWindowed || context->paintingDisabled()) |
| return; |
| |
| ASSERT(parent()->isFrameView()); |
| IntRect rectInWindow = static_cast<FrameView*>(parent())->contentsToWindow(frameGeometry()); |
| HDC hdc = context->getWindowsContext(rectInWindow, m_isTransparent); |
| NPEvent npEvent; |
| |
| if (!context->inTransparencyLayer()) { |
| // The plugin expects that the passed in DC has window coordinates. |
| XFORM transform; |
| GetWorldTransform(hdc, &transform); |
| transform.eDx = 0; |
| transform.eDy = 0; |
| SetWorldTransform(hdc, &transform); |
| } |
| |
| m_npWindow.type = NPWindowTypeDrawable; |
| m_npWindow.window = hdc; |
| |
| IntPoint p = static_cast<FrameView*>(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<uint32>(&windowpos); |
| npEvent.wParam = 0; |
| |
| dispatchNPEvent(npEvent); |
| |
| setNPWindowRect(frameGeometry()); |
| |
| npEvent.event = WM_PAINT; |
| npEvent.wParam = reinterpret_cast<uint32>(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; |
| |
| dispatchNPEvent(npEvent); |
| |
| context->releaseWindowsContext(hdc, frameGeometry(), m_isTransparent); |
| } |
| |
| void PluginView::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 (!dispatchNPEvent(npEvent)) |
| event->setDefaultHandled(); |
| } |
| |
| extern HCURSOR lastSetCursor; |
| extern bool ignoreNextSetCursor; |
| |
| void PluginView::handleMouseEvent(MouseEvent* event) |
| { |
| NPEvent npEvent; |
| |
| IntPoint p = static_cast<FrameView*>(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 || |
| event->type() == mouseoutEvent || |
| event->type() == mouseoverEvent) { |
| 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) { |
| // Focus the plugin |
| if (Page* page = m_parentFrame->page()) |
| page->focusController()->setFocusedFrame(m_parentFrame); |
| m_parentFrame->document()->setFocusedNode(m_element); |
| switch (event->button()) { |
| case 0: |
| npEvent.event = WM_LBUTTONDOWN; |
| 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 (!dispatchNPEvent(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 PluginView::handleEvent(Event* event) |
| { |
| if (!m_plugin || m_isWindowed) |
| return; |
| |
| if (event->isMouseEvent()) |
| handleMouseEvent(static_cast<MouseEvent*>(event)); |
| else if (event->isKeyboardEvent()) |
| handleKeyboardEvent(static_cast<KeyboardEvent*>(event)); |
| } |
| |
| void PluginView::setParent(ScrollView* parent) |
| { |
| Widget::setParent(parent); |
| |
| if (parent) |
| init(); |
| else { |
| if (!m_window) |
| return; |
| |
| // If the plug-in window or one of its children have the focus, we need to |
| // clear it to prevent the web view window from being focused because that can |
| // trigger a layout while the plugin element is being detached. |
| HWND focusedWindow = ::GetFocus(); |
| if (m_window == focusedWindow || ::IsChild(m_window, focusedWindow)) |
| ::SetFocus(0); |
| } |
| |
| } |
| |
| void PluginView::attachToWindow() |
| { |
| if (m_attachedToWindow) |
| return; |
| |
| m_attachedToWindow = true; |
| if (m_isVisible && m_window) |
| ShowWindow(m_window, SW_SHOWNA); |
| } |
| |
| void PluginView::detachFromWindow() |
| { |
| if (!m_attachedToWindow) |
| return; |
| |
| if (m_isVisible && m_window) |
| ShowWindow(m_window, SW_HIDE); |
| m_attachedToWindow = false; |
| } |
| |
| void PluginView::setNPWindowRect(const IntRect& rect) |
| { |
| if (!m_isStarted) |
| return; |
| |
| IntPoint p = static_cast<FrameView*>(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; |
| setCallingPlugin(true); |
| m_plugin->pluginFuncs()->setwindow(m_instance, &m_npWindow); |
| setCallingPlugin(false); |
| |
| if (!m_isWindowed) |
| return; |
| |
| ASSERT(m_window); |
| |
| WNDPROC currentWndProc = (WNDPROC)GetWindowLongPtr(m_window, GWLP_WNDPROC); |
| if (currentWndProc != PluginViewWndProc) |
| m_pluginWndProc = (WNDPROC)SetWindowLongPtr(m_window, GWLP_WNDPROC, (LONG)PluginViewWndProc); |
| } |
| } |
| |
| bool PluginView::start() |
| { |
| if (m_isStarted) |
| return false; |
| |
| ASSERT(m_plugin); |
| ASSERT(m_plugin->pluginFuncs()->newp); |
| |
| NPError npErr; |
| PluginView::setCurrentPluginView(this); |
| { |
| KJS::JSLock::DropAllLocks dropAllLocks; |
| setCallingPlugin(true); |
| npErr = m_plugin->pluginFuncs()->newp((NPMIMEType)m_mimeType.data(), m_instance, m_mode, m_paramCount, m_paramNames, m_paramValues, NULL); |
| setCallingPlugin(false); |
| LOG_NPERROR(npErr); |
| } |
| PluginView::setCurrentPluginView(0); |
| |
| if (npErr != NPERR_NO_ERROR) |
| return false; |
| |
| m_isStarted = true; |
| |
| if (!m_url.isEmpty() && !m_loadManually) { |
| FrameLoadRequest frameLoadRequest; |
| frameLoadRequest.resourceRequest().setHTTPMethod("GET"); |
| frameLoadRequest.resourceRequest().setURL(m_url); |
| load(frameLoadRequest, false, 0); |
| } |
| |
| return true; |
| } |
| |
| void PluginView::stop() |
| { |
| if (!m_isStarted) |
| return; |
| |
| HashSet<RefPtr<PluginStream> > streams = m_streams; |
| HashSet<RefPtr<PluginStream> >::iterator end = streams.end(); |
| for (HashSet<RefPtr<PluginStream> >::iterator it = streams.begin(); it != end; ++it) { |
| (*it)->stop(); |
| disconnectStream((*it).get()); |
| } |
| |
| ASSERT(m_streams.isEmpty()); |
| |
| m_isStarted = false; |
| |
| // Unsubclass the window |
| if (m_isWindowed) { |
| WNDPROC currentWndProc = (WNDPROC)GetWindowLongPtr(m_window, GWLP_WNDPROC); |
| |
| if (currentWndProc == PluginViewWndProc) |
| SetWindowLongPtr(m_window, GWLP_WNDPROC, (LONG)m_pluginWndProc); |
| } |
| |
| KJS::JSLock::DropAllLocks; |
| |
| // Clear the window |
| m_npWindow.window = 0; |
| if (m_plugin->pluginFuncs()->setwindow && !m_quirks.contains(PluginQuirkDontSetNullWindowHandleOnDestroy)) { |
| setCallingPlugin(true); |
| m_plugin->pluginFuncs()->setwindow(m_instance, &m_npWindow); |
| setCallingPlugin(false); |
| } |
| |
| // Destroy the plugin |
| NPSavedData* savedData = 0; |
| setCallingPlugin(true); |
| NPError npErr = m_plugin->pluginFuncs()->destroy(m_instance, &savedData); |
| setCallingPlugin(false); |
| LOG_NPERROR(npErr); |
| |
| if (savedData) { |
| if (savedData->buf) |
| NPN_MemFree(savedData->buf); |
| NPN_MemFree(savedData); |
| } |
| |
| m_instance->pdata = 0; |
| } |
| |
| void PluginView::setCurrentPluginView(PluginView* pluginView) |
| { |
| s_currentPluginView = pluginView; |
| } |
| |
| PluginView* PluginView::currentPluginView() |
| { |
| return s_currentPluginView; |
| } |
| |
| static char* createUTF8String(const String& str) |
| { |
| CString cstr = str.utf8(); |
| char* result = reinterpret_cast<char*>(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->globalObject()->globalExec(); |
| UString ustring = result->toString(exec); |
| exec->clearException(); |
| |
| string = ustring; |
| return true; |
| } |
| |
| void PluginView::performRequest(PluginRequest* request) |
| { |
| // don't let a plugin start any loads if it is no longer part of a document that is being |
| // displayed unless the loads are in the same frame as the plugin. |
| const String& targetFrameName = request->frameLoadRequest().frameName(); |
| if (m_parentFrame->loader()->documentLoader() != m_parentFrame->loader()->activeDocumentLoader() && |
| (targetFrameName.isNull() || m_parentFrame->tree()->find(targetFrameName) != m_parentFrame)) |
| return; |
| |
| KURL requestURL = request->frameLoadRequest().resourceRequest().url(); |
| String jsString = scriptStringIfJavaScriptURL(requestURL); |
| |
| if (jsString.isNull()) { |
| // if this is not a targeted request, create a stream for it. otherwise, |
| // just pass it off to the loader |
| if (targetFrameName.isEmpty()) { |
| PluginStream* stream = new PluginStream(this, m_parentFrame, request->frameLoadRequest().resourceRequest(), request->sendNotification(), request->notifyData(), plugin()->pluginFuncs(), instance(), m_quirks); |
| m_streams.add(stream); |
| stream->start(); |
| } else { |
| m_parentFrame->loader()->load(request->frameLoadRequest().resourceRequest(), targetFrameName); |
| |
| // FIXME: <rdar://problem/4807469> This should be sent when the document has finished loading |
| if (request->sendNotification()) { |
| KJS::JSLock::DropAllLocks dropAllLocks; |
| setCallingPlugin(true); |
| m_plugin->pluginFuncs()->urlnotify(m_instance, requestURL.deprecatedString().utf8(), NPRES_DONE, request->notifyData()); |
| setCallingPlugin(false); |
| } |
| } |
| return; |
| } |
| |
| // Targeted JavaScript requests are only allowed on the frame that contains the JavaScript plugin |
| // and this has been made sure in ::load. |
| ASSERT(targetFrameName.isEmpty() || m_parentFrame->tree()->find(targetFrameName) == m_parentFrame); |
| |
| // Executing a script can cause the plugin view to be destroyed, so we keep a reference to the parent frame. |
| RefPtr<Frame> parentFrame = m_parentFrame; |
| JSValue* result = m_parentFrame->loader()->executeScript(jsString, request->shouldAllowPopups()); |
| |
| if (targetFrameName.isNull()) { |
| String resultString; |
| |
| CString cstr; |
| if (getString(parentFrame->scriptProxy(), result, resultString)) |
| cstr = resultString.utf8(); |
| |
| RefPtr<PluginStream> stream = new PluginStream(this, m_parentFrame, request->frameLoadRequest().resourceRequest(), request->sendNotification(), request->notifyData(), plugin()->pluginFuncs(), instance(), m_quirks); |
| m_streams.add(stream); |
| stream->sendJavaScriptStream(requestURL, cstr); |
| } |
| } |
| |
| void PluginView::requestTimerFired(Timer<PluginView>* timer) |
| { |
| ASSERT(timer == &m_requestTimer); |
| ASSERT(m_requests.size() > 0); |
| |
| PluginRequest* 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); |
| delete request; |
| } |
| |
| void PluginView::scheduleRequest(PluginRequest* request) |
| { |
| m_requests.append(request); |
| m_requestTimer.startOneShot(0); |
| } |
| |
| NPError PluginView::load(const FrameLoadRequest& frameLoadRequest, bool sendNotification, void* notifyData) |
| { |
| ASSERT(frameLoadRequest.resourceRequest().httpMethod() == "GET" || frameLoadRequest.resourceRequest().httpMethod() == "POST"); |
| |
| KURL url = frameLoadRequest.resourceRequest().url(); |
| |
| if (url.isEmpty()) |
| return NPERR_INVALID_URL; |
| |
| const String& targetFrameName = 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; |
| } |
| |
| if (!targetFrameName.isNull() && m_parentFrame->tree()->find(targetFrameName) != m_parentFrame) { |
| // For security reasons, only allow JS requests to be made on the frame that contains the plug-in. |
| return NPERR_INVALID_PARAM; |
| } |
| } else if (!FrameLoader::canLoad(url, m_parentFrame->document())) |
| return NPERR_GENERIC_ERROR; |
| |
| PluginRequest* request = new PluginRequest(frameLoadRequest, sendNotification, notifyData, arePopupsAllowed()); |
| scheduleRequest(request); |
| |
| 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 PluginView::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 PluginView::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<char>& buffer) |
| { |
| return buffer.size() > 0 && buffer[0] == '\n'; |
| } |
| |
| static inline int locationAfterFirstBlankLine(const Vector<char>& 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. <header>CRLFLF<body>), 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 = toASCIIUpper(name[i]); |
| else if (!capitalizeCharacter && name[i] >= 'A' && name[i] <= 'Z') |
| c = toASCIILower(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<char>& 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 PluginView::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<char> 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<FormData>(new FormData(postData, postDataLength))); |
| frameLoadRequest.setFrameName(target); |
| |
| return load(frameLoadRequest, sendNotification, notifyData); |
| } |
| |
| NPError PluginView::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 PluginView::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 PluginView::newStream(NPMIMEType type, const char* target, NPStream** stream) |
| { |
| notImplemented(); |
| // Unsupported |
| return NPERR_GENERIC_ERROR; |
| } |
| |
| int32 PluginView::write(NPStream* stream, int32 len, void* buffer) |
| { |
| notImplemented(); |
| // Unsupported |
| return -1; |
| } |
| |
| NPError PluginView::destroyStream(NPStream* stream, NPReason reason) |
| { |
| PluginStream* browserStream = static_cast<PluginStream*>(stream->ndata); |
| |
| if (!stream || PluginStream::ownerForStream(stream) != m_instance) |
| return NPERR_INVALID_INSTANCE_ERROR; |
| |
| browserStream->cancelAndDestroyStream(reason); |
| return NPERR_NO_ERROR; |
| } |
| |
| const char* PluginView::userAgent() |
| { |
| if (m_quirks.contains(PluginQuirkWantsMozillaUserAgent)) |
| return MozillaUserAgent; |
| |
| if (m_userAgent.isNull()) |
| m_userAgent = m_parentFrame->loader()->userAgent(m_url).utf8(); |
| return m_userAgent.data(); |
| } |
| |
| void PluginView::status(const char* message) |
| { |
| String s = DeprecatedString::fromLatin1(message); |
| |
| if (Page* page = m_parentFrame->page()) |
| page->chrome()->setStatusbarText(m_parentFrame, s); |
| } |
| |
| NPError PluginView::getValue(NPNVariable variable, void* value) |
| { |
| switch (variable) { |
| case NPNVWindowNPObject: { |
| NPObject* windowScriptObject = m_parentFrame->windowScriptNPObject(); |
| |
| // Return value is expected to be retained, as described here: <http://www.mozilla.org/projects/plugin/npruntime.html> |
| 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<HTMLPlugInElement*>(m_element)->getNPObject(); |
| |
| // Return value is expected to be retained, as described here: <http://www.mozilla.org/projects/plugin/npruntime.html> |
| if (pluginScriptObject) |
| _NPN_RetainObject(pluginScriptObject); |
| |
| void** v = (void**)value; |
| *v = pluginScriptObject; |
| |
| return NPERR_NO_ERROR; |
| } |
| |
| case NPNVnetscapeWindow: { |
| HWND* w = reinterpret_cast<HWND*>(value); |
| |
| *w = containingWindow(); |
| |
| return NPERR_NO_ERROR; |
| } |
| default: |
| return NPERR_GENERIC_ERROR; |
| } |
| } |
| |
| NPError PluginView::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 PluginView::invalidateTimerFired(Timer<PluginView>* timer) |
| { |
| ASSERT(timer == &m_invalidateTimer); |
| |
| for (unsigned i = 0; i < m_invalidRects.size(); i++) |
| Widget::invalidateRect(m_invalidRects[i]); |
| m_invalidRects.clear(); |
| } |
| |
| |
| void PluginView::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.contains(PluginQuirkThrottleInvalidate)) { |
| m_invalidRects.append(r); |
| if (!m_invalidateTimer.isActive()) |
| m_invalidateTimer.startOneShot(0.001); |
| } else |
| Widget::invalidateRect(r); |
| } |
| } |
| |
| void PluginView::invalidateRegion(NPRegion region) |
| { |
| if (m_isWindowed) |
| return; |
| |
| RECT r; |
| |
| if (GetRgnBox(region, &r) == 0) { |
| invalidate(); |
| return; |
| } |
| |
| Widget::invalidateRect(r); |
| } |
| |
| void PluginView::forceRedraw() |
| { |
| if (m_isWindowed) |
| ::UpdateWindow(m_window); |
| else |
| ::UpdateWindow(containingWindow()); |
| } |
| |
| void PluginView::pushPopupsEnabledState(bool state) |
| { |
| m_popupStateStack.append(state); |
| } |
| |
| void PluginView::popPopupsEnabledState() |
| { |
| m_popupStateStack.removeLast(); |
| } |
| |
| bool PluginView::arePopupsAllowed() const |
| { |
| if (!m_popupStateStack.isEmpty()) |
| return m_popupStateStack.last(); |
| |
| return false; |
| } |
| |
| KJS::Bindings::Instance* PluginView::bindingInstance() |
| { |
| NPObject* object = 0; |
| |
| if (!m_plugin || !m_plugin->pluginFuncs()->getvalue) |
| return 0; |
| |
| NPError npErr; |
| { |
| KJS::JSLock::DropAllLocks dropAllLocks; |
| setCallingPlugin(true); |
| npErr = m_plugin->pluginFuncs()->getvalue(m_instance, NPPVpluginScriptableNPObject, &object); |
| setCallingPlugin(false); |
| } |
| |
| if (npErr != NPERR_NO_ERROR || !object) |
| return 0; |
| |
| RefPtr<KJS::Bindings::RootObject> root = m_parentFrame->createRootObject(this, m_parentFrame->scriptProxy()->globalObject()); |
| KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance(KJS::Bindings::Instance::CLanguage, object, root.release()); |
| |
| _NPN_ReleaseObject(object); |
| |
| return instance; |
| } |
| |
| PluginView::~PluginView() |
| { |
| 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_quirks.contains(PluginQuirkDontUnloadPlugin)) |
| m_plugin->unload(); |
| } |
| |
| void PluginView::disconnectStream(PluginStream* stream) |
| { |
| ASSERT(m_streams.contains(stream)); |
| |
| m_streams.remove(stream); |
| } |
| |
| void PluginView::determineQuirks(const String& mimeType) |
| { |
| static const unsigned lastKnownUnloadableRealPlayerVersionLS = 0x000B0B24; |
| static const unsigned lastKnownUnloadableRealPlayerVersionMS = 0x00060000; |
| |
| if (mimeType == "application/x-shockwave-flash") { |
| // The flash plugin only requests windowless plugins if we return a mozilla user agent |
| m_quirks.add(PluginQuirkWantsMozillaUserAgent); |
| m_quirks.add(PluginQuirkThrottleInvalidate); |
| m_quirks.add(PluginQuirkThrottleWMUserPlusOneMessages); |
| m_quirks.add(PluginQuirkFlashURLNotifyBug); |
| } |
| |
| if (m_plugin->name().contains("Microsoft") && m_plugin->name().contains("Windows Media")) { |
| // 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 |
| m_quirks.add(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.add(PluginQuirkRemoveWindowlessVideoParam); |
| |
| // WMP has a modal message loop that it enters whenever we call it or |
| // ask it to paint. This modal loop can deliver messages to other |
| // windows in WebKit at times when they are not expecting them (for |
| // example, delivering a WM_PAINT message during a layout), and these |
| // can cause crashes. |
| m_quirks.add(PluginQuirkHasModalMessageLoop); |
| } |
| |
| // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window handle |
| if (m_plugin->name() == "VLC Multimedia Plugin") |
| m_quirks.add(PluginQuirkDontSetNullWindowHandleOnDestroy); |
| |
| // 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.add(PluginQuirkDeferFirstSetWindowCall); |
| |
| // FIXME: This is a workaround for a problem in our NPRuntime bindings; if a plug-in creates an |
| // NPObject and passes it to a function it's not possible to see what root object that NPObject belongs to. |
| // Thus, we don't know that the object should be invalidated when the plug-in instance goes away. |
| // See <rdar://problem/5487742>. |
| if (mimeType == "application/x-silverlight") |
| m_quirks.add(PluginQuirkDontUnloadPlugin); |
| |
| if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) { |
| // Because a single process cannot create multiple VMs, and we cannot reliably unload a |
| // Java VM, we cannot unload the Java plugin, or we'll lose reference to our only VM |
| m_quirks.add(PluginQuirkDontUnloadPlugin); |
| |
| // Setting the window region to an empty region causes bad scrolling repaint problems |
| // with the Java plug-in. |
| m_quirks.add(PluginQuirkDontClipToZeroRectWhenScrolling); |
| } |
| |
| if (mimeType == "audio/x-pn-realaudio-plugin") { |
| // Prevent the Real plugin from calling the Window Proc recursively, causing the stack to overflow. |
| m_quirks.add(PluginQuirkDontCallWndProcForSameMessageRecursively); |
| |
| // Unloading RealPlayer versions newer than 10.5 can cause a hang; see rdar://5669317. |
| // FIXME: Resume unloading when this bug in the RealPlayer Plug-In is fixed (rdar://5713147) |
| if (m_plugin->compareFileVersion(lastKnownUnloadableRealPlayerVersionMS, lastKnownUnloadableRealPlayerVersionLS) > 0) |
| m_quirks.add(PluginQuirkDontUnloadPlugin); |
| } |
| } |
| |
| void PluginView::setParameters(const Vector<String>& paramNames, const Vector<String>& paramValues) |
| { |
| ASSERT(paramNames.size() == paramValues.size()); |
| |
| unsigned size = paramNames.size(); |
| unsigned paramCount = 0; |
| |
| m_paramNames = reinterpret_cast<char**>(fastMalloc(sizeof(char*) * size)); |
| m_paramValues = reinterpret_cast<char**>(fastMalloc(sizeof(char*) * size)); |
| |
| for (unsigned i = 0; i < size; i++) { |
| if (m_quirks.contains(PluginQuirkRemoveWindowlessVideoParam) && equalIgnoringCase(paramNames[i], "windowlessvideo")) |
| continue; |
| |
| m_paramNames[paramCount] = createUTF8String(paramNames[i]); |
| m_paramValues[paramCount] = createUTF8String(paramValues[i]); |
| |
| paramCount++; |
| } |
| |
| m_paramCount = paramCount; |
| } |
| |
| PluginView::PluginView(Frame* parentFrame, const IntSize& size, PluginPackage* plugin, Element* element, const KURL& url, const Vector<String>& paramNames, const Vector<String>& paramValues, const String& mimeType, bool loadManually) |
| : 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, &PluginView::requestTimerFired) |
| , m_invalidateTimer(this, &PluginView::invalidateTimerFired) |
| , m_popPopupsStateTimer(this, &PluginView::popPopupsStateTimerFired) |
| , m_paramNames(0) |
| , m_paramValues(0) |
| , m_window(0) |
| , m_pluginWndProc(0) |
| , m_isWindowed(true) |
| , m_isTransparent(false) |
| , m_isVisible(false) |
| , m_attachedToWindow(false) |
| , m_haveInitialized(false) |
| , m_lastMessage(0) |
| , m_isCallingPluginWndProc(false) |
| , m_loadManually(loadManually) |
| , m_manualStream(0) |
| { |
| 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 = m_loadManually ? NP_FULL : NP_EMBED; |
| |
| resize(size); |
| } |
| |
| void PluginView::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, kWebPluginViewdowClassName, 0, flags, |
| 0, 0, 0, 0, m_parentFrame->view()->containingWindow(), 0, Page::instanceHandle(), 0); |
| |
| // Calling SetWindowLongPtrA here makes the window proc ASCII, which is required by at least |
| // the Shockwave Director plug-in. |
| ::SetWindowLongPtrA(m_window, GWL_WNDPROC, (LONG)DefWindowProcA); |
| |
| 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.contains(PluginQuirkDeferFirstSetWindowCall)) |
| setNPWindowRect(frameGeometry()); |
| |
| m_status = PluginStatusLoadedSuccessfully; |
| } |
| |
| void PluginView::didReceiveResponse(const ResourceResponse& response) |
| { |
| ASSERT(m_loadManually); |
| ASSERT(!m_manualStream); |
| |
| m_manualStream = new PluginStream(this, m_parentFrame, m_parentFrame->loader()->activeDocumentLoader()->request(), false, 0, plugin()->pluginFuncs(), instance(), m_quirks); |
| m_manualStream->setLoadManually(true); |
| |
| m_manualStream->didReceiveResponse(0, response); |
| } |
| |
| void PluginView::didReceiveData(const char* data, int length) |
| { |
| ASSERT(m_loadManually); |
| ASSERT(m_manualStream); |
| |
| m_manualStream->didReceiveData(0, data, length); |
| } |
| |
| void PluginView::didFinishLoading() |
| { |
| ASSERT(m_loadManually); |
| ASSERT(m_manualStream); |
| |
| m_manualStream->didFinishLoading(0); |
| } |
| |
| void PluginView::didFail(const ResourceError& error) |
| { |
| ASSERT(m_loadManually); |
| ASSERT(m_manualStream); |
| |
| m_manualStream->didFail(0, error); |
| } |
| |
| void PluginView::setCallingPlugin(bool b) const |
| { |
| if (!m_quirks.contains(PluginQuirkHasModalMessageLoop)) |
| return; |
| |
| if (b) |
| ++s_callingPlugin; |
| else |
| --s_callingPlugin; |
| |
| ASSERT(s_callingPlugin >= 0); |
| } |
| |
| bool PluginView::isCallingPlugin() |
| { |
| return s_callingPlugin > 0; |
| } |
| |
| } // namespace WebCore |