/* * Copyright (C) 2011, 2012 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER 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 "WebViewImpl.h" #include "AXObjectCache.h" #include "ActivePlatformGestureAnimation.h" #include "AutofillPopupMenuClient.h" #include "BackForwardListChromium.h" #include "BatteryClientImpl.h" #include "CSSValueKeywords.h" #include "Chrome.h" #include "Color.h" #include "ColorSpace.h" #include "CompositionUnderlineVectorBuilder.h" #include "ContextFeaturesClientImpl.h" #include "ContextMenu.h" #include "ContextMenuController.h" #include "ContextMenuItem.h" #include "Cursor.h" #include "DOMUtilitiesPrivate.h" #include "DeviceOrientationClientProxy.h" #include "Document.h" #include "DocumentLoader.h" #include "DragController.h" #include "DragData.h" #include "DragScrollTimer.h" #include "DragSession.h" #include "Editor.h" #include "EventHandler.h" #include "Extensions3D.h" #include "FocusController.h" #include "FontDescription.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "FrameTree.h" #include "FrameView.h" #include "GeolocationClientProxy.h" #include "GeolocationController.h" #include "GraphicsContext.h" #include "GraphicsContext3D.h" #include "GraphicsContext3DPrivate.h" #include "HTMLInputElement.h" #include "HTMLMediaElement.h" #include "HTMLNames.h" #include "HTMLTextAreaElement.h" #include "HitTestResult.h" #include "Image.h" #include "ImageBuffer.h" #include "InspectorController.h" #include "InspectorInstrumentation.h" #include "KeyboardCodes.h" #include "KeyboardEvent.h" #include "LayerChromium.h" #include "LayerPainterChromium.h" #include "MIMETypeRegistry.h" #include "NodeRenderStyle.h" #include "NonCompositedContentHost.h" #include "Page.h" #include "PageGroup.h" #include "PageGroupLoadDeferrer.h" #include "PagePopupClient.h" #include "PageWidgetDelegate.h" #include "Pasteboard.h" #include "PlatformContextSkia.h" #include "PlatformKeyboardEvent.h" #include "PlatformMouseEvent.h" #include "PlatformThemeChromiumLinux.h" #include "PlatformWheelEvent.h" #include "PointerLock.h" #include "PointerLockController.h" #include "PopupContainer.h" #include "PopupMenuClient.h" #include "PrerendererClientImpl.h" #include "ProgressTracker.h" #include "RenderLayerCompositor.h" #include "RenderView.h" #include "RenderWidget.h" #include "ResourceHandle.h" #include "SchemeRegistry.h" #include "SecurityOrigin.h" #include "SecurityPolicy.h" #include "Settings.h" #include "SharedGraphicsContext3D.h" #include "SpeechInputClientImpl.h" #include "SpeechRecognitionClientProxy.h" #include "StyleResolver.h" #include "Text.h" #include "TextFieldDecoratorImpl.h" #include "TextIterator.h" #include "Timer.h" #include "TouchpadFlingPlatformGestureCurve.h" #include "TraceEvent.h" #include "UserGestureIndicator.h" #include "WebAccessibilityObject.h" #include "WebActiveWheelFlingParameters.h" #include "WebAutofillClient.h" #include "WebCompositorImpl.h" #include "WebDevToolsAgentImpl.h" #include "WebDevToolsAgentPrivate.h" #include "WebFrameImpl.h" #include "WebHelperPluginImpl.h" #include "WebInputElement.h" #include "WebInputEvent.h" #include "WebInputEventConversion.h" #include "WebKit.h" #include "WebMediaPlayerAction.h" #include "WebNode.h" #include "WebPagePopupImpl.h" #include "WebPlugin.h" #include "WebPluginAction.h" #include "WebPluginContainerImpl.h" #include "WebPopupMenuImpl.h" #include "WebRange.h" #include "WebRuntimeFeatures.h" #include "WebSettingsImpl.h" #include "WebTextInputInfo.h" #include "WebViewClient.h" #include "WheelEvent.h" #include "cc/CCProxy.h" #include "cc/CCSettings.h" #include "painting/GraphicsContextBuilder.h" #include "platform/WebKitPlatformSupport.h" #include "platform/WebString.h" #include "platform/WebVector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE(GESTURE_EVENTS) #include "PlatformGestureEvent.h" #endif #if OS(WINDOWS) #include "RenderThemeChromiumWin.h" #else #if OS(UNIX) && !OS(DARWIN) #include "RenderThemeChromiumLinux.h" #endif #include "RenderTheme.h" #endif // Get rid of WTF's pow define so we can use std::pow. #undef pow #include // for std::pow using namespace WebCore; using namespace std; // The following constants control parameters for automated scaling of webpages // (such as due to a double tap gesture or find in page etc.). These are // experimentally determined. static const int touchPointPadding = 32; static const float minScaleDifference = 0.01f; static const float doubleTapZoomContentDefaultMargin = 5; static const float doubleTapZoomContentMinimumMargin = 2; namespace WebKit { // Change the text zoom level by kTextSizeMultiplierRatio each time the user // zooms text in or out (ie., change by 20%). The min and max values limit // text zoom to half and 3x the original text size. These three values match // those in Apple's port in WebKit/WebKit/WebView/WebView.mm const double WebView::textSizeMultiplierRatio = 1.2; const double WebView::minTextSizeMultiplier = 0.5; const double WebView::maxTextSizeMultiplier = 3.0; const float WebView::minPageScaleFactor = 0.25; const float WebView::maxPageScaleFactor = 4.0; // The group name identifies a namespace of pages. Page group is used on PLATFORM(MAC) // for some programs that use HTML views to display things that don't seem like // web pages to the user (so shouldn't have visited link coloring). We only use // one page group. const char* pageGroupName = "default"; // Used to defer all page activity in cases where the embedder wishes to run // a nested event loop. Using a stack enables nesting of message loop invocations. static Vector& pageGroupLoadDeferrerStack() { DEFINE_STATIC_LOCAL(Vector, deferrerStack, ()); return deferrerStack; } // Ensure that the WebDragOperation enum values stay in sync with the original // DragOperation constants. #define COMPILE_ASSERT_MATCHING_ENUM(coreName) \ COMPILE_ASSERT(int(coreName) == int(Web##coreName), dummy##coreName) COMPILE_ASSERT_MATCHING_ENUM(DragOperationNone); COMPILE_ASSERT_MATCHING_ENUM(DragOperationCopy); COMPILE_ASSERT_MATCHING_ENUM(DragOperationLink); COMPILE_ASSERT_MATCHING_ENUM(DragOperationGeneric); COMPILE_ASSERT_MATCHING_ENUM(DragOperationPrivate); COMPILE_ASSERT_MATCHING_ENUM(DragOperationMove); COMPILE_ASSERT_MATCHING_ENUM(DragOperationDelete); COMPILE_ASSERT_MATCHING_ENUM(DragOperationEvery); static const PopupContainerSettings autofillPopupSettings = { false, // setTextOnIndexChange false, // acceptOnAbandon true, // loopSelectionNavigation false // restrictWidthOfListBox (For security reasons show the entire entry // so the user doesn't enter information he did not intend to.) }; static bool shouldUseExternalPopupMenus = false; static int webInputEventKeyStateToPlatformEventKeyState(int webInputEventKeyState) { int platformEventKeyState = 0; if (webInputEventKeyState & WebInputEvent::ShiftKey) platformEventKeyState = platformEventKeyState | WebCore::PlatformEvent::ShiftKey; if (webInputEventKeyState & WebInputEvent::ControlKey) platformEventKeyState = platformEventKeyState | WebCore::PlatformEvent::CtrlKey; if (webInputEventKeyState & WebInputEvent::AltKey) platformEventKeyState = platformEventKeyState | WebCore::PlatformEvent::AltKey; if (webInputEventKeyState & WebInputEvent::MetaKey) platformEventKeyState = platformEventKeyState | WebCore::PlatformEvent::MetaKey; return platformEventKeyState; } // WebView ---------------------------------------------------------------- WebView* WebView::create(WebViewClient* client) { // Keep runtime flag for device motion turned off until it's implemented. WebRuntimeFeatures::enableDeviceMotion(false); // Pass the WebViewImpl's self-reference to the caller. return adoptRef(new WebViewImpl(client)).leakRef(); } void WebView::setUseExternalPopupMenus(bool useExternalPopupMenus) { shouldUseExternalPopupMenus = useExternalPopupMenus; } void WebView::updateVisitedLinkState(unsigned long long linkHash) { Page::visitedStateChanged(PageGroup::pageGroup(pageGroupName), linkHash); } void WebView::resetVisitedLinkState() { Page::allVisitedStateChanged(PageGroup::pageGroup(pageGroupName)); } void WebView::willEnterModalLoop() { PageGroup* pageGroup = PageGroup::pageGroup(pageGroupName); ASSERT(pageGroup); if (pageGroup->pages().isEmpty()) pageGroupLoadDeferrerStack().append(static_cast(0)); else { // Pick any page in the page group since we are deferring all pages. pageGroupLoadDeferrerStack().append(new PageGroupLoadDeferrer(*pageGroup->pages().begin(), true)); } } void WebView::didExitModalLoop() { ASSERT(pageGroupLoadDeferrerStack().size()); delete pageGroupLoadDeferrerStack().last(); pageGroupLoadDeferrerStack().removeLast(); } void WebViewImpl::initializeMainFrame(WebFrameClient* frameClient) { // NOTE: The WebFrameImpl takes a reference to itself within InitMainFrame // and releases that reference once the corresponding Frame is destroyed. RefPtr frame = WebFrameImpl::create(frameClient); frame->initializeAsMainFrame(page()); // Restrict the access to the local file system // (see WebView.mm WebView::_commonInitializationWithFrameName). SecurityPolicy::setLocalLoadPolicy(SecurityPolicy::AllowLocalLoadsForLocalOnly); } void WebViewImpl::initializeHelperPluginFrame(WebFrameClient* client) { RefPtr frame = WebFrameImpl::create(client); } void WebViewImpl::setAutofillClient(WebAutofillClient* autofillClient) { m_autofillClient = autofillClient; } void WebViewImpl::setDevToolsAgentClient(WebDevToolsAgentClient* devToolsClient) { if (devToolsClient) m_devToolsAgent = adoptPtr(new WebDevToolsAgentImpl(this, devToolsClient)); else m_devToolsAgent.clear(); } void WebViewImpl::setPermissionClient(WebPermissionClient* permissionClient) { m_permissionClient = permissionClient; m_featureSwitchClient->setPermissionClient(permissionClient); } void WebViewImpl::setPrerendererClient(WebPrerendererClient* prerendererClient) { providePrerendererClientTo(m_page.get(), new PrerendererClientImpl(prerendererClient)); } void WebViewImpl::setSpellCheckClient(WebSpellCheckClient* spellCheckClient) { m_spellCheckClient = spellCheckClient; } void WebViewImpl::addTextFieldDecoratorClient(WebTextFieldDecoratorClient* client) { ASSERT(client); // We limit the number of decorators because it affects performance of text // field creation. If you'd like to add more decorators, consider moving // your decorator or existing decorators to WebCore. const unsigned maximumNumberOfDecorators = 8; if (m_textFieldDecorators.size() >= maximumNumberOfDecorators) CRASH(); m_textFieldDecorators.append(TextFieldDecoratorImpl::create(client)); } WebViewImpl::WebViewImpl(WebViewClient* client) : m_client(client) , m_autofillClient(0) , m_permissionClient(0) , m_spellCheckClient(0) , m_chromeClientImpl(this) , m_contextMenuClientImpl(this) , m_dragClientImpl(this) , m_editorClientImpl(this) , m_inspectorClientImpl(this) , m_shouldAutoResize(false) , m_observedNewNavigation(false) #ifndef NDEBUG , m_newNavigationLoader(0) #endif , m_zoomLevel(0) , m_minimumZoomLevel(zoomFactorToZoomLevel(minTextSizeMultiplier)) , m_maximumZoomLevel(zoomFactorToZoomLevel(maxTextSizeMultiplier)) , m_pageDefinedMinimumPageScaleFactor(-1) , m_pageDefinedMaximumPageScaleFactor(-1) , m_minimumPageScaleFactor(minPageScaleFactor) , m_maximumPageScaleFactor(maxPageScaleFactor) , m_ignoreViewportTagMaximumScale(false) , m_pageScaleFactorIsSet(false) , m_contextMenuAllowed(false) , m_doingDragAndDrop(false) , m_ignoreInputEvents(false) , m_suppressNextKeypressEvent(false) , m_initialNavigationPolicy(WebNavigationPolicyIgnore) , m_imeAcceptEvents(true) , m_operationsAllowed(WebDragOperationNone) , m_dragOperation(WebDragOperationNone) , m_featureSwitchClient(adoptPtr(new ContextFeaturesClientImpl())) , m_autofillPopupShowing(false) , m_autofillPopup(0) , m_isTransparent(false) , m_tabsToLinks(false) , m_dragScrollTimer(adoptPtr(new DragScrollTimer)) , m_isCancelingFullScreen(false) #if USE(ACCELERATED_COMPOSITING) , m_rootGraphicsLayer(0) , m_isAcceleratedCompositingActive(false) , m_compositorCreationFailed(false) , m_recreatingGraphicsContext(false) , m_compositorSurfaceReady(false) , m_deviceScaleInCompositor(1) #endif #if ENABLE(INPUT_SPEECH) , m_speechInputClient(SpeechInputClientImpl::create(client)) #endif #if ENABLE(SCRIPTED_SPEECH) , m_speechRecognitionClient(SpeechRecognitionClientProxy::create(client ? client->speechRecognizer() : 0)) #endif , m_deviceOrientationClientProxy(adoptPtr(new DeviceOrientationClientProxy(client ? client->deviceOrientationClient() : 0))) , m_geolocationClientProxy(adoptPtr(new GeolocationClientProxy(client ? client->geolocationClient() : 0))) #if ENABLE(BATTERY_STATUS) , m_batteryClient(adoptPtr(new BatteryClientImpl(client ? client->batteryStatusClient() : 0))) #endif , m_emulatedTextZoomFactor(1) #if ENABLE(MEDIA_STREAM) , m_userMediaClientImpl(this) #endif , m_flingModifier(0) { // WebKit/win/WebView.cpp does the same thing, except they call the // KJS specific wrapper around this method. We need to have threading // initialized because CollatorICU requires it. WTF::initializeThreading(); WTF::initializeMainThread(); Page::PageClients pageClients; pageClients.chromeClient = &m_chromeClientImpl; pageClients.contextMenuClient = &m_contextMenuClientImpl; pageClients.editorClient = &m_editorClientImpl; pageClients.dragClient = &m_dragClientImpl; pageClients.inspectorClient = &m_inspectorClientImpl; pageClients.backForwardClient = BackForwardListChromium::create(this); m_page = adoptPtr(new Page(pageClients)); #if ENABLE(MEDIA_STREAM) provideUserMediaTo(m_page.get(), &m_userMediaClientImpl); #endif #if ENABLE(INPUT_SPEECH) provideSpeechInputTo(m_page.get(), m_speechInputClient.get()); #endif #if ENABLE(SCRIPTED_SPEECH) provideSpeechRecognitionTo(m_page.get(), m_speechRecognitionClient.get()); #endif #if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS) provideNotification(m_page.get(), notificationPresenterImpl()); #endif provideContextFeaturesTo(m_page.get(), m_featureSwitchClient.get()); provideDeviceOrientationTo(m_page.get(), m_deviceOrientationClientProxy.get()); provideGeolocationTo(m_page.get(), m_geolocationClientProxy.get()); m_geolocationClientProxy->setController(GeolocationController::from(m_page.get())); #if ENABLE(BATTERY_STATUS) provideBatteryTo(m_page.get(), m_batteryClient.get()); #endif m_page->setGroupName(pageGroupName); #if ENABLE(PAGE_VISIBILITY_API) if (m_client) setVisibilityState(m_client->visibilityState(), true); #endif m_inspectorSettingsMap = adoptPtr(new SettingsMap); } WebViewImpl::~WebViewImpl() { ASSERT(!m_page); } RenderTheme* WebViewImpl::theme() const { return m_page ? m_page->theme() : RenderTheme::defaultTheme().get(); } WebFrameImpl* WebViewImpl::mainFrameImpl() { return m_page ? WebFrameImpl::fromFrame(m_page->mainFrame()) : 0; } bool WebViewImpl::tabKeyCyclesThroughElements() const { ASSERT(m_page); return m_page->tabKeyCyclesThroughElements(); } void WebViewImpl::setTabKeyCyclesThroughElements(bool value) { if (m_page) m_page->setTabKeyCyclesThroughElements(value); } void WebViewImpl::handleMouseLeave(Frame& mainFrame, const WebMouseEvent& event) { m_client->setMouseOverURL(WebURL()); PageWidgetEventHandler::handleMouseLeave(mainFrame, event); } void WebViewImpl::handleMouseDown(Frame& mainFrame, const WebMouseEvent& event) { // If there is a select popup open, close it as the user is clicking on // the page (outside of the popup). We also save it so we can prevent a // click on the select element from immediately reopening the popup. RefPtr selectPopup; if (event.button == WebMouseEvent::ButtonLeft) { selectPopup = m_selectPopup; hideSelectPopup(); ASSERT(!m_selectPopup); } m_lastMouseDownPoint = WebPoint(event.x, event.y); if (event.button == WebMouseEvent::ButtonLeft) { IntPoint point(event.x, event.y); point = m_page->mainFrame()->view()->windowToContents(point); HitTestResult result(m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, false)); Node* hitNode = result.innerNonSharedNode(); // Take capture on a mouse down on a plugin so we can send it mouse events. if (hitNode && hitNode->renderer() && hitNode->renderer()->isEmbeddedObject()) m_mouseCaptureNode = hitNode; } PageWidgetEventHandler::handleMouseDown(mainFrame, event); if (m_selectPopup && m_selectPopup == selectPopup) { // That click triggered a select popup which is the same as the one that // was showing before the click. It means the user clicked the select // while the popup was showing, and as a result we first closed then // immediately reopened the select popup. It needs to be closed. hideSelectPopup(); } // Dispatch the contextmenu event regardless of if the click was swallowed. // On Windows, we handle it on mouse up, not down. #if OS(DARWIN) if (event.button == WebMouseEvent::ButtonRight || (event.button == WebMouseEvent::ButtonLeft && event.modifiers & WebMouseEvent::ControlKey)) mouseContextMenu(event); #elif OS(UNIX) || OS(ANDROID) if (event.button == WebMouseEvent::ButtonRight) mouseContextMenu(event); #endif } void WebViewImpl::mouseContextMenu(const WebMouseEvent& event) { if (!mainFrameImpl() || !mainFrameImpl()->frameView()) return; m_page->contextMenuController()->clearContextMenu(); PlatformMouseEventBuilder pme(mainFrameImpl()->frameView(), event); // Find the right target frame. See issue 1186900. HitTestResult result = hitTestResultForWindowPos(pme.position()); Frame* targetFrame; if (result.innerNonSharedNode()) targetFrame = result.innerNonSharedNode()->document()->frame(); else targetFrame = m_page->focusController()->focusedOrMainFrame(); #if OS(WINDOWS) targetFrame->view()->setCursor(pointerCursor()); #endif m_contextMenuAllowed = true; targetFrame->eventHandler()->sendContextMenuEvent(pme); m_contextMenuAllowed = false; // Actually showing the context menu is handled by the ContextMenuClient // implementation... } void WebViewImpl::handleMouseUp(Frame& mainFrame, const WebMouseEvent& event) { #if OS(UNIX) && !OS(DARWIN) // If the event was a middle click, attempt to copy text into the focused // frame. We execute this before we let the page have a go at the event // because the page may change what is focused during in its event handler. // // This code is in the mouse up handler. There is some debate about putting // this here, as opposed to the mouse down handler. // xterm: pastes on up. // GTK: pastes on down. // Firefox: pastes on up. // Midori: couldn't paste at all with 0.1.2 // // There is something of a webcompat angle to this well, as highlighted by // crbug.com/14608. Pages can clear text boxes 'onclick' and, if we paste on // down then the text is pasted just before the onclick handler runs and // clears the text box. So it's important this happens after the // handleMouseReleaseEvent() earlier in this function if (event.button == WebMouseEvent::ButtonMiddle) { Frame* focused = focusedWebCoreFrame(); FrameView* view = m_page->mainFrame()->view(); IntPoint clickPoint(m_lastMouseDownPoint.x, m_lastMouseDownPoint.y); IntPoint contentPoint = view->windowToContents(clickPoint); HitTestResult hitTestResult = focused->eventHandler()->hitTestResultAtPoint(contentPoint, false, false, ShouldHitTestScrollbars); // We don't want to send a paste when middle clicking a scroll bar or a // link (which will navigate later in the code). The main scrollbars // have to be handled separately. if (!hitTestResult.scrollbar() && !hitTestResult.isLiveLink() && focused && !view->scrollbarAtPoint(clickPoint)) { Editor* editor = focused->editor(); Pasteboard* pasteboard = Pasteboard::generalPasteboard(); bool oldSelectionMode = pasteboard->isSelectionMode(); pasteboard->setSelectionMode(true); editor->command(AtomicString("Paste")).execute(); pasteboard->setSelectionMode(oldSelectionMode); } } #endif PageWidgetEventHandler::handleMouseUp(mainFrame, event); #if OS(WINDOWS) // Dispatch the contextmenu event regardless of if the click was swallowed. // On Mac/Linux, we handle it on mouse down, not up. if (event.button == WebMouseEvent::ButtonRight) mouseContextMenu(event); #endif } void WebViewImpl::scrollBy(const WebCore::IntPoint& delta) { WebMouseWheelEvent syntheticWheel; const float tickDivisor = WebCore::WheelEvent::tickMultiplier; syntheticWheel.deltaX = delta.x(); syntheticWheel.deltaY = delta.y(); syntheticWheel.wheelTicksX = delta.x() / tickDivisor; syntheticWheel.wheelTicksY = delta.y() / tickDivisor; syntheticWheel.hasPreciseScrollingDeltas = true; syntheticWheel.x = m_lastWheelPosition.x; syntheticWheel.y = m_lastWheelPosition.y; syntheticWheel.globalX = m_lastWheelGlobalPosition.x; syntheticWheel.globalY = m_lastWheelGlobalPosition.y; syntheticWheel.modifiers = m_flingModifier; if (m_page && m_page->mainFrame() && m_page->mainFrame()->view()) handleMouseWheel(*m_page->mainFrame(), syntheticWheel); } #if ENABLE(GESTURE_EVENTS) bool WebViewImpl::handleGestureEvent(const WebGestureEvent& event) { switch (event.type) { case WebInputEvent::GestureFlingStart: { m_lastWheelPosition = WebPoint(event.x, event.y); m_lastWheelGlobalPosition = WebPoint(event.globalX, event.globalY); m_flingModifier = event.modifiers; // FIXME: Make the curve parametrizable from the browser. m_gestureAnimation = ActivePlatformGestureAnimation::create(TouchpadFlingPlatformGestureCurve::create(FloatPoint(event.deltaX, event.deltaY)), this); scheduleAnimation(); return true; } case WebInputEvent::GestureFlingCancel: if (m_gestureAnimation) { m_gestureAnimation.clear(); return true; } return false; case WebInputEvent::GestureTap: { PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(), event); RefPtr selectPopup; selectPopup = m_selectPopup; hideSelectPopup(); ASSERT(!m_selectPopup); bool gestureHandled = mainFrameImpl()->frame()->eventHandler()->handleGestureEvent(platformEvent); if (m_selectPopup && m_selectPopup == selectPopup) { // That tap triggered a select popup which is the same as the one that // was showing before the tap. It means the user tapped the select // while the popup was showing, and as a result we first closed then // immediately reopened the select popup. It needs to be closed. hideSelectPopup(); } return gestureHandled; } case WebInputEvent::GestureTwoFingerTap: case WebInputEvent::GestureLongPress: { if (!mainFrameImpl() || !mainFrameImpl()->frameView()) return false; m_page->contextMenuController()->clearContextMenu(); m_contextMenuAllowed = true; PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(), event); bool handled = mainFrameImpl()->frame()->eventHandler()->sendContextMenuEventForGesture(platformEvent); m_contextMenuAllowed = false; return handled; } case WebInputEvent::GestureScrollBegin: case WebInputEvent::GestureScrollEnd: case WebInputEvent::GestureScrollUpdate: case WebInputEvent::GestureTapDown: case WebInputEvent::GestureDoubleTap: case WebInputEvent::GesturePinchBegin: case WebInputEvent::GesturePinchEnd: case WebInputEvent::GesturePinchUpdate: { PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(), event); return mainFrameImpl()->frame()->eventHandler()->handleGestureEvent(platformEvent); } default: ASSERT_NOT_REACHED(); } return false; } void WebViewImpl::transferActiveWheelFlingAnimation(const WebActiveWheelFlingParameters& parameters) { TRACE_EVENT0("webkit", "WebViewImpl::transferActiveWheelFlingAnimation"); ASSERT(!m_gestureAnimation); m_lastWheelPosition = parameters.point; m_lastWheelGlobalPosition = parameters.globalPoint; m_flingModifier = parameters.modifiers; OwnPtr curve = TouchpadFlingPlatformGestureCurve::create(parameters.delta, IntPoint(parameters.cumulativeScroll)); m_gestureAnimation = ActivePlatformGestureAnimation::create(curve.release(), this, parameters.startTime); scheduleAnimation(); } void WebViewImpl::renderingStats(WebRenderingStats& stats) const { ASSERT(isAcceleratedCompositingActive()); if (!m_layerTreeView.isNull()) m_layerTreeView.renderingStats(stats); } void WebViewImpl::startPageScaleAnimation(const IntPoint& scroll, bool useAnchor, float newScale, double durationSec) { if (!m_layerTreeView.isNull()) m_layerTreeView.startPageScaleAnimation(scroll, useAnchor, newScale, durationSec); } #endif bool WebViewImpl::handleKeyEvent(const WebKeyboardEvent& event) { ASSERT((event.type == WebInputEvent::RawKeyDown) || (event.type == WebInputEvent::KeyDown) || (event.type == WebInputEvent::KeyUp)); // Halt an in-progress fling on a key event. if (m_gestureAnimation) m_gestureAnimation.clear(); // Please refer to the comments explaining the m_suppressNextKeypressEvent // member. // The m_suppressNextKeypressEvent is set if the KeyDown is handled by // Webkit. A keyDown event is typically associated with a keyPress(char) // event and a keyUp event. We reset this flag here as this is a new keyDown // event. m_suppressNextKeypressEvent = false; // If there is a select popup, it should be the one processing the event, // not the page. if (m_selectPopup) return m_selectPopup->handleKeyEvent(PlatformKeyboardEventBuilder(event)); #if ENABLE(PAGE_POPUP) if (m_pagePopup) { m_pagePopup->handleKeyEvent(PlatformKeyboardEventBuilder(event)); // We need to ignore the next Char event after this otherwise pressing // enter when selecting an item in the popup will go to the page. if (WebInputEvent::RawKeyDown == event.type) m_suppressNextKeypressEvent = true; return true; } #endif // Give Autocomplete a chance to consume the key events it is interested in. if (autocompleteHandleKeyEvent(event)) return true; RefPtr frame = focusedWebCoreFrame(); if (!frame) return false; EventHandler* handler = frame->eventHandler(); if (!handler) return keyEventDefault(event); #if !OS(DARWIN) const WebInputEvent::Type contextMenuTriggeringEventType = #if OS(WINDOWS) WebInputEvent::KeyUp; #elif OS(UNIX) WebInputEvent::RawKeyDown; #endif bool isUnmodifiedMenuKey = !(event.modifiers & WebInputEvent::InputModifiers) && event.windowsKeyCode == VKEY_APPS; bool isShiftF10 = event.modifiers == WebInputEvent::ShiftKey && event.windowsKeyCode == VKEY_F10; if ((isUnmodifiedMenuKey || isShiftF10) && event.type == contextMenuTriggeringEventType) { sendContextMenuEvent(event); return true; } #endif // !OS(DARWIN) PlatformKeyboardEventBuilder evt(event); if (handler->keyEvent(evt)) { if (WebInputEvent::RawKeyDown == event.type) { // Suppress the next keypress event unless the focused node is a plug-in node. // (Flash needs these keypress events to handle non-US keyboards.) Node* node = focusedWebCoreNode(); if (!node || !node->renderer() || !node->renderer()->isEmbeddedObject()) m_suppressNextKeypressEvent = true; } return true; } return keyEventDefault(event); } bool WebViewImpl::autocompleteHandleKeyEvent(const WebKeyboardEvent& event) { if (!m_autofillPopupShowing // Home and End should be left to the text field to process. || event.windowsKeyCode == VKEY_HOME || event.windowsKeyCode == VKEY_END) return false; // Pressing delete triggers the removal of the selected suggestion from the DB. if (event.windowsKeyCode == VKEY_DELETE && m_autofillPopup->selectedIndex() != -1) { Node* node = focusedWebCoreNode(); if (!node || (node->nodeType() != Node::ELEMENT_NODE)) { ASSERT_NOT_REACHED(); return false; } Element* element = static_cast(node); if (!element->hasLocalName(HTMLNames::inputTag)) { ASSERT_NOT_REACHED(); return false; } int selectedIndex = m_autofillPopup->selectedIndex(); if (!m_autofillPopupClient->canRemoveSuggestionAtIndex(selectedIndex)) return false; WebString name = WebInputElement(static_cast(element)).nameForAutofill(); WebString value = m_autofillPopupClient->itemText(selectedIndex); m_autofillClient->removeAutocompleteSuggestion(name, value); // Update the entries in the currently showing popup to reflect the // deletion. m_autofillPopupClient->removeSuggestionAtIndex(selectedIndex); refreshAutofillPopup(); return false; } if (!m_autofillPopup->isInterestedInEventForKey(event.windowsKeyCode)) return false; if (m_autofillPopup->handleKeyEvent(PlatformKeyboardEventBuilder(event))) { // We need to ignore the next Char event after this otherwise pressing // enter when selecting an item in the menu will go to the page. if (WebInputEvent::RawKeyDown == event.type) m_suppressNextKeypressEvent = true; return true; } return false; } bool WebViewImpl::handleCharEvent(const WebKeyboardEvent& event) { ASSERT(event.type == WebInputEvent::Char); // Please refer to the comments explaining the m_suppressNextKeypressEvent // member. The m_suppressNextKeypressEvent is set if the KeyDown is // handled by Webkit. A keyDown event is typically associated with a // keyPress(char) event and a keyUp event. We reset this flag here as it // only applies to the current keyPress event. bool suppress = m_suppressNextKeypressEvent; m_suppressNextKeypressEvent = false; // If there is a select popup, it should be the one processing the event, // not the page. if (m_selectPopup) return m_selectPopup->handleKeyEvent(PlatformKeyboardEventBuilder(event)); #if ENABLE(PAGE_POPUP) if (m_pagePopup) return m_pagePopup->handleKeyEvent(PlatformKeyboardEventBuilder(event)); #endif Frame* frame = focusedWebCoreFrame(); if (!frame) return suppress; EventHandler* handler = frame->eventHandler(); if (!handler) return suppress || keyEventDefault(event); PlatformKeyboardEventBuilder evt(event); if (!evt.isCharacterKey()) return true; // Accesskeys are triggered by char events and can't be suppressed. if (handler->handleAccessKey(evt)) return true; // Safari 3.1 does not pass off windows system key messages (WM_SYSCHAR) to // the eventHandler::keyEvent. We mimic this behavior on all platforms since // for now we are converting other platform's key events to windows key // events. if (evt.isSystemKey()) return false; if (!suppress && !handler->keyEvent(evt)) return keyEventDefault(event); return true; } #if ENABLE(GESTURE_EVENTS) WebRect WebViewImpl::computeBlockBounds(const WebRect& rect, AutoZoomType zoomType) { if (!mainFrameImpl()) return WebRect(); // Use the rect-based hit test to find the node. IntPoint point = mainFrameImpl()->frameView()->windowToContents(IntPoint(rect.x, rect.y)); HitTestResult result = mainFrameImpl()->frame()->eventHandler()->hitTestResultAtPoint(point, false, zoomType == FindInPage, DontHitTestScrollbars, HitTestRequest::Active | HitTestRequest::ReadOnly, IntSize(rect.width, rect.height)); Node* node = result.innerNonSharedNode(); if (!node) return WebRect(); // Find the block type node based on the hit node. while (node && (!node->renderer() || node->renderer()->isInline())) node = node->parentNode(); // Return the bounding box in the window coordinate system. if (node) { IntRect rect = node->Node::getPixelSnappedRect(); Frame* frame = node->document()->frame(); return frame->view()->contentsToWindow(rect); } return WebRect(); } WebRect WebViewImpl::widenRectWithinPageBounds(const WebRect& source, int targetMargin, int minimumMargin) { WebSize maxSize; if (mainFrame()) maxSize = mainFrame()->contentsSize(); IntSize scrollOffset; if (mainFrame()) scrollOffset = mainFrame()->scrollOffset(); int leftMargin = targetMargin; int rightMargin = targetMargin; const int absoluteSourceX = source.x + scrollOffset.width(); if (leftMargin > absoluteSourceX) { leftMargin = absoluteSourceX; rightMargin = max(leftMargin, minimumMargin); } const int maximumRightMargin = maxSize.width - (source.width + absoluteSourceX); if (rightMargin > maximumRightMargin) { rightMargin = maximumRightMargin; leftMargin = min(leftMargin, max(rightMargin, minimumMargin)); } const int newWidth = source.width + leftMargin + rightMargin; const int newX = source.x - leftMargin; ASSERT(newWidth >= 0); ASSERT(scrollOffset.width() + newX + newWidth <= maxSize.width); return WebRect(newX, source.y, newWidth, source.height); } void WebViewImpl::computeScaleAndScrollForHitRect(const WebRect& hitRect, AutoZoomType zoomType, float& scale, WebPoint& scroll) { scale = pageScaleFactor(); scroll.x = scroll.y = 0; WebRect targetRect = hitRect; if (targetRect.isEmpty()) targetRect.width = targetRect.height = touchPointPadding; WebRect rect = computeBlockBounds(targetRect, zoomType); const float overviewScale = m_minimumPageScaleFactor; bool scaleUnchanged = true; if (!rect.isEmpty()) { // Pages should be as legible as on desktop when at dpi scale, so no // need to zoom in further when automatically determining zoom level // (after double tap, find in page, etc), though the user should still // be allowed to manually pinch zoom in further if they desire. const float maxScale = deviceScaleFactor(); const float defaultMargin = doubleTapZoomContentDefaultMargin * deviceScaleFactor(); const float minimumMargin = doubleTapZoomContentMinimumMargin * deviceScaleFactor(); // We want the margins to have the same physical size, which means we // need to express them in post-scale size. To do that we'd need to know // the scale we're scaling to, but that depends on the margins. Instead // we express them as a fraction of the target rectangle: this will be // correct if we end up fully zooming to it, and won't matter if we // don't. rect = widenRectWithinPageBounds(rect, static_cast(defaultMargin * rect.width / m_size.width), static_cast(minimumMargin * rect.width / m_size.width)); // Fit block to screen, respecting limits. scale *= static_cast(m_size.width) / rect.width; scale = min(scale, maxScale); scale = clampPageScaleFactorToLimits(scale); scaleUnchanged = fabs(pageScaleFactor() - scale) < minScaleDifference; } if (zoomType == DoubleTap) { if (rect.isEmpty() || scaleUnchanged) { // Zoom out to overview mode. if (overviewScale) scale = overviewScale; return; } } else if (rect.isEmpty()) { // Keep current scale (no need to scroll as x,y will normally already // be visible). FIXME: Revisit this if it isn't always true. return; } // FIXME: If this is being called for auto zoom during find in page, // then if the user manually zooms in it'd be nice to preserve the relative // increase in zoom they caused (if they zoom out then it's ok to zoom // them back in again). This isn't compatible with our current double-tap // zoom strategy (fitting the containing block to the screen) though. float screenHeight = m_size.height / scale * pageScaleFactor(); float screenWidth = m_size.width / scale * pageScaleFactor(); // Scroll to vertically align the block. if (rect.height < screenHeight) { // Vertically center short blocks. rect.y -= 0.5 * (screenHeight - rect.height); } else { // Ensure position we're zooming to (+ padding) isn't off the bottom of // the screen. rect.y = max(rect.y, hitRect.y + touchPointPadding - screenHeight); } // Otherwise top align the block. // Do the same thing for horizontal alignment. if (rect.width < screenWidth) rect.x -= 0.5 * (screenWidth - rect.width); else rect.x = max(rect.x, hitRect.x + touchPointPadding - screenWidth); scroll.x = rect.x; scroll.y = rect.y; } #endif void WebViewImpl::numberOfWheelEventHandlersChanged(unsigned numberOfWheelHandlers) { if (m_client) m_client->numberOfWheelEventHandlersChanged(numberOfWheelHandlers); } void WebViewImpl::numberOfTouchEventHandlersChanged(unsigned numberOfTouchHandlers) { if (m_client) m_client->numberOfTouchEventHandlersChanged(numberOfTouchHandlers); } #if !OS(DARWIN) // Mac has no way to open a context menu based on a keyboard event. bool WebViewImpl::sendContextMenuEvent(const WebKeyboardEvent& event) { // The contextMenuController() holds onto the last context menu that was // popped up on the page until a new one is created. We need to clear // this menu before propagating the event through the DOM so that we can // detect if we create a new menu for this event, since we won't create // a new menu if the DOM swallows the event and the defaultEventHandler does // not run. page()->contextMenuController()->clearContextMenu(); m_contextMenuAllowed = true; Frame* focusedFrame = page()->focusController()->focusedOrMainFrame(); bool handled = focusedFrame->eventHandler()->sendContextMenuEventForKey(); m_contextMenuAllowed = false; return handled; } #endif bool WebViewImpl::keyEventDefault(const WebKeyboardEvent& event) { Frame* frame = focusedWebCoreFrame(); if (!frame) return false; switch (event.type) { case WebInputEvent::Char: if (event.windowsKeyCode == VKEY_SPACE) { int keyCode = ((event.modifiers & WebInputEvent::ShiftKey) ? VKEY_PRIOR : VKEY_NEXT); return scrollViewWithKeyboard(keyCode, event.modifiers); } break; case WebInputEvent::RawKeyDown: if (event.modifiers == WebInputEvent::ControlKey) { switch (event.windowsKeyCode) { #if !OS(DARWIN) case 'A': focusedFrame()->executeCommand(WebString::fromUTF8("SelectAll")); return true; case VKEY_INSERT: case 'C': focusedFrame()->executeCommand(WebString::fromUTF8("Copy")); return true; #endif // Match FF behavior in the sense that Ctrl+home/end are the only Ctrl // key combinations which affect scrolling. Safari is buggy in the // sense that it scrolls the page for all Ctrl+scrolling key // combinations. For e.g. Ctrl+pgup/pgdn/up/down, etc. case VKEY_HOME: case VKEY_END: break; default: return false; } } if (!event.isSystemKey && !(event.modifiers & WebInputEvent::ShiftKey)) return scrollViewWithKeyboard(event.windowsKeyCode, event.modifiers); break; default: break; } return false; } bool WebViewImpl::scrollViewWithKeyboard(int keyCode, int modifiers) { ScrollDirection scrollDirection; ScrollGranularity scrollGranularity; #if OS(DARWIN) // Control-Up/Down should be PageUp/Down on Mac. if (modifiers & WebMouseEvent::ControlKey) { if (keyCode == VKEY_UP) keyCode = VKEY_PRIOR; else if (keyCode == VKEY_DOWN) keyCode = VKEY_NEXT; } #endif if (!mapKeyCodeForScroll(keyCode, &scrollDirection, &scrollGranularity)) return false; return propagateScroll(scrollDirection, scrollGranularity); } bool WebViewImpl::mapKeyCodeForScroll(int keyCode, WebCore::ScrollDirection* scrollDirection, WebCore::ScrollGranularity* scrollGranularity) { switch (keyCode) { case VKEY_LEFT: *scrollDirection = ScrollLeft; *scrollGranularity = ScrollByLine; break; case VKEY_RIGHT: *scrollDirection = ScrollRight; *scrollGranularity = ScrollByLine; break; case VKEY_UP: *scrollDirection = ScrollUp; *scrollGranularity = ScrollByLine; break; case VKEY_DOWN: *scrollDirection = ScrollDown; *scrollGranularity = ScrollByLine; break; case VKEY_HOME: *scrollDirection = ScrollUp; *scrollGranularity = ScrollByDocument; break; case VKEY_END: *scrollDirection = ScrollDown; *scrollGranularity = ScrollByDocument; break; case VKEY_PRIOR: // page up *scrollDirection = ScrollUp; *scrollGranularity = ScrollByPage; break; case VKEY_NEXT: // page down *scrollDirection = ScrollDown; *scrollGranularity = ScrollByPage; break; default: return false; } return true; } void WebViewImpl::hideSelectPopup() { if (m_selectPopup) m_selectPopup->hidePopup(); } bool WebViewImpl::propagateScroll(ScrollDirection scrollDirection, ScrollGranularity scrollGranularity) { Frame* frame = focusedWebCoreFrame(); if (!frame) return false; bool scrollHandled = frame->eventHandler()->scrollOverflow(scrollDirection, scrollGranularity); Frame* currentFrame = frame; while (!scrollHandled && currentFrame) { scrollHandled = currentFrame->view()->scroll(scrollDirection, scrollGranularity); currentFrame = currentFrame->tree()->parent(); } return scrollHandled; } void WebViewImpl::popupOpened(WebCore::PopupContainer* popupContainer) { if (popupContainer->popupType() == WebCore::PopupContainer::Select) { ASSERT(!m_selectPopup); m_selectPopup = popupContainer; } } void WebViewImpl::popupClosed(WebCore::PopupContainer* popupContainer) { if (popupContainer->popupType() == WebCore::PopupContainer::Select) { ASSERT(m_selectPopup); m_selectPopup = 0; } } #if ENABLE(PAGE_POPUP) PagePopup* WebViewImpl::openPagePopup(PagePopupClient* client, const IntRect& originBoundsInRootView) { ASSERT(client); if (hasOpenedPopup()) hidePopups(); ASSERT(!m_pagePopup); WebWidget* popupWidget = m_client->createPopupMenu(WebPopupTypePage); ASSERT(popupWidget); m_pagePopup = static_cast(popupWidget); if (!m_pagePopup->init(this, client, originBoundsInRootView)) { m_pagePopup->closePopup(); m_pagePopup = 0; } if (Frame* frame = focusedWebCoreFrame()) frame->selection()->setCaretVisible(false); return m_pagePopup.get(); } void WebViewImpl::closePagePopup(PagePopup* popup) { ASSERT(popup); WebPagePopupImpl* popupImpl = static_cast(popup); ASSERT(m_pagePopup.get() == popupImpl); if (m_pagePopup.get() != popupImpl) return; m_pagePopup->closePopup(); m_pagePopup = 0; if (Frame* frame = focusedWebCoreFrame()) frame->selection()->pageActivationChanged(); } #endif void WebViewImpl::hideAutofillPopup() { if (m_autofillPopupShowing) { m_autofillPopup->hidePopup(); m_autofillPopupShowing = false; } } WebHelperPluginImpl* WebViewImpl::createHelperPlugin(const String& pluginType) { WebWidget* popupWidget = m_client->createPopupMenu(WebPopupTypeHelperPlugin); ASSERT(popupWidget); WebHelperPluginImpl* helperPlugin = static_cast(popupWidget); if (!helperPlugin->init(this, pluginType)) { helperPlugin->closeHelperPlugin(); helperPlugin = 0; } return helperPlugin; } Frame* WebViewImpl::focusedWebCoreFrame() const { return m_page ? m_page->focusController()->focusedOrMainFrame() : 0; } WebViewImpl* WebViewImpl::fromPage(Page* page) { if (!page) return 0; ChromeClientImpl* chromeClient = static_cast(page->chrome()->client()); return static_cast(chromeClient->webView()); } PageGroup* WebViewImpl::defaultPageGroup() { return PageGroup::pageGroup(pageGroupName); } // WebWidget ------------------------------------------------------------------ void WebViewImpl::close() { RefPtr mainFrameImpl; if (m_page) { // Initiate shutdown for the entire frameset. This will cause a lot of // notifications to be sent. if (m_page->mainFrame()) { mainFrameImpl = WebFrameImpl::fromFrame(m_page->mainFrame()); m_page->mainFrame()->loader()->frameDetached(); } m_page.clear(); } // Should happen after m_page.clear(). if (m_devToolsAgent) m_devToolsAgent.clear(); // Reset the delegate to prevent notifications being sent as we're being // deleted. m_client = 0; deref(); // Balances ref() acquired in WebView::create } void WebViewImpl::willStartLiveResize() { if (mainFrameImpl() && mainFrameImpl()->frameView()) mainFrameImpl()->frameView()->willStartLiveResize(); Frame* frame = mainFrameImpl()->frame(); WebPluginContainerImpl* pluginContainer = WebFrameImpl::pluginContainerFromFrame(frame); if (pluginContainer) pluginContainer->willStartLiveResize(); } void WebViewImpl::resize(const WebSize& newSize) { if (m_shouldAutoResize || m_size == newSize) return; FrameView* view = mainFrameImpl()->frameView(); if (!view) return; WebSize oldSize = m_size; float oldPageScaleFactor = pageScaleFactor(); IntSize oldScrollOffset = view->scrollOffset(); int oldFixedLayoutWidth = fixedLayoutSize().width; m_size = newSize; #if ENABLE(VIEWPORT) if (settings()->viewportEnabled()) { // Fallback width is used to layout sites designed for desktop. The // conventional size used by all mobile browsers is 980. When a mobile // device has a particularly wide screen (such as a 10" tablet held in // landscape), it can be larger. const int standardFallbackWidth = 980; int dpiIndependentViewportWidth = newSize.width / page()->deviceScaleFactor(); settings()->setLayoutFallbackWidth(std::max(standardFallbackWidth, dpiIndependentViewportWidth)); ViewportArguments viewportArguments = mainFrameImpl()->frame()->document()->viewportArguments(); m_page->chrome()->client()->dispatchViewportPropertiesDidChange(viewportArguments); } #endif WebDevToolsAgentPrivate* agentPrivate = devToolsAgentPrivate(); if (agentPrivate && agentPrivate->metricsOverridden()) agentPrivate->webViewResized(); else { WebFrameImpl* webFrame = mainFrameImpl(); if (webFrame->frameView()) webFrame->frameView()->resize(newSize.width, newSize.height); } #if ENABLE(VIEWPORT) if (settings()->viewportEnabled()) { // Relayout immediately to obtain the new content width, which is needed // to calculate the minimum scale limit. view->layout(); computePageScaleFactorLimits(); // When the device rotates: // - If the page width is unchanged, then zoom by new width/old width // such as to keep the same content horizontally onscreen. // - If the page width stretches proportionally to the change in // screen width, then don't zoom at all (assuming the content has // scaled uniformly, then the same content will be horizontally // onscreen). // - If the page width partially stretches, then zoom partially to // make up the difference. // In all cases try to keep the same content at the top of the screen. float viewportWidthRatio = !oldSize.width ? 1 : newSize.width / (float) oldSize.width; float fixedLayoutWidthRatio = !oldFixedLayoutWidth ? 1 : fixedLayoutSize().width / (float) oldFixedLayoutWidth; float scaleMultiplier = viewportWidthRatio / fixedLayoutWidthRatio; if (scaleMultiplier != 1) { IntSize scrollOffsetAtNewScale = oldScrollOffset; scrollOffsetAtNewScale.scale(scaleMultiplier); setPageScaleFactor(oldPageScaleFactor * scaleMultiplier, IntPoint(scrollOffsetAtNewScale)); } } #endif sendResizeEventAndRepaint(); } void WebViewImpl::willEndLiveResize() { if (mainFrameImpl() && mainFrameImpl()->frameView()) mainFrameImpl()->frameView()->willEndLiveResize(); Frame* frame = mainFrameImpl()->frame(); WebPluginContainerImpl* pluginContainer = WebFrameImpl::pluginContainerFromFrame(frame); if (pluginContainer) pluginContainer->willEndLiveResize(); } void WebViewImpl::willEnterFullScreen() { #if ENABLE(FULLSCREEN_API) if (!m_provisionalFullScreenElement) return; // Ensure that this element's document is still attached. Document* doc = m_provisionalFullScreenElement->document(); if (doc->frame()) { doc->webkitWillEnterFullScreenForElement(m_provisionalFullScreenElement.get()); m_fullScreenFrame = doc->frame(); } m_provisionalFullScreenElement.clear(); #endif } void WebViewImpl::didEnterFullScreen() { #if ENABLE(FULLSCREEN_API) if (!m_fullScreenFrame) return; if (Document* doc = m_fullScreenFrame->document()) { if (doc->webkitIsFullScreen()) doc->webkitDidEnterFullScreenForElement(0); } #endif } void WebViewImpl::willExitFullScreen() { #if ENABLE(FULLSCREEN_API) if (!m_fullScreenFrame) return; if (Document* doc = m_fullScreenFrame->document()) { if (doc->webkitIsFullScreen()) { // When the client exits from full screen we have to call webkitCancelFullScreen to // notify the document. While doing that, suppress notifications back to the client. m_isCancelingFullScreen = true; doc->webkitCancelFullScreen(); m_isCancelingFullScreen = false; doc->webkitWillExitFullScreenForElement(0); } } #endif } void WebViewImpl::didExitFullScreen() { #if ENABLE(FULLSCREEN_API) if (!m_fullScreenFrame) return; if (Document* doc = m_fullScreenFrame->document()) { if (doc->webkitIsFullScreen()) doc->webkitDidExitFullScreenForElement(0); } m_fullScreenFrame.clear(); #endif } void WebViewImpl::instrumentBeginFrame() { InspectorInstrumentation::didBeginFrame(m_page.get()); } void WebViewImpl::instrumentCancelFrame() { InspectorInstrumentation::didCancelFrame(m_page.get()); } #if ENABLE(BATTERY_STATUS) void WebViewImpl::updateBatteryStatus(const WebBatteryStatus& status) { m_batteryClient->updateBatteryStatus(status); } #endif void WebViewImpl::setCompositorSurfaceReady() { m_compositorSurfaceReady = true; if (!m_layerTreeView.isNull()) m_layerTreeView.setSurfaceReady(); } void WebViewImpl::animate(double) { #if ENABLE(REQUEST_ANIMATION_FRAME) double monotonicFrameBeginTime = monotonicallyIncreasingTime(); #if USE(ACCELERATED_COMPOSITING) // In composited mode, we always go through the compositor so it can apply // appropriate flow-control mechanisms. if (isAcceleratedCompositingActive()) m_layerTreeView.updateAnimations(monotonicFrameBeginTime); else #endif updateAnimations(monotonicFrameBeginTime); #endif } void WebViewImpl::willBeginFrame() { instrumentBeginFrame(); m_client->willBeginCompositorFrame(); } void WebViewImpl::didBeginFrame() { InspectorInstrumentation::didComposite(m_page.get()); } void WebViewImpl::updateAnimations(double monotonicFrameBeginTime) { #if ENABLE(REQUEST_ANIMATION_FRAME) TRACE_EVENT("WebViewImpl::updateAnimations", this, 0); WebFrameImpl* webframe = mainFrameImpl(); if (!webframe) return; FrameView* view = webframe->frameView(); if (!view) return; // Create synthetic wheel events as necessary for fling. if (m_gestureAnimation) { if (m_gestureAnimation->animate(monotonicFrameBeginTime)) scheduleAnimation(); else m_gestureAnimation.clear(); } PageWidgetDelegate::animate(m_page.get(), monotonicFrameBeginTime); #endif } void WebViewImpl::layout() { TRACE_EVENT("WebViewImpl::layout", this, 0); PageWidgetDelegate::layout(m_page.get()); } #if USE(ACCELERATED_COMPOSITING) void WebViewImpl::doPixelReadbackToCanvas(WebCanvas* canvas, const IntRect& rect) { ASSERT(!m_layerTreeView.isNull()); PlatformContextSkia context(canvas); // PlatformGraphicsContext is actually a pointer to PlatformContextSkia GraphicsContext gc(reinterpret_cast(&context)); int bitmapHeight = canvas->getDevice()->accessBitmap(false).height(); // Compute rect to sample from inverted GPU buffer. IntRect invertRect(rect.x(), bitmapHeight - rect.maxY(), rect.width(), rect.height()); OwnPtr imageBuffer(ImageBuffer::create(rect.size())); RefPtr pixelArray(Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4)); if (imageBuffer && pixelArray) { m_layerTreeView.compositeAndReadback(pixelArray->data(), invertRect); imageBuffer->putByteArray(Premultiplied, pixelArray.get(), rect.size(), IntRect(IntPoint(), rect.size()), IntPoint()); gc.save(); gc.translate(IntSize(0, bitmapHeight)); gc.scale(FloatSize(1.0f, -1.0f)); // Use invertRect in next line, so that transform above inverts it back to // desired destination rect. gc.drawImageBuffer(imageBuffer.get(), ColorSpaceDeviceRGB, invertRect.location()); gc.restore(); } } #endif void WebViewImpl::paint(WebCanvas* canvas, const WebRect& rect) { if (isAcceleratedCompositingActive()) { #if USE(ACCELERATED_COMPOSITING) // If a canvas was passed in, we use it to grab a copy of the // freshly-rendered pixels. if (canvas) { // Clip rect to the confines of the rootLayerTexture. IntRect resizeRect(rect); resizeRect.intersect(IntRect(IntPoint(0, 0), m_layerTreeView.viewportSize())); doPixelReadbackToCanvas(canvas, resizeRect); } #endif } else { double paintStart = currentTime(); PageWidgetDelegate::paint(m_page.get(), pageOverlays(), canvas, rect, isTransparent() ? PageWidgetDelegate::Translucent : PageWidgetDelegate::Opaque); double paintEnd = currentTime(); double pixelsPerSec = (rect.width * rect.height) / (paintEnd - paintStart); WebKit::Platform::current()->histogramCustomCounts("Renderer4.SoftwarePaintDurationMS", (paintEnd - paintStart) * 1000, 0, 120, 30); WebKit::Platform::current()->histogramCustomCounts("Renderer4.SoftwarePaintMegapixPerSecond", pixelsPerSec / 1000000, 10, 210, 30); } } void WebViewImpl::themeChanged() { if (!page()) return; FrameView* view = page()->mainFrame()->view(); WebRect damagedRect(0, 0, m_size.width, m_size.height); view->invalidateRect(damagedRect); } void WebViewImpl::composite(bool) { #if USE(ACCELERATED_COMPOSITING) if (CCProxy::hasImplThread()) m_layerTreeView.setNeedsRedraw(); else { ASSERT(isAcceleratedCompositingActive()); if (!page()) return; if (m_pageOverlays) m_pageOverlays->update(); m_layerTreeView.composite(); } #endif } void WebViewImpl::setNeedsRedraw() { #if USE(ACCELERATED_COMPOSITING) if (!m_layerTreeView.isNull() && isAcceleratedCompositingActive()) m_layerTreeView.setNeedsRedraw(); #endif } bool WebViewImpl::isInputThrottled() const { #if USE(ACCELERATED_COMPOSITING) if (!m_layerTreeView.isNull() && isAcceleratedCompositingActive()) return m_layerTreeView.commitRequested(); #endif return false; } void WebViewImpl::loseCompositorContext(int numTimes) { #if USE(ACCELERATED_COMPOSITING) if (!m_layerTreeView.isNull()) m_layerTreeView.loseCompositorContext(numTimes); #endif } void WebViewImpl::enterFullScreenForElement(WebCore::Element* element) { // We are already transitioning to fullscreen for a different element. if (m_provisionalFullScreenElement) { m_provisionalFullScreenElement = element; return; } // We are already in fullscreen mode. if (m_fullScreenFrame) { m_provisionalFullScreenElement = element; willEnterFullScreen(); didEnterFullScreen(); return; } #if USE(NATIVE_FULLSCREEN_VIDEO) if (element && element->isMediaElement()) { HTMLMediaElement* mediaElement = static_cast(element); if (mediaElement->player() && mediaElement->player()->canEnterFullscreen()) { mediaElement->player()->enterFullscreen(); m_provisionalFullScreenElement = element; } return; } #endif // We need to transition to fullscreen mode. if (m_client && m_client->enterFullScreen()) m_provisionalFullScreenElement = element; } void WebViewImpl::exitFullScreenForElement(WebCore::Element* element) { // The client is exiting full screen, so don't send a notification. if (m_isCancelingFullScreen) return; #if USE(NATIVE_FULLSCREEN_VIDEO) if (element && element->isMediaElement()) { HTMLMediaElement* mediaElement = static_cast(element); if (mediaElement->player()) mediaElement->player()->exitFullscreen(); return; } #endif if (m_client) m_client->exitFullScreen(); } bool WebViewImpl::hasHorizontalScrollbar() { return mainFrameImpl()->frameView()->horizontalScrollbar(); } bool WebViewImpl::hasVerticalScrollbar() { return mainFrameImpl()->frameView()->verticalScrollbar(); } const WebInputEvent* WebViewImpl::m_currentInputEvent = 0; bool WebViewImpl::handleInputEvent(const WebInputEvent& inputEvent) { UserGestureIndicator gestureIndicator(WebInputEvent::isUserGestureEventType(inputEvent.type) ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture); // If we've started a drag and drop operation, ignore input events until // we're done. if (m_doingDragAndDrop) return true; // Report the event to be NOT processed by WebKit, so that the browser can handle it appropriately. if (m_ignoreInputEvents) return false; m_currentInputEvent = &inputEvent; #if ENABLE(POINTER_LOCK) if (isPointerLocked() && WebInputEvent::isMouseEventType(inputEvent.type)) { pointerLockMouseEvent(inputEvent); return true; } #endif if (m_mouseCaptureNode && WebInputEvent::isMouseEventType(inputEvent.type)) { // Save m_mouseCaptureNode since mouseCaptureLost() will clear it. RefPtr node = m_mouseCaptureNode; // Not all platforms call mouseCaptureLost() directly. if (inputEvent.type == WebInputEvent::MouseUp) mouseCaptureLost(); AtomicString eventType; switch (inputEvent.type) { case WebInputEvent::MouseMove: eventType = eventNames().mousemoveEvent; break; case WebInputEvent::MouseLeave: eventType = eventNames().mouseoutEvent; break; case WebInputEvent::MouseDown: eventType = eventNames().mousedownEvent; break; case WebInputEvent::MouseUp: eventType = eventNames().mouseupEvent; break; default: ASSERT_NOT_REACHED(); } node->dispatchMouseEvent( PlatformMouseEventBuilder(mainFrameImpl()->frameView(), *static_cast(&inputEvent)), eventType, static_cast(&inputEvent)->clickCount); m_currentInputEvent = 0; return true; } bool handled = PageWidgetDelegate::handleInputEvent(m_page.get(), *this, inputEvent); m_currentInputEvent = 0; return handled; } void WebViewImpl::mouseCaptureLost() { m_mouseCaptureNode = 0; } void WebViewImpl::setFocus(bool enable) { m_page->focusController()->setFocused(enable); if (enable) { m_page->focusController()->setActive(true); RefPtr focusedFrame = m_page->focusController()->focusedFrame(); if (focusedFrame) { Node* focusedNode = focusedFrame->document()->focusedNode(); if (focusedNode && focusedNode->isElementNode() && focusedFrame->selection()->selection().isNone()) { // If the selection was cleared while the WebView was not // focused, then the focus element shows with a focus ring but // no caret and does respond to keyboard inputs. Element* element = static_cast(focusedNode); if (element->isTextFormControl()) element->updateFocusAppearance(true); else if (focusedNode->isContentEditable()) { // updateFocusAppearance() selects all the text of // contentseditable DIVs. So we set the selection explicitly // instead. Note that this has the side effect of moving the // caret back to the beginning of the text. Position position(focusedNode, 0, Position::PositionIsOffsetInAnchor); focusedFrame->selection()->setSelection( VisibleSelection(position, SEL_DEFAULT_AFFINITY)); } } } m_imeAcceptEvents = true; } else { hidePopups(); // Clear focus on the currently focused frame if any. if (!m_page) return; Frame* frame = m_page->mainFrame(); if (!frame) return; RefPtr focusedFrame = m_page->focusController()->focusedFrame(); if (focusedFrame) { // Finish an ongoing composition to delete the composition node. Editor* editor = focusedFrame->editor(); if (editor && editor->hasComposition()) editor->confirmComposition(); m_imeAcceptEvents = false; } } } bool WebViewImpl::setComposition( const WebString& text, const WebVector& underlines, int selectionStart, int selectionEnd) { Frame* focused = focusedWebCoreFrame(); if (!focused || !m_imeAcceptEvents) return false; Editor* editor = focused->editor(); if (!editor) return false; // The input focus has been moved to another WebWidget object. // We should use this |editor| object only to complete the ongoing // composition. if (!editor->canEdit() && !editor->hasComposition()) return false; // We should verify the parent node of this IME composition node are // editable because JavaScript may delete a parent node of the composition // node. In this case, WebKit crashes while deleting texts from the parent // node, which doesn't exist any longer. PassRefPtr range = editor->compositionRange(); if (range) { Node* node = range->startContainer(); if (!node || !node->isContentEditable()) return false; } // If we're not going to fire a keypress event, then the keydown event was // canceled. In that case, cancel any existing composition. if (text.isEmpty() || m_suppressNextKeypressEvent) { // A browser process sent an IPC message which does not contain a valid // string, which means an ongoing composition has been canceled. // If the ongoing composition has been canceled, replace the ongoing // composition string with an empty string and complete it. String emptyString; Vector emptyUnderlines; editor->setComposition(emptyString, emptyUnderlines, 0, 0); return text.isEmpty(); } // When the range of composition underlines overlap with the range between // selectionStart and selectionEnd, WebKit somehow won't paint the selection // at all (see InlineTextBox::paint() function in InlineTextBox.cpp). // But the selection range actually takes effect. editor->setComposition(String(text), CompositionUnderlineVectorBuilder(underlines), selectionStart, selectionEnd); return editor->hasComposition(); } bool WebViewImpl::confirmComposition() { return confirmComposition(WebString()); } bool WebViewImpl::confirmComposition(const WebString& text) { Frame* focused = focusedWebCoreFrame(); if (!focused || !m_imeAcceptEvents) return false; Editor* editor = focused->editor(); if (!editor || (!editor->hasComposition() && !text.length())) return false; // We should verify the parent node of this IME composition node are // editable because JavaScript may delete a parent node of the composition // node. In this case, WebKit crashes while deleting texts from the parent // node, which doesn't exist any longer. PassRefPtr range = editor->compositionRange(); if (range) { Node* node = range->startContainer(); if (!node || !node->isContentEditable()) return false; } if (editor->hasComposition()) { if (text.length()) editor->confirmComposition(String(text)); else editor->confirmComposition(); } else editor->insertText(String(text), 0); return true; } bool WebViewImpl::compositionRange(size_t* location, size_t* length) { Frame* focused = focusedWebCoreFrame(); if (!focused || !focused->selection() || !m_imeAcceptEvents) return false; Editor* editor = focused->editor(); if (!editor || !editor->hasComposition()) return false; RefPtr range = editor->compositionRange(); if (!range) return false; if (TextIterator::getLocationAndLengthFromRange(focused->selection()->rootEditableElementOrDocumentElement(), range.get(), *location, *length)) return true; return false; } WebTextInputInfo WebViewImpl::textInputInfo() { WebTextInputInfo info; Frame* focused = focusedWebCoreFrame(); if (!focused) return info; Editor* editor = focused->editor(); if (!editor || !editor->canEdit()) return info; FrameSelection* selection = focused->selection(); if (!selection) return info; Node* node = focusedWebCoreNode(); if (!node) return info; info.type = textInputType(); if (info.type == WebTextInputTypeNone) return info; if (node->hasTagName(HTMLNames::textareaTag)) info.value = static_cast(node)->value(); else if (node->hasTagName(HTMLNames::inputTag)) info.value = static_cast(node)->value(); else if (node->shouldUseInputMethod()) info.value = node->nodeValue(); else return info; if (info.value.isEmpty()) return info; if (node->hasTagName(HTMLNames::textareaTag) || node->hasTagName(HTMLNames::inputTag)) { HTMLTextFormControlElement* formElement = static_cast(node); info.selectionStart = formElement->selectionStart(); info.selectionEnd = formElement->selectionEnd(); if (editor->hasComposition()) { info.compositionStart = formElement->indexForVisiblePosition(Position(editor->compositionNode(), editor->compositionStart())); info.compositionEnd = formElement->indexForVisiblePosition(Position(editor->compositionNode(), editor->compositionEnd())); } } else { info.selectionStart = selection->start().computeOffsetInContainerNode(); info.selectionEnd = selection->end().computeOffsetInContainerNode(); if (editor->hasComposition()) { info.compositionStart = static_cast(editor->compositionStart()); info.compositionEnd = static_cast(editor->compositionEnd()); } } return info; } WebTextInputType WebViewImpl::textInputType() { Node* node = focusedWebCoreNode(); if (!node) return WebTextInputTypeNone; if (node->hasTagName(HTMLNames::inputTag)) { HTMLInputElement* input = static_cast(node); if (input->readOnly() || input->disabled()) return WebTextInputTypeNone; if (input->isPasswordField()) return WebTextInputTypePassword; if (input->isSearchField()) return WebTextInputTypeSearch; if (input->isEmailField()) return WebTextInputTypeEmail; if (input->isNumberField()) return WebTextInputTypeNumber; if (input->isTelephoneField()) return WebTextInputTypeTelephone; if (input->isURLField()) return WebTextInputTypeURL; if (input->isDateField()) return WebTextInputTypeDate; if (input->isDateTimeField()) return WebTextInputTypeDateTime; if (input->isDateTimeLocalField()) return WebTextInputTypeDateTimeLocal; if (input->isMonthField()) return WebTextInputTypeMonth; if (input->isTimeField()) return WebTextInputTypeTime; if (input->isWeekField()) return WebTextInputTypeWeek; if (input->isTextField()) return WebTextInputTypeText; return WebTextInputTypeNone; } if (node->hasTagName(HTMLNames::textareaTag)) { HTMLTextAreaElement* textarea = static_cast(node); if (textarea->readOnly() || textarea->disabled()) return WebTextInputTypeNone; return WebTextInputTypeText; } if (node->shouldUseInputMethod()) return WebTextInputTypeText; return WebTextInputTypeNone; } bool WebViewImpl::selectionBounds(WebRect& start, WebRect& end) const { const Frame* frame = focusedWebCoreFrame(); if (!frame) return false; FrameSelection* selection = frame->selection(); if (!selection) return false; if (selection->isCaret()) { start = end = frame->view()->contentsToWindow(selection->absoluteCaretBounds()); return true; } RefPtr selectedRange = frame->selection()->toNormalizedRange(); if (!selectedRange) return false; RefPtr range(Range::create(selectedRange->startContainer()->document(), selectedRange->startContainer(), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset())); start = frame->editor()->firstRectForRange(range.get()); range = Range::create(selectedRange->endContainer()->document(), selectedRange->endContainer(), selectedRange->endOffset(), selectedRange->endContainer(), selectedRange->endOffset()); end = frame->editor()->firstRectForRange(range.get()); start = frame->view()->contentsToWindow(start); end = frame->view()->contentsToWindow(end); if (!frame->selection()->selection().isBaseFirst()) std::swap(start, end); return true; } bool WebViewImpl::selectionTextDirection(WebTextDirection& start, WebTextDirection& end) const { const Frame* frame = focusedWebCoreFrame(); if (!frame) return false; FrameSelection* selection = frame->selection(); if (!selection) return false; if (!selection->toNormalizedRange()) return false; start = selection->start().primaryDirection() == RTL ? WebTextDirectionRightToLeft : WebTextDirectionLeftToRight; end = selection->end().primaryDirection() == RTL ? WebTextDirectionRightToLeft : WebTextDirectionLeftToRight; return true; } bool WebViewImpl::setEditableSelectionOffsets(int start, int end) { const Frame* focused = focusedWebCoreFrame(); if (!focused) return false; Editor* editor = focused->editor(); if (!editor || !editor->canEdit()) return false; return editor->setSelectionOffsets(start, end); } bool WebViewImpl::caretOrSelectionRange(size_t* location, size_t* length) { const Frame* focused = focusedWebCoreFrame(); if (!focused) return false; FrameSelection* selection = focused->selection(); if (!selection) return false; RefPtr range = selection->selection().firstRange(); if (!range) return false; if (TextIterator::getLocationAndLengthFromRange(selection->rootEditableElementOrDocumentElement(), range.get(), *location, *length)) return true; return false; } void WebViewImpl::setTextDirection(WebTextDirection direction) { // The Editor::setBaseWritingDirection() function checks if we can change // the text direction of the selected node and updates its DOM "dir" // attribute and its CSS "direction" property. // So, we just call the function as Safari does. const Frame* focused = focusedWebCoreFrame(); if (!focused) return; Editor* editor = focused->editor(); if (!editor || !editor->canEdit()) return; switch (direction) { case WebTextDirectionDefault: editor->setBaseWritingDirection(NaturalWritingDirection); break; case WebTextDirectionLeftToRight: editor->setBaseWritingDirection(LeftToRightWritingDirection); break; case WebTextDirectionRightToLeft: editor->setBaseWritingDirection(RightToLeftWritingDirection); break; default: notImplemented(); break; } } bool WebViewImpl::isAcceleratedCompositingActive() const { #if USE(ACCELERATED_COMPOSITING) return m_isAcceleratedCompositingActive; #else return false; #endif } void WebViewImpl::didAcquirePointerLock() { #if ENABLE(POINTER_LOCK) if (page()) page()->pointerLockController()->didAcquirePointerLock(); #endif } void WebViewImpl::didNotAcquirePointerLock() { #if ENABLE(POINTER_LOCK) if (page()) page()->pointerLockController()->didNotAcquirePointerLock(); #endif } void WebViewImpl::didLosePointerLock() { #if ENABLE(POINTER_LOCK) if (page()) page()->pointerLockController()->didLosePointerLock(); #endif } void WebViewImpl::didChangeWindowResizerRect() { if (mainFrameImpl()->frameView()) mainFrameImpl()->frameView()->windowResizerRectChanged(); } // WebView -------------------------------------------------------------------- WebSettingsImpl* WebViewImpl::settingsImpl() { if (!m_webSettings) m_webSettings = adoptPtr(new WebSettingsImpl(m_page->settings())); ASSERT(m_webSettings); return m_webSettings.get(); } WebSettings* WebViewImpl::settings() { return settingsImpl(); } WebString WebViewImpl::pageEncoding() const { if (!m_page) return WebString(); // FIXME: Is this check needed? if (!m_page->mainFrame()->document()->loader()) return WebString(); return m_page->mainFrame()->document()->encoding(); } void WebViewImpl::setPageEncoding(const WebString& encodingName) { if (!m_page) return; // Only change override encoding, don't change default encoding. // Note that the new encoding must be 0 if it isn't supposed to be set. String newEncodingName; if (!encodingName.isEmpty()) newEncodingName = encodingName; m_page->mainFrame()->loader()->reloadWithOverrideEncoding(newEncodingName); } bool WebViewImpl::dispatchBeforeUnloadEvent() { // FIXME: This should really cause a recursive depth-first walk of all // frames in the tree, calling each frame's onbeforeunload. At the moment, // we're consistent with Safari 3.1, not IE/FF. Frame* frame = m_page->mainFrame(); if (!frame) return true; return frame->loader()->shouldClose(); } void WebViewImpl::dispatchUnloadEvent() { // Run unload handlers. m_page->mainFrame()->loader()->closeURL(); } WebFrame* WebViewImpl::mainFrame() { return mainFrameImpl(); } WebFrame* WebViewImpl::findFrameByName( const WebString& name, WebFrame* relativeToFrame) { if (!relativeToFrame) relativeToFrame = mainFrame(); Frame* frame = static_cast(relativeToFrame)->frame(); frame = frame->tree()->find(name); return WebFrameImpl::fromFrame(frame); } WebFrame* WebViewImpl::focusedFrame() { return WebFrameImpl::fromFrame(focusedWebCoreFrame()); } void WebViewImpl::setFocusedFrame(WebFrame* frame) { if (!frame) { // Clears the focused frame if any. Frame* frame = focusedWebCoreFrame(); if (frame) frame->selection()->setFocused(false); return; } WebFrameImpl* frameImpl = static_cast(frame); Frame* webcoreFrame = frameImpl->frame(); webcoreFrame->page()->focusController()->setFocusedFrame(webcoreFrame); } void WebViewImpl::setInitialFocus(bool reverse) { if (!m_page) return; // Since we don't have a keyboard event, we'll create one. WebKeyboardEvent keyboardEvent; keyboardEvent.type = WebInputEvent::RawKeyDown; if (reverse) keyboardEvent.modifiers = WebInputEvent::ShiftKey; // VK_TAB which is only defined on Windows. keyboardEvent.windowsKeyCode = 0x09; PlatformKeyboardEventBuilder platformEvent(keyboardEvent); RefPtr webkitEvent = KeyboardEvent::create(platformEvent, 0); Frame* frame = page()->focusController()->focusedOrMainFrame(); if (Document* document = frame->document()) document->setFocusedNode(0); page()->focusController()->setInitialFocus( reverse ? FocusDirectionBackward : FocusDirectionForward, webkitEvent.get()); } void WebViewImpl::clearFocusedNode() { RefPtr frame = focusedWebCoreFrame(); if (!frame) return; RefPtr document = frame->document(); if (!document) return; RefPtr oldFocusedNode = document->focusedNode(); // Clear the focused node. document->setFocusedNode(0); if (!oldFocusedNode) return; // If a text field has focus, we need to make sure the selection controller // knows to remove selection from it. Otherwise, the text field is still // processing keyboard events even though focus has been moved to the page and // keystrokes get eaten as a result. if (oldFocusedNode->isContentEditable() || (oldFocusedNode->isElementNode() && static_cast(oldFocusedNode.get())->isTextFormControl())) { frame->selection()->clear(); } } void WebViewImpl::scrollFocusedNodeIntoView() { Node* focusedNode = focusedWebCoreNode(); if (focusedNode && focusedNode->isElementNode()) { Element* elementNode = static_cast(focusedNode); elementNode->scrollIntoViewIfNeeded(true); } } void WebViewImpl::scrollFocusedNodeIntoRect(const WebRect& rect) { Frame* frame = page()->mainFrame(); Node* focusedNode = focusedWebCoreNode(); if (!frame || !frame->view() || !focusedNode || !focusedNode->isElementNode()) return; Element* elementNode = static_cast(focusedNode); frame->view()->scrollElementToRect(elementNode, IntRect(rect.x, rect.y, rect.width, rect.height)); } void WebViewImpl::advanceFocus(bool reverse) { page()->focusController()->advanceFocus(reverse ? FocusDirectionBackward : FocusDirectionForward, 0); } double WebViewImpl::zoomLevel() { return m_zoomLevel; } double WebViewImpl::setZoomLevel(bool textOnly, double zoomLevel) { if (zoomLevel < m_minimumZoomLevel) m_zoomLevel = m_minimumZoomLevel; else if (zoomLevel > m_maximumZoomLevel) m_zoomLevel = m_maximumZoomLevel; else m_zoomLevel = zoomLevel; Frame* frame = mainFrameImpl()->frame(); WebPluginContainerImpl* pluginContainer = WebFrameImpl::pluginContainerFromFrame(frame); if (pluginContainer) pluginContainer->plugin()->setZoomLevel(m_zoomLevel, textOnly); else { float zoomFactor = static_cast(zoomLevelToZoomFactor(m_zoomLevel)); if (textOnly) frame->setPageAndTextZoomFactors(1, zoomFactor * m_emulatedTextZoomFactor); else frame->setPageAndTextZoomFactors(zoomFactor, m_emulatedTextZoomFactor); } return m_zoomLevel; } void WebViewImpl::zoomLimitsChanged(double minimumZoomLevel, double maximumZoomLevel) { m_minimumZoomLevel = minimumZoomLevel; m_maximumZoomLevel = maximumZoomLevel; m_client->zoomLimitsChanged(m_minimumZoomLevel, m_maximumZoomLevel); } void WebViewImpl::fullFramePluginZoomLevelChanged(double zoomLevel) { if (zoomLevel == m_zoomLevel) return; m_zoomLevel = max(min(zoomLevel, m_maximumZoomLevel), m_minimumZoomLevel); m_client->zoomLevelChanged(); } double WebView::zoomLevelToZoomFactor(double zoomLevel) { return pow(textSizeMultiplierRatio, zoomLevel); } double WebView::zoomFactorToZoomLevel(double factor) { // Since factor = 1.2^level, level = log(factor) / log(1.2) return log(factor) / log(textSizeMultiplierRatio); } float WebViewImpl::pageScaleFactor() const { if (!page()) return 1; return page()->pageScaleFactor(); } bool WebViewImpl::isPageScaleFactorSet() const { return m_pageScaleFactorIsSet; } float WebViewImpl::clampPageScaleFactorToLimits(float scaleFactor) { return min(max(scaleFactor, m_minimumPageScaleFactor), m_maximumPageScaleFactor); } WebPoint WebViewImpl::clampOffsetAtScale(const WebPoint& offset, float scale) { // This is the scaled content size. We need to convert it to the new scale factor. WebSize contentSize = mainFrame()->contentsSize(); float deltaScale = scale / pageScaleFactor(); int docWidthAtNewScale = contentSize.width * deltaScale; int docHeightAtNewScale = contentSize.height * deltaScale; int viewWidth = m_size.width; int viewHeight = m_size.height; // Enforce the maximum and minimum scroll positions at the new scale. IntPoint clampedOffset = offset; clampedOffset = clampedOffset.shrunkTo(IntPoint(docWidthAtNewScale - viewWidth, docHeightAtNewScale - viewHeight)); clampedOffset.clampNegativeToZero(); return clampedOffset; } void WebViewImpl::setPageScaleFactorPreservingScrollOffset(float scaleFactor) { // Pick a scale factor that is within the expected limits scaleFactor = clampPageScaleFactorToLimits(scaleFactor); IntPoint scrollOffsetAtNewScale(mainFrame()->scrollOffset().width, mainFrame()->scrollOffset().height); float deltaScale = scaleFactor / pageScaleFactor(); scrollOffsetAtNewScale.scale(deltaScale, deltaScale); WebPoint clampedOffsetAtNewScale = clampOffsetAtScale(scrollOffsetAtNewScale, scaleFactor); setPageScaleFactor(scaleFactor, clampedOffsetAtNewScale); } void WebViewImpl::setPageScaleFactor(float scaleFactor, const WebPoint& origin) { if (!page()) return; if (!scaleFactor) scaleFactor = 1; if (m_deviceScaleInCompositor != 1) { // Don't allow page scaling when compositor scaling is being used, // as they are currently incompatible. ASSERT(scaleFactor == 1); } scaleFactor = clampPageScaleFactorToLimits(scaleFactor); WebPoint clampedOrigin = clampOffsetAtScale(origin, scaleFactor); page()->setPageScaleFactor(scaleFactor, clampedOrigin); m_pageScaleFactorIsSet = true; } float WebViewImpl::deviceScaleFactor() const { if (!page()) return 1; return page()->deviceScaleFactor(); } void WebViewImpl::setDeviceScaleFactor(float scaleFactor) { if (!page()) return; page()->setDeviceScaleFactor(scaleFactor); if (m_deviceScaleInCompositor != 1) { // Don't allow page scaling when compositor scaling is being used, // as they are currently incompatible. This means the deviceScale // needs to match the one in the compositor. ASSERT(scaleFactor == m_deviceScaleInCompositor); } if (!m_layerTreeView.isNull() && m_webSettings->applyDefaultDeviceScaleFactorInCompositor()) { m_deviceScaleInCompositor = page()->deviceScaleFactor(); m_layerTreeView.setDeviceScaleFactor(m_deviceScaleInCompositor); } } bool WebViewImpl::isFixedLayoutModeEnabled() const { if (!page()) return false; Frame* frame = page()->mainFrame(); if (!frame || !frame->view()) return false; return frame->view()->useFixedLayout(); } void WebViewImpl::enableFixedLayoutMode(bool enable) { if (!page()) return; Frame* frame = page()->mainFrame(); if (!frame || !frame->view()) return; frame->view()->setUseFixedLayout(enable); #if USE(ACCELERATED_COMPOSITING) // Also notify the base layer, which RenderLayerCompositor does not see. if (m_nonCompositedContentHost) updateLayerTreeViewport(); #endif } void WebViewImpl::enableAutoResizeMode(const WebSize& minSize, const WebSize& maxSize) { m_shouldAutoResize = true; m_minAutoSize = minSize; m_maxAutoSize = maxSize; configureAutoResizeMode(); } void WebViewImpl::disableAutoResizeMode() { m_shouldAutoResize = false; configureAutoResizeMode(); } void WebViewImpl::setPageScaleFactorLimits(float minPageScale, float maxPageScale) { m_pageDefinedMinimumPageScaleFactor = minPageScale; m_pageDefinedMaximumPageScaleFactor = maxPageScale; computePageScaleFactorLimits(); } void WebViewImpl::setIgnoreViewportTagMaximumScale(bool flag) { m_ignoreViewportTagMaximumScale = flag; if (!page() || !page()->mainFrame()) return; m_page->chrome()->client()->dispatchViewportPropertiesDidChange(page()->mainFrame()->document()->viewportArguments()); } bool WebViewImpl::computePageScaleFactorLimits() { if (m_pageDefinedMinimumPageScaleFactor == -1 || m_pageDefinedMaximumPageScaleFactor == -1) return false; if (!mainFrame() || !page() || !page()->mainFrame() || !page()->mainFrame()->view()) return false; m_minimumPageScaleFactor = min(max(m_pageDefinedMinimumPageScaleFactor, minPageScaleFactor), maxPageScaleFactor) * (deviceScaleFactor() / m_deviceScaleInCompositor); m_maximumPageScaleFactor = max(min(m_pageDefinedMaximumPageScaleFactor, maxPageScaleFactor), minPageScaleFactor) * (deviceScaleFactor() / m_deviceScaleInCompositor); int viewWidthNotIncludingScrollbars = page()->mainFrame()->view()->visibleContentRect(false).width(); int contentsWidth = mainFrame()->contentsSize().width; if (viewWidthNotIncludingScrollbars && contentsWidth) { // Limit page scaling down to the document width. int unscaledContentWidth = contentsWidth / pageScaleFactor(); m_minimumPageScaleFactor = max(m_minimumPageScaleFactor, static_cast(viewWidthNotIncludingScrollbars) / unscaledContentWidth); m_maximumPageScaleFactor = max(m_minimumPageScaleFactor, m_maximumPageScaleFactor); } ASSERT(m_minimumPageScaleFactor <= m_maximumPageScaleFactor); float clampedScale = clampPageScaleFactorToLimits(pageScaleFactor()); #if USE(ACCELERATED_COMPOSITING) if (!m_layerTreeView.isNull()) m_layerTreeView.setPageScaleFactorAndLimits(clampedScale, m_minimumPageScaleFactor, m_maximumPageScaleFactor); #endif if (clampedScale != pageScaleFactor()) { setPageScaleFactorPreservingScrollOffset(clampedScale); return true; } return false; } float WebViewImpl::minimumPageScaleFactor() const { return m_minimumPageScaleFactor; } float WebViewImpl::maximumPageScaleFactor() const { return m_maximumPageScaleFactor; } WebSize WebViewImpl::fixedLayoutSize() const { if (!page()) return WebSize(); Frame* frame = page()->mainFrame(); if (!frame || !frame->view()) return WebSize(); return frame->view()->fixedLayoutSize(); } void WebViewImpl::setFixedLayoutSize(const WebSize& layoutSize) { if (!page()) return; Frame* frame = page()->mainFrame(); if (!frame || !frame->view()) return; frame->view()->setFixedLayoutSize(layoutSize); } void WebViewImpl::performMediaPlayerAction(const WebMediaPlayerAction& action, const WebPoint& location) { HitTestResult result = hitTestResultForWindowPos(location); RefPtr node = result.innerNonSharedNode(); if (!node->hasTagName(HTMLNames::videoTag) && !node->hasTagName(HTMLNames::audioTag)) return; RefPtr mediaElement = static_pointer_cast(node); switch (action.type) { case WebMediaPlayerAction::Play: if (action.enable) mediaElement->play(); else mediaElement->pause(); break; case WebMediaPlayerAction::Mute: mediaElement->setMuted(action.enable); break; case WebMediaPlayerAction::Loop: mediaElement->setLoop(action.enable); break; case WebMediaPlayerAction::Controls: mediaElement->setControls(action.enable); break; default: ASSERT_NOT_REACHED(); } } void WebViewImpl::performPluginAction(const WebPluginAction& action, const WebPoint& location) { HitTestResult result = hitTestResultForWindowPos(location); RefPtr node = result.innerNonSharedNode(); if (!node->hasTagName(HTMLNames::objectTag) && !node->hasTagName(HTMLNames::embedTag)) return; RenderObject* object = node->renderer(); if (object && object->isWidget()) { Widget* widget = toRenderWidget(object)->widget(); if (widget && widget->isPluginContainer()) { WebPluginContainerImpl* plugin = static_cast(widget); switch (action.type) { case WebPluginAction::Rotate90Clockwise: plugin->plugin()->rotateView(WebPlugin::RotationType90Clockwise); break; case WebPluginAction::Rotate90Counterclockwise: plugin->plugin()->rotateView(WebPlugin::RotationType90Counterclockwise); break; default: ASSERT_NOT_REACHED(); } } } } void WebViewImpl::copyImageAt(const WebPoint& point) { if (!m_page) return; HitTestResult result = hitTestResultForWindowPos(point); if (result.absoluteImageURL().isEmpty()) { // There isn't actually an image at these coordinates. Might be because // the window scrolled while the context menu was open or because the page // changed itself between when we thought there was an image here and when // we actually tried to retreive the image. // // FIXME: implement a cache of the most recent HitTestResult to avoid having // to do two hit tests. return; } m_page->mainFrame()->editor()->copyImage(result); } void WebViewImpl::dragSourceEndedAt( const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperation operation) { PlatformMouseEvent pme(clientPoint, screenPoint, LeftButton, PlatformEvent::MouseMoved, 0, false, false, false, false, 0); m_page->mainFrame()->eventHandler()->dragSourceEndedAt(pme, static_cast(operation)); m_dragScrollTimer->stop(); } void WebViewImpl::dragSourceMovedTo( const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperation operation) { m_dragScrollTimer->triggerScroll(mainFrameImpl()->frameView(), clientPoint); } void WebViewImpl::dragSourceSystemDragEnded() { // It's possible for us to get this callback while not doing a drag if // it's from a previous page that got unloaded. if (m_doingDragAndDrop) { m_page->dragController()->dragEnded(); m_doingDragAndDrop = false; } } WebDragOperation WebViewImpl::dragTargetDragEnter( const WebDragData& webDragData, const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperationsMask operationsAllowed) { return dragTargetDragEnter(webDragData, clientPoint, screenPoint, operationsAllowed, 0); } WebDragOperation WebViewImpl::dragTargetDragEnter( const WebDragData& webDragData, const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperationsMask operationsAllowed, int keyModifiers) { ASSERT(!m_currentDragData); m_currentDragData = webDragData; m_operationsAllowed = operationsAllowed; return dragTargetDragEnterOrOver(clientPoint, screenPoint, DragEnter, keyModifiers); } WebDragOperation WebViewImpl::dragTargetDragOver( const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperationsMask operationsAllowed) { return dragTargetDragOver(clientPoint, screenPoint, operationsAllowed, 0); } WebDragOperation WebViewImpl::dragTargetDragOver( const WebPoint& clientPoint, const WebPoint& screenPoint, WebDragOperationsMask operationsAllowed, int keyModifiers) { m_operationsAllowed = operationsAllowed; return dragTargetDragEnterOrOver(clientPoint, screenPoint, DragOver, keyModifiers); } void WebViewImpl::dragTargetDragLeave() { ASSERT(m_currentDragData); DragData dragData( m_currentDragData.get(), IntPoint(), IntPoint(), static_cast(m_operationsAllowed)); m_page->dragController()->dragExited(&dragData); // FIXME: why is the drag scroll timer not stopped here? m_dragOperation = WebDragOperationNone; m_currentDragData = 0; } void WebViewImpl::dragTargetDrop(const WebPoint& clientPoint, const WebPoint& screenPoint) { dragTargetDrop(clientPoint, screenPoint, 0); } void WebViewImpl::dragTargetDrop(const WebPoint& clientPoint, const WebPoint& screenPoint, int keyModifiers) { ASSERT(m_currentDragData); // If this webview transitions from the "drop accepting" state to the "not // accepting" state, then our IPC message reply indicating that may be in- // flight, or else delayed by javascript processing in this webview. If a // drop happens before our IPC reply has reached the browser process, then // the browser forwards the drop to this webview. So only allow a drop to // proceed if our webview m_dragOperation state is not DragOperationNone. if (m_dragOperation == WebDragOperationNone) { // IPC RACE CONDITION: do not allow this drop. dragTargetDragLeave(); return; } m_currentDragData->setModifierKeyState(webInputEventKeyStateToPlatformEventKeyState(keyModifiers)); DragData dragData( m_currentDragData.get(), clientPoint, screenPoint, static_cast(m_operationsAllowed)); m_page->dragController()->performDrag(&dragData); m_dragOperation = WebDragOperationNone; m_currentDragData = 0; m_dragScrollTimer->stop(); } WebDragOperation WebViewImpl::dragTargetDragEnterOrOver(const WebPoint& clientPoint, const WebPoint& screenPoint, DragAction dragAction, int keyModifiers) { ASSERT(m_currentDragData); m_currentDragData->setModifierKeyState(webInputEventKeyStateToPlatformEventKeyState(keyModifiers)); DragData dragData( m_currentDragData.get(), clientPoint, screenPoint, static_cast(m_operationsAllowed)); DragSession dragSession; if (dragAction == DragEnter) dragSession = m_page->dragController()->dragEntered(&dragData); else dragSession = m_page->dragController()->dragUpdated(&dragData); DragOperation dropEffect = dragSession.operation; // Mask the drop effect operation against the drag source's allowed operations. if (!(dropEffect & dragData.draggingSourceOperationMask())) dropEffect = DragOperationNone; m_dragOperation = static_cast(dropEffect); if (dragAction == DragOver) m_dragScrollTimer->triggerScroll(mainFrameImpl()->frameView(), clientPoint); else m_dragScrollTimer->stop(); return m_dragOperation; } void WebViewImpl::sendResizeEventAndRepaint() { if (mainFrameImpl()->frameView()) { // Enqueues the resize event. mainFrameImpl()->frame()->eventHandler()->sendResizeEvent(); } if (m_client) { if (isAcceleratedCompositingActive()) { #if USE(ACCELERATED_COMPOSITING) updateLayerTreeViewport(); #endif } else { WebRect damagedRect(0, 0, m_size.width, m_size.height); m_client->didInvalidateRect(damagedRect); } } } void WebViewImpl::configureAutoResizeMode() { if (!mainFrameImpl() || !mainFrameImpl()->frame() || !mainFrameImpl()->frame()->view()) return; mainFrameImpl()->frame()->view()->enableAutoSizeMode(m_shouldAutoResize, m_minAutoSize, m_maxAutoSize); } unsigned long WebViewImpl::createUniqueIdentifierForRequest() { if (m_page) return m_page->progress()->createUniqueIdentifier(); return 0; } void WebViewImpl::inspectElementAt(const WebPoint& point) { if (!m_page) return; if (point.x == -1 || point.y == -1) m_page->inspectorController()->inspect(0); else { HitTestResult result = hitTestResultForWindowPos(point); if (!result.innerNonSharedNode()) return; m_page->inspectorController()->inspect(result.innerNonSharedNode()); } } WebString WebViewImpl::inspectorSettings() const { return m_inspectorSettings; } void WebViewImpl::setInspectorSettings(const WebString& settings) { m_inspectorSettings = settings; } bool WebViewImpl::inspectorSetting(const WebString& key, WebString* value) const { if (!m_inspectorSettingsMap->contains(key)) return false; *value = m_inspectorSettingsMap->get(key); return true; } void WebViewImpl::setInspectorSetting(const WebString& key, const WebString& value) { m_inspectorSettingsMap->set(key, value); client()->didUpdateInspectorSetting(key, value); } WebDevToolsAgent* WebViewImpl::devToolsAgent() { return m_devToolsAgent.get(); } WebAccessibilityObject WebViewImpl::accessibilityObject() { if (!mainFrameImpl()) return WebAccessibilityObject(); Document* document = mainFrameImpl()->frame()->document(); return WebAccessibilityObject( document->axObjectCache()->getOrCreate(document->renderer())); } void WebViewImpl::applyAutofillSuggestions( const WebNode& node, const WebVector& names, const WebVector& labels, const WebVector& icons, const WebVector& itemIDs, int separatorIndex) { ASSERT(names.size() == labels.size()); ASSERT(names.size() == itemIDs.size()); if (names.isEmpty()) { hideAutofillPopup(); return; } RefPtr focusedNode = focusedWebCoreNode(); // If the node for which we queried the Autofill suggestions is not the // focused node, then we have nothing to do. FIXME: also check the // caret is at the end and that the text has not changed. if (!focusedNode || focusedNode != PassRefPtr(node)) { hideAutofillPopup(); return; } HTMLInputElement* inputElem = focusedNode->toInputElement(); ASSERT(inputElem); // The first time the Autofill popup is shown we'll create the client and // the popup. if (!m_autofillPopupClient) m_autofillPopupClient = adoptPtr(new AutofillPopupMenuClient); m_autofillPopupClient->initialize( inputElem, names, labels, icons, itemIDs, separatorIndex); if (!m_autofillPopup) { PopupContainerSettings popupSettings = autofillPopupSettings; popupSettings.deviceSupportsTouch = settingsImpl()->deviceSupportsTouch(); m_autofillPopup = PopupContainer::create(m_autofillPopupClient.get(), PopupContainer::Suggestion, popupSettings); } if (m_autofillPopupShowing) { refreshAutofillPopup(); } else { m_autofillPopupShowing = true; m_autofillPopup->showInRect(focusedNode->getPixelSnappedRect(), focusedNode->ownerDocument()->view(), 0); } } void WebViewImpl::hidePopups() { hideSelectPopup(); hideAutofillPopup(); #if ENABLE(PAGE_POPUP) if (m_pagePopup) closePagePopup(m_pagePopup.get()); #endif } void WebViewImpl::performCustomContextMenuAction(unsigned action) { if (!m_page) return; ContextMenu* menu = m_page->contextMenuController()->contextMenu(); if (!menu) return; ContextMenuItem* item = menu->itemWithAction(static_cast(ContextMenuItemBaseCustomTag + action)); if (item) m_page->contextMenuController()->contextMenuItemSelected(item); m_page->contextMenuController()->clearContextMenu(); } // WebView -------------------------------------------------------------------- void WebViewImpl::setIsTransparent(bool isTransparent) { // Set any existing frames to be transparent. Frame* frame = m_page->mainFrame(); while (frame) { frame->view()->setTransparent(isTransparent); frame = frame->tree()->traverseNext(); } // Future frames check this to know whether to be transparent. m_isTransparent = isTransparent; if (m_nonCompositedContentHost) m_nonCompositedContentHost->setOpaque(!isTransparent); if (!m_layerTreeView.isNull()) m_layerTreeView.setHasTransparentBackground(isTransparent); } bool WebViewImpl::isTransparent() const { return m_isTransparent; } void WebViewImpl::setIsActive(bool active) { if (page() && page()->focusController()) page()->focusController()->setActive(active); } bool WebViewImpl::isActive() const { return (page() && page()->focusController()) ? page()->focusController()->isActive() : false; } void WebViewImpl::setDomainRelaxationForbidden(bool forbidden, const WebString& scheme) { SchemeRegistry::setDomainRelaxationForbiddenForURLScheme(forbidden, String(scheme)); } void WebViewImpl::setScrollbarColors(unsigned inactiveColor, unsigned activeColor, unsigned trackColor) { #if OS(UNIX) && !OS(DARWIN) && !OS(ANDROID) PlatformThemeChromiumLinux::setScrollbarColors(inactiveColor, activeColor, trackColor); #endif } void WebViewImpl::setSelectionColors(unsigned activeBackgroundColor, unsigned activeForegroundColor, unsigned inactiveBackgroundColor, unsigned inactiveForegroundColor) { #if OS(UNIX) && !OS(DARWIN) && !OS(ANDROID) RenderThemeChromiumLinux::setSelectionColors(activeBackgroundColor, activeForegroundColor, inactiveBackgroundColor, inactiveForegroundColor); theme()->platformColorsDidChange(); #endif } void WebView::addUserScript(const WebString& sourceCode, const WebVector& patternsIn, WebView::UserScriptInjectAt injectAt, WebView::UserContentInjectIn injectIn) { OwnPtr > patterns = adoptPtr(new Vector); for (size_t i = 0; i < patternsIn.size(); ++i) patterns->append(patternsIn[i]); PageGroup* pageGroup = PageGroup::pageGroup(pageGroupName); RefPtr world(DOMWrapperWorld::create()); pageGroup->addUserScriptToWorld(world.get(), sourceCode, WebURL(), patterns.release(), nullptr, static_cast(injectAt), static_cast(injectIn)); } void WebView::addUserStyleSheet(const WebString& sourceCode, const WebVector& patternsIn, WebView::UserContentInjectIn injectIn, WebView::UserStyleInjectionTime injectionTime) { OwnPtr > patterns = adoptPtr(new Vector); for (size_t i = 0; i < patternsIn.size(); ++i) patterns->append(patternsIn[i]); PageGroup* pageGroup = PageGroup::pageGroup(pageGroupName); RefPtr world(DOMWrapperWorld::create()); // FIXME: Current callers always want the level to be "author". It probably makes sense to let // callers specify this though, since in other cases the caller will probably want "user" level. // // FIXME: It would be nice to populate the URL correctly, instead of passing an empty URL. pageGroup->addUserStyleSheetToWorld(world.get(), sourceCode, WebURL(), patterns.release(), nullptr, static_cast(injectIn), UserStyleAuthorLevel, static_cast(injectionTime)); } void WebView::removeAllUserContent() { PageGroup* pageGroup = PageGroup::pageGroup(pageGroupName); pageGroup->removeAllUserContent(); } void WebViewImpl::didCommitLoad(bool* isNewNavigation, bool isNavigationWithinPage) { if (isNewNavigation) *isNewNavigation = m_observedNewNavigation; #ifndef NDEBUG ASSERT(!m_observedNewNavigation || m_page->mainFrame()->loader()->documentLoader() == m_newNavigationLoader); m_newNavigationLoader = 0; #endif m_observedNewNavigation = false; if (*isNewNavigation && !isNavigationWithinPage) m_pageScaleFactorIsSet = false; m_gestureAnimation.clear(); } void WebViewImpl::layoutUpdated(WebFrameImpl* webframe) { if (!m_client || webframe != mainFrameImpl()) return; if (m_shouldAutoResize && mainFrameImpl()->frame() && mainFrameImpl()->frame()->view()) { WebSize frameSize = mainFrameImpl()->frame()->view()->frameRect().size(); if (frameSize != m_size) { m_size = frameSize; m_client->didAutoResize(m_size); sendResizeEventAndRepaint(); } } m_client->didUpdateLayout(); } void WebViewImpl::didChangeContentsSize() { #if ENABLE(VIEWPORT) if (!settings()->viewportEnabled()) return; bool didChangeScale = false; if (!isPageScaleFactorSet()) { // If the viewport tag failed to be processed earlier, we need // to recompute it now. ViewportArguments viewportArguments = mainFrameImpl()->frame()->document()->viewportArguments(); m_page->chrome()->client()->dispatchViewportPropertiesDidChange(viewportArguments); didChangeScale = true; } else didChangeScale = computePageScaleFactorLimits(); if (!didChangeScale) return; if (!mainFrameImpl()) return; FrameView* view = mainFrameImpl()->frameView(); if (view && view->needsLayout()) view->layout(); #endif } bool WebViewImpl::useExternalPopupMenus() { return shouldUseExternalPopupMenus; } void WebViewImpl::setEmulatedTextZoomFactor(float textZoomFactor) { m_emulatedTextZoomFactor = textZoomFactor; Frame* frame = mainFrameImpl()->frame(); if (frame) frame->setPageAndTextZoomFactors(frame->pageZoomFactor(), m_emulatedTextZoomFactor); } bool WebViewImpl::navigationPolicyFromMouseEvent(unsigned short button, bool ctrl, bool shift, bool alt, bool meta, WebNavigationPolicy* policy) { #if OS(DARWIN) const bool newTabModifier = (button == 1) || meta; #else const bool newTabModifier = (button == 1) || ctrl; #endif if (!newTabModifier && !shift && !alt) return false; ASSERT(policy); if (newTabModifier) { if (shift) *policy = WebNavigationPolicyNewForegroundTab; else *policy = WebNavigationPolicyNewBackgroundTab; } else { if (shift) *policy = WebNavigationPolicyNewWindow; else *policy = WebNavigationPolicyDownload; } return true; } void WebViewImpl::startDragging(Frame* frame, const WebDragData& dragData, WebDragOperationsMask mask, const WebImage& dragImage, const WebPoint& dragImageOffset) { if (!m_client) return; ASSERT(!m_doingDragAndDrop); m_doingDragAndDrop = true; m_client->startDragging(WebFrameImpl::fromFrame(frame), dragData, mask, dragImage, dragImageOffset); } void WebViewImpl::observeNewNavigation() { m_observedNewNavigation = true; #ifndef NDEBUG m_newNavigationLoader = m_page->mainFrame()->loader()->documentLoader(); #endif } void WebViewImpl::setIgnoreInputEvents(bool newValue) { ASSERT(m_ignoreInputEvents != newValue); m_ignoreInputEvents = newValue; } void WebViewImpl::addPageOverlay(WebPageOverlay* overlay, int zOrder) { if (!m_pageOverlays) m_pageOverlays = PageOverlayList::create(this); m_pageOverlays->add(overlay, zOrder); } void WebViewImpl::removePageOverlay(WebPageOverlay* overlay) { if (m_pageOverlays && m_pageOverlays->remove(overlay) && m_pageOverlays->empty()) m_pageOverlays = nullptr; } void WebViewImpl::setOverlayLayer(WebCore::GraphicsLayer* layer) { if (m_rootGraphicsLayer) { if (layer->parent() != m_rootGraphicsLayer) m_rootGraphicsLayer->addChild(layer); } } #if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS) NotificationPresenterImpl* WebViewImpl::notificationPresenterImpl() { if (!m_notificationPresenter.isInitialized() && m_client) m_notificationPresenter.initialize(m_client->notificationPresenter()); return &m_notificationPresenter; } #endif void WebViewImpl::refreshAutofillPopup() { ASSERT(m_autofillPopupShowing); // Hide the popup if it has become empty. if (!m_autofillPopupClient->listSize()) { hideAutofillPopup(); return; } WebRect newWidgetRect = m_autofillPopup->refresh(focusedWebCoreNode()->getPixelSnappedRect()); // Let's resize the backing window if necessary. WebPopupMenuImpl* popupMenu = static_cast(m_autofillPopup->client()); if (popupMenu && popupMenu->client()->windowRect() != newWidgetRect) popupMenu->client()->setWindowRect(newWidgetRect); } Node* WebViewImpl::focusedWebCoreNode() { Frame* frame = m_page->focusController()->focusedFrame(); if (!frame) return 0; Document* document = frame->document(); if (!document) return 0; return document->focusedNode(); } HitTestResult WebViewImpl::hitTestResultForWindowPos(const IntPoint& pos) { IntPoint docPoint(m_page->mainFrame()->view()->windowToContents(pos)); return m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(docPoint, false); } void WebViewImpl::setTabsToLinks(bool enable) { m_tabsToLinks = enable; } bool WebViewImpl::tabsToLinks() const { return m_tabsToLinks; } #if USE(ACCELERATED_COMPOSITING) bool WebViewImpl::allowsAcceleratedCompositing() { return !m_compositorCreationFailed; } void WebViewImpl::setRootGraphicsLayer(GraphicsLayer* layer) { m_rootGraphicsLayer = layer; setIsAcceleratedCompositingActive(layer); if (m_nonCompositedContentHost) { GraphicsLayer* scrollLayer = 0; if (layer) { Document* document = page()->mainFrame()->document(); RenderView* renderView = document->renderView(); RenderLayerCompositor* compositor = renderView->compositor(); scrollLayer = compositor->scrollLayer(); } m_nonCompositedContentHost->setScrollLayer(scrollLayer); } if (layer) m_rootLayer = WebLayer(layer->platformLayer()); if (!m_layerTreeView.isNull()) m_layerTreeView.setRootLayer(layer ? &m_rootLayer : 0); IntRect damagedRect(0, 0, m_size.width, m_size.height); if (!m_isAcceleratedCompositingActive) m_client->didInvalidateRect(damagedRect); } void WebViewImpl::scheduleCompositingLayerSync() { m_layerTreeView.setNeedsRedraw(); } void WebViewImpl::scrollRootLayerRect(const IntSize&, const IntRect&) { updateLayerTreeViewport(); } void WebViewImpl::invalidateRootLayerRect(const IntRect& rect) { ASSERT(!m_layerTreeView.isNull()); if (!page()) return; FrameView* view = page()->mainFrame()->view(); IntRect dirtyRect = view->windowToContents(rect); updateLayerTreeViewport(); m_nonCompositedContentHost->invalidateRect(dirtyRect); } NonCompositedContentHost* WebViewImpl::nonCompositedContentHost() { return m_nonCompositedContentHost.get(); } void WebViewImpl::setBackgroundColor(const WebCore::Color& color) { WebCore::Color documentBackgroundColor = color.isValid() ? color : WebCore::Color::white; WebColor webDocumentBackgroundColor = documentBackgroundColor.rgb(); m_nonCompositedContentHost->setBackgroundColor(documentBackgroundColor); m_layerTreeView.setBackgroundColor(webDocumentBackgroundColor); } #if ENABLE(REQUEST_ANIMATION_FRAME) void WebViewImpl::scheduleAnimation() { if (isAcceleratedCompositingActive()) { if (CCProxy::hasImplThread()) { ASSERT(!m_layerTreeView.isNull()); m_layerTreeView.setNeedsAnimate(); } else m_client->scheduleAnimation(); } else m_client->scheduleAnimation(); } #endif void WebViewImpl::paintRootLayer(GraphicsContext& context, const IntRect& contentRect) { double paintStart = currentTime(); if (!page()) return; FrameView* view = page()->mainFrame()->view(); view->paintContents(&context, contentRect); double paintEnd = currentTime(); double pixelsPerSec = (contentRect.width() * contentRect.height()) / (paintEnd - paintStart); WebKit::Platform::current()->histogramCustomCounts("Renderer4.AccelRootPaintDurationMS", (paintEnd - paintStart) * 1000, 0, 120, 30); WebKit::Platform::current()->histogramCustomCounts("Renderer4.AccelRootPaintMegapixPerSecond", pixelsPerSec / 1000000, 10, 210, 30); setBackgroundColor(view->documentBackgroundColor()); } void WebViewImpl::setIsAcceleratedCompositingActive(bool active) { WebKit::Platform::current()->histogramEnumeration("GPU.setIsAcceleratedCompositingActive", active * 2 + m_isAcceleratedCompositingActive, 4); if (m_isAcceleratedCompositingActive == active) return; if (!active) { m_isAcceleratedCompositingActive = false; // We need to finish all GL rendering before sending didDeactivateCompositor() to prevent // flickering when compositing turns off. if (!m_layerTreeView.isNull()) m_layerTreeView.finishAllRendering(); m_client->didDeactivateCompositor(); } else if (!m_layerTreeView.isNull()) { m_isAcceleratedCompositingActive = true; updateLayerTreeViewport(); m_client->didActivateCompositor(m_layerTreeView.compositorIdentifier()); } else { TRACE_EVENT("WebViewImpl::setIsAcceleratedCompositingActive(true)", this, 0); WebLayerTreeView::Settings layerTreeViewSettings; layerTreeViewSettings.acceleratePainting = page()->settings()->acceleratedDrawingEnabled(); layerTreeViewSettings.showFPSCounter = settingsImpl()->showFPSCounter(); layerTreeViewSettings.showPlatformLayerTree = settingsImpl()->showPlatformLayerTree(); layerTreeViewSettings.showPaintRects = settingsImpl()->showPaintRects(); layerTreeViewSettings.forceSoftwareCompositing = settings()->forceSoftwareCompositing(); layerTreeViewSettings.defaultTileSize = settingsImpl()->defaultTileSize(); layerTreeViewSettings.maxUntiledLayerSize = settingsImpl()->maxUntiledLayerSize(); m_nonCompositedContentHost = NonCompositedContentHost::create(this); m_nonCompositedContentHost->setShowDebugBorders(page()->settings()->showDebugBorders()); m_nonCompositedContentHost->setOpaque(!isTransparent()); m_layerTreeView.initialize(this, m_rootLayer, layerTreeViewSettings); if (!m_layerTreeView.isNull()) { if (m_webSettings->applyDefaultDeviceScaleFactorInCompositor() && page()->deviceScaleFactor() != 1) { ASSERT(page()->deviceScaleFactor()); m_deviceScaleInCompositor = page()->deviceScaleFactor(); setDeviceScaleFactor(m_deviceScaleInCompositor); } bool visible = page()->visibilityState() == PageVisibilityStateVisible; m_layerTreeView.setVisible(visible); m_layerTreeView.setPageScaleFactorAndLimits(pageScaleFactor(), m_minimumPageScaleFactor, m_maximumPageScaleFactor); if (m_compositorSurfaceReady) m_layerTreeView.setSurfaceReady(); m_layerTreeView.setHasTransparentBackground(isTransparent()); updateLayerTreeViewport(); m_client->didActivateCompositor(m_layerTreeView.compositorIdentifier()); m_isAcceleratedCompositingActive = true; m_compositorCreationFailed = false; if (m_pageOverlays) m_pageOverlays->update(); } else { m_nonCompositedContentHost.clear(); m_isAcceleratedCompositingActive = false; m_client->didDeactivateCompositor(); m_compositorCreationFailed = true; } } if (page()) page()->mainFrame()->view()->setClipsRepaints(!m_isAcceleratedCompositingActive); } #endif PassOwnPtr WebViewImpl::createCompositorGraphicsContext3D() { if (settings()->forceSoftwareCompositing()) CRASH(); // Explicitly disable antialiasing for the compositor. As of the time of // this writing, the only platform that supported antialiasing for the // compositor was Mac OS X, because the on-screen OpenGL context creation // code paths on Windows and Linux didn't yet have multisampling support. // Mac OS X essentially always behaves as though it's rendering offscreen. // Multisampling has a heavy cost especially on devices with relatively low // fill rate like most notebooks, and the Mac implementation would need to // be optimized to resolve directly into the IOSurface shared between the // GPU and browser processes. For these reasons and to avoid platform // disparities we explicitly disable antialiasing. WebKit::WebGraphicsContext3D::Attributes attributes; attributes.antialias = false; attributes.shareResources = true; OwnPtr webContext = adoptPtr(client()->createGraphicsContext3D(attributes)); if (!webContext) return nullptr; return webContext.release(); } WebKit::WebGraphicsContext3D* WebViewImpl::createContext3D() { if (settings()->forceSoftwareCompositing()) CRASH(); OwnPtr webContext; // If we've already created an onscreen context for this view, return that. if (m_temporaryOnscreenGraphicsContext3D) webContext = m_temporaryOnscreenGraphicsContext3D.release(); else // Otherwise make a new one. webContext = createCompositorGraphicsContext3D(); // The caller takes ownership of this object, but since there's no equivalent of PassOwnPtr<> in the WebKit API // we return a raw pointer. return webContext.leakPtr(); } void WebViewImpl::applyScrollAndScale(const WebSize& scrollDelta, float pageScaleDelta) { if (!mainFrameImpl() || !mainFrameImpl()->frameView()) return; if (pageScaleDelta == 1) { TRACE_EVENT_INSTANT2("webkit", "WebViewImpl::applyScrollAndScale::scrollBy", "x", scrollDelta.width, "y", scrollDelta.height); mainFrameImpl()->frameView()->scrollBy(scrollDelta); } else { // The page scale changed, so apply a scale and scroll in a single // operation. The old scroll offset (and passed-in delta) are // in the old coordinate space, so we first need to multiply them // by the page scale delta. WebSize scrollOffset = mainFrame()->scrollOffset(); scrollOffset.width += scrollDelta.width; scrollOffset.height += scrollDelta.height; WebPoint scaledScrollOffset(scrollOffset.width * pageScaleDelta, scrollOffset.height * pageScaleDelta); setPageScaleFactor(pageScaleFactor() * pageScaleDelta, scaledScrollOffset); } } void WebViewImpl::willCommit() { InspectorInstrumentation::willComposite(m_page.get()); } void WebViewImpl::didCommit() { if (m_client) m_client->didBecomeReadyForAdditionalInput(); } void WebViewImpl::didCommitAndDrawFrame() { if (m_client) m_client->didCommitAndDrawCompositorFrame(); } void WebViewImpl::didCompleteSwapBuffers() { if (m_client) m_client->didCompleteSwapBuffers(); } void WebViewImpl::didRebindGraphicsContext(bool success) { // Switch back to software rendering mode, if necessary if (!success) { ASSERT(m_isAcceleratedCompositingActive); setIsAcceleratedCompositingActive(false); m_compositorCreationFailed = true; m_client->didInvalidateRect(IntRect(0, 0, m_size.width, m_size.height)); // Force a style recalc to remove all the composited layers. m_page->mainFrame()->document()->scheduleForcedStyleRecalc(); return; } if (m_pageOverlays) m_pageOverlays->update(); } void WebViewImpl::scheduleComposite() { ASSERT(!CCProxy::hasImplThread()); m_client->scheduleComposite(); } void WebViewImpl::updateLayerTreeViewport() { if (!page() || !m_nonCompositedContentHost || m_layerTreeView.isNull()) return; FrameView* view = page()->mainFrame()->view(); IntRect visibleRect = view->visibleContentRect(true /* include scrollbars */); IntPoint scroll(view->scrollX(), view->scrollY()); // This part of the deviceScale will be used to scale the contents of // the NCCH's GraphicsLayer. float deviceScale = m_deviceScaleInCompositor; m_nonCompositedContentHost->setViewport(visibleRect.size(), view->contentsSize(), scroll, view->scrollOrigin(), deviceScale); m_layerTreeView.setViewportSize(size()); m_layerTreeView.setPageScaleFactorAndLimits(pageScaleFactor(), m_minimumPageScaleFactor, m_maximumPageScaleFactor); } WebGraphicsContext3D* WebViewImpl::sharedGraphicsContext3D() { if (!m_page->settings()->acceleratedCompositingEnabled() || !allowsAcceleratedCompositing()) return 0; return GraphicsContext3DPrivate::extractWebGraphicsContext3D(SharedGraphicsContext3D::get().get()); } void WebViewImpl::setVisibilityState(WebPageVisibilityState visibilityState, bool isInitialState) { if (!page()) return; #if ENABLE(PAGE_VISIBILITY_API) ASSERT(visibilityState == WebPageVisibilityStateVisible || visibilityState == WebPageVisibilityStateHidden || visibilityState == WebPageVisibilityStatePrerender || visibilityState == WebPageVisibilityStatePreview); m_page->setVisibilityState(static_cast(static_cast(visibilityState)), isInitialState); #endif #if USE(ACCELERATED_COMPOSITING) if (!m_layerTreeView.isNull()) { bool visible = visibilityState == WebPageVisibilityStateVisible; m_layerTreeView.setVisible(visible); } #endif } #if ENABLE(POINTER_LOCK) bool WebViewImpl::requestPointerLock() { return m_client && m_client->requestPointerLock(); } void WebViewImpl::requestPointerUnlock() { if (m_client) m_client->requestPointerUnlock(); } bool WebViewImpl::isPointerLocked() { return m_client && m_client->isPointerLocked(); } void WebViewImpl::pointerLockMouseEvent(const WebInputEvent& event) { AtomicString eventType; switch (event.type) { case WebInputEvent::MouseDown: eventType = eventNames().mousedownEvent; break; case WebInputEvent::MouseUp: eventType = eventNames().mouseupEvent; break; case WebInputEvent::MouseMove: eventType = eventNames().mousemoveEvent; break; default: ASSERT_NOT_REACHED(); } const WebMouseEvent& mouseEvent = static_cast(event); if (page()) page()->pointerLockController()->dispatchLockedMouseEvent( PlatformMouseEventBuilder(mainFrameImpl()->frameView(), mouseEvent), eventType); } #endif } // namespace WebKit