|  | // Copyright 2015 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "web/DevToolsEmulator.h" | 
|  |  | 
|  | #include "core/frame/FrameHost.h" | 
|  | #include "core/frame/FrameView.h" | 
|  | #include "core/frame/Settings.h" | 
|  | #include "core/page/Page.h" | 
|  | #include "core/style/ComputedStyle.h" | 
|  | #include "platform/RuntimeEnabledFeatures.h" | 
|  | #include "public/platform/WebLayerTreeView.h" | 
|  | #include "web/WebInputEventConversion.h" | 
|  | #include "web/WebLocalFrameImpl.h" | 
|  | #include "web/WebSettingsImpl.h" | 
|  | #include "web/WebViewImpl.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | static float calculateDeviceScaleAdjustment(int width, int height, float deviceScaleFactor) | 
|  | { | 
|  | // Chromium on Android uses a device scale adjustment for fonts used in text autosizing for | 
|  | // improved legibility. This function computes this adjusted value for text autosizing. | 
|  | // For a description of the Android device scale adjustment algorithm, see: | 
|  | // chrome/browser/chrome_content_browser_client.cc, GetDeviceScaleAdjustment(...) | 
|  | if (!width || !height || !deviceScaleFactor) | 
|  | return 1; | 
|  |  | 
|  | static const float kMinFSM = 1.05f; | 
|  | static const int kWidthForMinFSM = 320; | 
|  | static const float kMaxFSM = 1.3f; | 
|  | static const int kWidthForMaxFSM = 800; | 
|  |  | 
|  | float minWidth = std::min(width, height) / deviceScaleFactor; | 
|  | if (minWidth <= kWidthForMinFSM) | 
|  | return kMinFSM; | 
|  | if (minWidth >= kWidthForMaxFSM) | 
|  | return kMaxFSM; | 
|  |  | 
|  | // The font scale multiplier varies linearly between kMinFSM and kMaxFSM. | 
|  | float ratio = static_cast<float>(minWidth - kWidthForMinFSM) / (kWidthForMaxFSM - kWidthForMinFSM); | 
|  | return ratio * (kMaxFSM - kMinFSM) + kMinFSM; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | DevToolsEmulator::DevToolsEmulator(WebViewImpl* webViewImpl) | 
|  | : m_webViewImpl(webViewImpl) | 
|  | , m_deviceMetricsEnabled(false) | 
|  | , m_emulateMobileEnabled(false) | 
|  | , m_isOverlayScrollbarsEnabled(false) | 
|  | , m_isOrientationEventEnabled(false) | 
|  | , m_isMobileLayoutThemeEnabled(false) | 
|  | , m_originalDefaultMinimumPageScaleFactor(0) | 
|  | , m_originalDefaultMaximumPageScaleFactor(0) | 
|  | , m_embedderTextAutosizingEnabled(webViewImpl->page()->settings().textAutosizingEnabled()) | 
|  | , m_embedderDeviceScaleAdjustment(webViewImpl->page()->settings().deviceScaleAdjustment()) | 
|  | , m_embedderPreferCompositingToLCDTextEnabled(webViewImpl->page()->settings().preferCompositingToLCDTextEnabled()) | 
|  | , m_embedderViewportStyle(webViewImpl->page()->settings().viewportStyle()) | 
|  | , m_embedderPluginsEnabled(webViewImpl->page()->settings().pluginsEnabled()) | 
|  | , m_embedderAvailablePointerTypes(webViewImpl->page()->settings().availablePointerTypes()) | 
|  | , m_embedderPrimaryPointerType(webViewImpl->page()->settings().primaryPointerType()) | 
|  | , m_embedderAvailableHoverTypes(webViewImpl->page()->settings().availableHoverTypes()) | 
|  | , m_embedderPrimaryHoverType(webViewImpl->page()->settings().primaryHoverType()) | 
|  | , m_touchEventEmulationEnabled(false) | 
|  | , m_doubleTapToZoomEnabled(false) | 
|  | , m_mainFrameResizesAreOrientationChanges(false) | 
|  | , m_originalTouchEnabled(false) | 
|  | , m_originalDeviceSupportsMouse(false) | 
|  | , m_originalDeviceSupportsTouch(false) | 
|  | , m_originalMaxTouchPoints(0) | 
|  | , m_embedderScriptEnabled(webViewImpl->page()->settings().scriptEnabled()) | 
|  | , m_scriptExecutionDisabled(false) | 
|  | { | 
|  | } | 
|  |  | 
|  | DevToolsEmulator::~DevToolsEmulator() | 
|  | { | 
|  | } | 
|  |  | 
|  | DevToolsEmulator* DevToolsEmulator::create(WebViewImpl* webViewImpl) | 
|  | { | 
|  | return new DevToolsEmulator(webViewImpl); | 
|  | } | 
|  |  | 
|  | DEFINE_TRACE(DevToolsEmulator) | 
|  | { | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setTextAutosizingEnabled(bool enabled) | 
|  | { | 
|  | m_embedderTextAutosizingEnabled = enabled; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setTextAutosizingEnabled(enabled); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setDeviceScaleAdjustment(float deviceScaleAdjustment) | 
|  | { | 
|  | m_embedderDeviceScaleAdjustment = deviceScaleAdjustment; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setDeviceScaleAdjustment(deviceScaleAdjustment); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setPreferCompositingToLCDTextEnabled(bool enabled) | 
|  | { | 
|  | m_embedderPreferCompositingToLCDTextEnabled = enabled; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setPreferCompositingToLCDTextEnabled(enabled); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setViewportStyle(WebViewportStyle style) | 
|  | { | 
|  | m_embedderViewportStyle = style; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setViewportStyle(style); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setPluginsEnabled(bool enabled) | 
|  | { | 
|  | m_embedderPluginsEnabled = enabled; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setPluginsEnabled(enabled); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setScriptEnabled(bool enabled) | 
|  | { | 
|  | m_embedderScriptEnabled = enabled; | 
|  | if (!m_scriptExecutionDisabled) | 
|  | m_webViewImpl->page()->settings().setScriptEnabled(enabled); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setDoubleTapToZoomEnabled(bool enabled) | 
|  | { | 
|  | m_doubleTapToZoomEnabled = enabled; | 
|  | } | 
|  |  | 
|  | bool DevToolsEmulator::doubleTapToZoomEnabled() const | 
|  | { | 
|  | return m_touchEventEmulationEnabled ? true : m_doubleTapToZoomEnabled; | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setMainFrameResizesAreOrientationChanges(bool enabled) | 
|  | { | 
|  | m_mainFrameResizesAreOrientationChanges = enabled; | 
|  | } | 
|  |  | 
|  | bool DevToolsEmulator::mainFrameResizesAreOrientationChanges() const | 
|  | { | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | return emulateMobileEnabled ? true : m_mainFrameResizesAreOrientationChanges; | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setAvailablePointerTypes(int types) | 
|  | { | 
|  | m_embedderAvailablePointerTypes = types; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setAvailablePointerTypes(types); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setPrimaryPointerType(PointerType pointerType) | 
|  | { | 
|  | m_embedderPrimaryPointerType = pointerType; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setPrimaryPointerType(pointerType); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setAvailableHoverTypes(int types) | 
|  | { | 
|  | m_embedderAvailableHoverTypes = types; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setAvailableHoverTypes(types); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setPrimaryHoverType(HoverType hoverType) | 
|  | { | 
|  | m_embedderPrimaryHoverType = hoverType; | 
|  | bool emulateMobileEnabled = m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | if (!emulateMobileEnabled) | 
|  | m_webViewImpl->page()->settings().setPrimaryHoverType(hoverType); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::enableDeviceEmulation(const WebDeviceEmulationParams& params) | 
|  | { | 
|  | if (m_deviceMetricsEnabled | 
|  | && m_emulationParams.viewSize == params.viewSize | 
|  | && m_emulationParams.screenPosition == params.screenPosition | 
|  | && m_emulationParams.deviceScaleFactor == params.deviceScaleFactor | 
|  | && m_emulationParams.offset == params.offset | 
|  | && m_emulationParams.scale == params.scale) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | m_emulationParams = params; | 
|  |  | 
|  | if (!m_deviceMetricsEnabled) { | 
|  | m_deviceMetricsEnabled = true; | 
|  | if (params.viewSize.width || params.viewSize.height) | 
|  | m_webViewImpl->setBackgroundColorOverride(Color::darkGray); | 
|  | } | 
|  |  | 
|  | m_webViewImpl->page()->settings().setDeviceScaleAdjustment(calculateDeviceScaleAdjustment(params.viewSize.width, params.viewSize.height, params.deviceScaleFactor)); | 
|  |  | 
|  | if (params.screenPosition == WebDeviceEmulationParams::Mobile) | 
|  | enableMobileEmulation(); | 
|  | else | 
|  | disableMobileEmulation(); | 
|  |  | 
|  | m_webViewImpl->setCompositorDeviceScaleFactorOverride(params.deviceScaleFactor); | 
|  | m_webViewImpl->setRootLayerTransform(WebSize(params.offset.x, params.offset.y), params.scale); | 
|  | // TODO(dgozman): mainFrameImpl() is null when it's remote. Figure out how | 
|  | // we end up with enabling emulation in this case. | 
|  | if (m_webViewImpl->mainFrameImpl()) { | 
|  | if (Document* document = m_webViewImpl->mainFrameImpl()->frame()->document()) | 
|  | document->mediaQueryAffectingValueChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::disableDeviceEmulation() | 
|  | { | 
|  | if (!m_deviceMetricsEnabled) | 
|  | return; | 
|  |  | 
|  | m_deviceMetricsEnabled = false; | 
|  | m_webViewImpl->setBackgroundColorOverride(Color::transparent); | 
|  | m_webViewImpl->page()->settings().setDeviceScaleAdjustment(m_embedderDeviceScaleAdjustment); | 
|  | disableMobileEmulation(); | 
|  | m_webViewImpl->setCompositorDeviceScaleFactorOverride(0.f); | 
|  | m_webViewImpl->setRootLayerTransform(WebSize(0.f, 0.f), 1.f); | 
|  | m_webViewImpl->setPageScaleFactor(1.f); | 
|  | // mainFrameImpl() could be null during cleanup or remote <-> local swap. | 
|  | if (m_webViewImpl->mainFrameImpl()) { | 
|  | if (Document* document = m_webViewImpl->mainFrameImpl()->frame()->document()) | 
|  | document->mediaQueryAffectingValueChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool DevToolsEmulator::resizeIsDeviceSizeChange() | 
|  | { | 
|  | return m_deviceMetricsEnabled && m_emulateMobileEnabled; | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::enableMobileEmulation() | 
|  | { | 
|  | if (m_emulateMobileEnabled) | 
|  | return; | 
|  | m_emulateMobileEnabled = true; | 
|  | m_isOverlayScrollbarsEnabled = RuntimeEnabledFeatures::overlayScrollbarsEnabled(); | 
|  | RuntimeEnabledFeatures::setOverlayScrollbarsEnabled(true); | 
|  | m_isOrientationEventEnabled = RuntimeEnabledFeatures::orientationEventEnabled(); | 
|  | RuntimeEnabledFeatures::setOrientationEventEnabled(true); | 
|  | m_isMobileLayoutThemeEnabled = RuntimeEnabledFeatures::mobileLayoutThemeEnabled(); | 
|  | RuntimeEnabledFeatures::setMobileLayoutThemeEnabled(true); | 
|  | ComputedStyle::invalidateInitialStyle(); | 
|  | m_webViewImpl->page()->settings().setViewportStyle(WebViewportStyle::Mobile); | 
|  | m_webViewImpl->page()->settings().setViewportEnabled(true); | 
|  | m_webViewImpl->page()->settings().setViewportMetaEnabled(true); | 
|  | m_webViewImpl->page()->frameHost().visualViewport().initializeScrollbars(); | 
|  | m_webViewImpl->settings()->setShrinksViewportContentToFit(true); | 
|  | m_webViewImpl->page()->settings().setTextAutosizingEnabled(true); | 
|  | m_webViewImpl->page()->settings().setPreferCompositingToLCDTextEnabled(true); | 
|  | m_webViewImpl->page()->settings().setPluginsEnabled(false); | 
|  | m_webViewImpl->page()->settings().setAvailablePointerTypes(PointerTypeCoarse); | 
|  | m_webViewImpl->page()->settings().setPrimaryPointerType(PointerTypeCoarse); | 
|  | m_webViewImpl->page()->settings().setAvailableHoverTypes(HoverTypeOnDemand); | 
|  | m_webViewImpl->page()->settings().setPrimaryHoverType(HoverTypeOnDemand); | 
|  | m_webViewImpl->page()->settings().setResizeIsDeviceSizeChange(true); | 
|  | m_webViewImpl->setZoomFactorOverride(1); | 
|  |  | 
|  | m_originalDefaultMinimumPageScaleFactor = m_webViewImpl->defaultMinimumPageScaleFactor(); | 
|  | m_originalDefaultMaximumPageScaleFactor = m_webViewImpl->defaultMaximumPageScaleFactor(); | 
|  | m_webViewImpl->setDefaultPageScaleLimits(0.25f, 5); | 
|  | // TODO(dgozman): mainFrameImpl() is null when it's remote. Figure out how | 
|  | // we end up with enabling emulation in this case. | 
|  | if (m_webViewImpl->mainFrameImpl()) | 
|  | m_webViewImpl->mainFrameImpl()->frameView()->layout(); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::disableMobileEmulation() | 
|  | { | 
|  | if (!m_emulateMobileEnabled) | 
|  | return; | 
|  | RuntimeEnabledFeatures::setOverlayScrollbarsEnabled(m_isOverlayScrollbarsEnabled); | 
|  | RuntimeEnabledFeatures::setOrientationEventEnabled(m_isOrientationEventEnabled); | 
|  | RuntimeEnabledFeatures::setMobileLayoutThemeEnabled(m_isMobileLayoutThemeEnabled); | 
|  | ComputedStyle::invalidateInitialStyle(); | 
|  | m_webViewImpl->page()->settings().setViewportEnabled(false); | 
|  | m_webViewImpl->page()->settings().setViewportMetaEnabled(false); | 
|  | m_webViewImpl->page()->frameHost().visualViewport().initializeScrollbars(); | 
|  | m_webViewImpl->settings()->setShrinksViewportContentToFit(false); | 
|  | m_webViewImpl->page()->settings().setTextAutosizingEnabled(m_embedderTextAutosizingEnabled); | 
|  | m_webViewImpl->page()->settings().setPreferCompositingToLCDTextEnabled(m_embedderPreferCompositingToLCDTextEnabled); | 
|  | m_webViewImpl->page()->settings().setViewportStyle(m_embedderViewportStyle); | 
|  | m_webViewImpl->page()->settings().setPluginsEnabled(m_embedderPluginsEnabled); | 
|  | m_webViewImpl->page()->settings().setAvailablePointerTypes(m_embedderAvailablePointerTypes); | 
|  | m_webViewImpl->page()->settings().setPrimaryPointerType(m_embedderPrimaryPointerType); | 
|  | m_webViewImpl->page()->settings().setAvailableHoverTypes(m_embedderAvailableHoverTypes); | 
|  | m_webViewImpl->page()->settings().setPrimaryHoverType(m_embedderPrimaryHoverType); | 
|  | m_webViewImpl->page()->settings().setResizeIsDeviceSizeChange(false); | 
|  | m_webViewImpl->setZoomFactorOverride(0); | 
|  | m_emulateMobileEnabled = false; | 
|  | m_webViewImpl->setDefaultPageScaleLimits( | 
|  | m_originalDefaultMinimumPageScaleFactor, | 
|  | m_originalDefaultMaximumPageScaleFactor); | 
|  | // mainFrameImpl() could be null during cleanup or remote <-> local swap. | 
|  | if (m_webViewImpl->mainFrameImpl()) | 
|  | m_webViewImpl->mainFrameImpl()->frameView()->layout(); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setTouchEventEmulationEnabled(bool enabled) | 
|  | { | 
|  | if (m_touchEventEmulationEnabled == enabled) | 
|  | return; | 
|  | if (!m_touchEventEmulationEnabled) { | 
|  | m_originalTouchEnabled = RuntimeEnabledFeatures::touchEnabled(); | 
|  | m_originalDeviceSupportsMouse = m_webViewImpl->page()->settings().deviceSupportsMouse(); | 
|  | m_originalDeviceSupportsTouch = m_webViewImpl->page()->settings().deviceSupportsTouch(); | 
|  | m_originalMaxTouchPoints = m_webViewImpl->page()->settings().maxTouchPoints(); | 
|  | } | 
|  | RuntimeEnabledFeatures::setTouchEnabled(enabled ? true : m_originalTouchEnabled); | 
|  | if (!m_originalDeviceSupportsTouch) { | 
|  | m_webViewImpl->page()->settings().setDeviceSupportsMouse(enabled ? false : m_originalDeviceSupportsMouse); | 
|  | m_webViewImpl->page()->settings().setDeviceSupportsTouch(enabled ? true : m_originalDeviceSupportsTouch); | 
|  | // Currently emulation does not provide multiple touch points. | 
|  | m_webViewImpl->page()->settings().setMaxTouchPoints(enabled ? 1 : m_originalMaxTouchPoints); | 
|  | } | 
|  | m_touchEventEmulationEnabled = enabled; | 
|  | // TODO(dgozman): mainFrameImpl() check in this class should be unnecessary. | 
|  | // It is only needed when we reattach and restore InspectorEmulationAgent, | 
|  | // which happens before everything has been setup correctly, and therefore | 
|  | // fails during remote -> local main frame transition. | 
|  | // We should instead route emulation from browser through the WebViewImpl | 
|  | // to the local main frame, and remove InspectorEmulationAgent entirely. | 
|  | if (m_webViewImpl->mainFrameImpl()) | 
|  | m_webViewImpl->mainFrameImpl()->frameView()->layout(); | 
|  | } | 
|  |  | 
|  | void DevToolsEmulator::setScriptExecutionDisabled(bool scriptExecutionDisabled) | 
|  | { | 
|  | m_scriptExecutionDisabled = scriptExecutionDisabled; | 
|  | m_webViewImpl->page()->settings().setScriptEnabled(m_scriptExecutionDisabled ? false : m_embedderScriptEnabled); | 
|  | } | 
|  |  | 
|  | bool DevToolsEmulator::handleInputEvent(const WebInputEvent& inputEvent) | 
|  | { | 
|  | Page* page = m_webViewImpl->page(); | 
|  | if (!page) | 
|  | return false; | 
|  |  | 
|  | // FIXME: This workaround is required for touch emulation on Mac, where | 
|  | // compositor-side pinch handling is not enabled. See http://crbug.com/138003. | 
|  | bool isPinch = inputEvent.type == WebInputEvent::GesturePinchBegin || inputEvent.type == WebInputEvent::GesturePinchUpdate || inputEvent.type == WebInputEvent::GesturePinchEnd; | 
|  | if (isPinch && m_touchEventEmulationEnabled) { | 
|  | FrameView* frameView = page->deprecatedLocalMainFrame()->view(); | 
|  | PlatformGestureEventBuilder gestureEvent(frameView, static_cast<const WebGestureEvent&>(inputEvent)); | 
|  | float pageScaleFactor = page->pageScaleFactor(); | 
|  | if (gestureEvent.type() == PlatformEvent::GesturePinchBegin) { | 
|  | m_lastPinchAnchorCss = adoptPtr(new IntPoint(frameView->scrollPosition() + gestureEvent.position())); | 
|  | m_lastPinchAnchorDip = adoptPtr(new IntPoint(gestureEvent.position())); | 
|  | m_lastPinchAnchorDip->scale(pageScaleFactor, pageScaleFactor); | 
|  | } | 
|  | if (gestureEvent.type() == PlatformEvent::GesturePinchUpdate && m_lastPinchAnchorCss) { | 
|  | float newPageScaleFactor = pageScaleFactor * gestureEvent.scale(); | 
|  | IntPoint anchorCss(*m_lastPinchAnchorDip.get()); | 
|  | anchorCss.scale(1.f / newPageScaleFactor, 1.f / newPageScaleFactor); | 
|  | m_webViewImpl->setPageScaleFactor(newPageScaleFactor); | 
|  | m_webViewImpl->mainFrame()->setScrollOffset(toIntSize(*m_lastPinchAnchorCss.get() - toIntSize(anchorCss))); | 
|  | } | 
|  | if (gestureEvent.type() == PlatformEvent::GesturePinchEnd) { | 
|  | m_lastPinchAnchorCss.clear(); | 
|  | m_lastPinchAnchorDip.clear(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | } // namespace blink |