blob: aca83f2ecf44758f44b5de1c93d9e95b369d678e [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "WebPage.h"
#include "ApplicationCacheStorage.h"
#include "AuthenticationChallengeManager.h"
#include "AutofillManager.h"
#include "BackForwardController.h"
#include "BackForwardListImpl.h"
#include "BackingStoreClient.h"
#include "BackingStore_p.h"
#if ENABLE(BATTERY_STATUS)
#include "BatteryClientBlackBerry.h"
#endif
#include "CachedImage.h"
#include "Chrome.h"
#include "ChromeClientBlackBerry.h"
#include "ContextMenuClientBlackBerry.h"
#include "CookieManager.h"
#include "CredentialManager.h"
#include "CredentialStorage.h"
#include "CredentialTransformData.h"
#include "DOMSupport.h"
#include "Database.h"
#include "DatabaseSync.h"
#include "DatabaseTracker.h"
#include "DefaultTapHighlight.h"
#include "DeviceMotionClientBlackBerry.h"
#include "DeviceOrientationClientBlackBerry.h"
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
#include "DeviceOrientationClientMock.h"
#endif
#include "DragClientBlackBerry.h"
// FIXME: We should be using DumpRenderTreeClient, but I'm not sure where we should
// create the DRT_BB object. See PR #120355.
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
#include "DumpRenderTreeBlackBerry.h"
#endif
#include "EditorClientBlackBerry.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameLoaderClientBlackBerry.h"
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
#include "GeolocationClientMock.h"
#endif
#include "GeolocationClientBlackBerry.h"
#include "GroupSettings.h"
#include "HTMLAreaElement.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLMediaElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "HTTPParsers.h"
#include "HistoryItem.h"
#include "IconDatabaseClientBlackBerry.h"
#include "InPageSearchManager.h"
#include "InRegionScrollableArea.h"
#include "InRegionScroller_p.h"
#include "InputHandler.h"
#include "InspectorBackendDispatcher.h"
#include "InspectorClientBlackBerry.h"
#include "InspectorController.h"
#include "InspectorInstrumentation.h"
#include "InspectorOverlay.h"
#include "JavaScriptVariant_p.h"
#include "LayerWebKitThread.h"
#if ENABLE(NETWORK_INFO)
#include "NetworkInfoClientBlackBerry.h"
#endif
#include "NetworkManager.h"
#include "NodeRenderStyle.h"
#if ENABLE(NAVIGATOR_CONTENT_UTILS)
#include "NavigatorContentUtilsClientBlackBerry.h"
#endif
#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
#include "NotificationPresenterImpl.h"
#endif
#include "Page.h"
#include "PageCache.h"
#include "PageGroup.h"
#include "PagePopupBlackBerry.h"
#include "PlatformTouchEvent.h"
#include "PlatformWheelEvent.h"
#include "PluginDatabase.h"
#include "PluginView.h"
#include "RenderLayerBacking.h"
#include "RenderLayerCompositor.h"
#if ENABLE(FULLSCREEN_API)
#include "RenderFullScreen.h"
#endif
#include "RenderText.h"
#include "RenderThemeBlackBerry.h"
#include "RenderTreeAsText.h"
#include "RenderView.h"
#include "RenderWidget.h"
#include "ScriptSourceCode.h"
#include "ScriptValue.h"
#include "ScrollTypes.h"
#include "SecurityPolicy.h"
#include "SelectionHandler.h"
#include "SelectionOverlay.h"
#include "Settings.h"
#include "Storage.h"
#include "StorageNamespace.h"
#include "SurfacePool.h"
#include "Text.h"
#include "ThreadCheck.h"
#include "TouchEventHandler.h"
#include "TransformationMatrix.h"
#if ENABLE(MEDIA_STREAM)
#include "UserMediaClientImpl.h"
#endif
#if ENABLE(VIBRATION)
#include "VibrationClientBlackBerry.h"
#endif
#include "VisiblePosition.h"
#include "WebCookieJar.h"
#if ENABLE(WEBDOM)
#include "WebDOMDocument.h"
#endif
#include "WebKitThreadViewportAccessor.h"
#include "WebKitVersion.h"
#include "WebOverlay.h"
#include "WebOverlay_p.h"
#include "WebPageClient.h"
#include "WebSocket.h"
#include "WebViewportArguments.h"
#include "npapi.h"
#include "runtime_root.h"
#if ENABLE(VIDEO)
#include "MediaPlayer.h"
#include "MediaPlayerPrivateBlackBerry.h"
#endif
#if USE(SKIA)
#include "PlatformContextSkia.h"
#endif
#if USE(ACCELERATED_COMPOSITING)
#include "FrameLayers.h"
#include "WebPageCompositorClient.h"
#include "WebPageCompositor_p.h"
#endif
#include <BlackBerryPlatformDeviceInfo.h>
#include <BlackBerryPlatformExecutableMessage.h>
#include <BlackBerryPlatformKeyboardEvent.h>
#include <BlackBerryPlatformMessageClient.h>
#include <BlackBerryPlatformMouseEvent.h>
#include <BlackBerryPlatformScreen.h>
#include <BlackBerryPlatformSettings.h>
#include <JavaScriptCore/APICast.h>
#include <JavaScriptCore/JSContextRef.h>
#include <JavaScriptCore/JSStringRef.h>
#include <SharedPointer.h>
#include <sys/keycodes.h>
#include <unicode/ustring.h> // platform ICU
#include <wtf/text/CString.h>
#ifndef USER_PROCESSES
#include <memalloc.h>
#endif
#if ENABLE(ACCELERATED_2D_CANVAS)
#include "GrContext.h"
#include "SharedGraphicsContext3D.h"
#endif
#if ENABLE(REQUEST_ANIMATION_FRAME)
#include "PlatformScreen.h"
#endif
#define DEBUG_TOUCH_EVENTS 0
#define DEBUG_WEBPAGE_LOAD 0
#define DEBUG_AC_COMMIT 0
using namespace std;
using namespace WebCore;
typedef const unsigned short* CUShortPtr;
namespace BlackBerry {
namespace WebKit {
static Vector<WebPage*>* visibleWebPages()
{
static Vector<WebPage*>* s_visibleWebPages = 0; // Initially, no web page is visible.
if (!s_visibleWebPages)
s_visibleWebPages = new Vector<WebPage*>;
return s_visibleWebPages;
}
const unsigned blockZoomMargin = 3; // Add 3 pixel margin on each side.
static int blockClickRadius = 0;
static double maximumBlockZoomScale = 3; // This scale can be clamped by the maximumScale set for the page.
const double manualScrollInterval = 0.1; // The time interval during which we associate user action with scrolling.
const IntSize minimumLayoutSize(10, 10); // Needs to be a small size, greater than 0, that we can grow the layout from.
const double minimumExpandingRatio = 0.15;
const double minimumZoomToFitScale = 0.25;
// Helper function to parse a URL and fill in missing parts.
static KURL parseUrl(const String& url)
{
String urlString(url);
KURL kurl = KURL(KURL(), urlString);
if (kurl.protocol().isEmpty()) {
urlString.insert("http://", 0);
kurl = KURL(KURL(), urlString);
}
return kurl;
}
// Helper functions to convert to and from WebCore types.
static inline WebCore::PlatformEvent::Type toWebCoreMouseEventType(const BlackBerry::Platform::MouseEvent::Type type)
{
switch (type) {
case BlackBerry::Platform::MouseEvent::MouseButtonDown:
return WebCore::PlatformEvent::MousePressed;
case Platform::MouseEvent::MouseButtonUp:
return WebCore::PlatformEvent::MouseReleased;
case Platform::MouseEvent::MouseMove:
default:
return WebCore::PlatformEvent::MouseMoved;
}
}
static inline ResourceRequestCachePolicy toWebCoreCachePolicy(Platform::NetworkRequest::CachePolicy policy)
{
switch (policy) {
case Platform::NetworkRequest::UseProtocolCachePolicy:
return UseProtocolCachePolicy;
case Platform::NetworkRequest::ReloadIgnoringCacheData:
return ReloadIgnoringCacheData;
case Platform::NetworkRequest::ReturnCacheDataElseLoad:
return ReturnCacheDataElseLoad;
case Platform::NetworkRequest::ReturnCacheDataDontLoad:
return ReturnCacheDataDontLoad;
default:
ASSERT_NOT_REACHED();
return UseProtocolCachePolicy;
}
}
#if ENABLE(EVENT_MODE_METATAGS)
static inline Platform::CursorEventMode toPlatformCursorEventMode(CursorEventMode mode)
{
switch (mode) {
case ProcessedCursorEvents:
return Platform::ProcessedCursorEvents;
case NativeCursorEvents:
return Platform::NativeCursorEvents;
default:
ASSERT_NOT_REACHED();
return Platform::ProcessedCursorEvents;
}
}
static inline Platform::TouchEventMode toPlatformTouchEventMode(TouchEventMode mode)
{
switch (mode) {
case ProcessedTouchEvents:
return Platform::ProcessedTouchEvents;
case NativeTouchEvents:
return Platform::NativeTouchEvents;
case PureTouchEventsWithMouseConversion:
return Platform::PureTouchEventsWithMouseConversion;
default:
ASSERT_NOT_REACHED();
return Platform::ProcessedTouchEvents;
}
}
#endif
static inline HistoryItem* historyItemFromBackForwardId(WebPage::BackForwardId id)
{
return reinterpret_cast<HistoryItem*>(id);
}
static inline WebPage::BackForwardId backForwardIdFromHistoryItem(HistoryItem* item)
{
return reinterpret_cast<WebPage::BackForwardId>(item);
}
void WebPage::setUserViewportArguments(const WebViewportArguments& viewportArguments)
{
d->m_userViewportArguments = *(viewportArguments.d);
}
void WebPage::resetUserViewportArguments()
{
d->m_userViewportArguments = ViewportArguments();
}
template <bool WebPagePrivate::* isActive>
class DeferredTask: public WebPagePrivate::DeferredTaskBase {
public:
static void finishOrCancel(WebPagePrivate* webPagePrivate)
{
webPagePrivate->*isActive = false;
}
protected:
DeferredTask(WebPagePrivate* webPagePrivate)
: DeferredTaskBase(webPagePrivate, isActive)
{
}
typedef DeferredTask<isActive> DeferredTaskType;
};
void WebPage::autofillTextField(const BlackBerry::Platform::String& item)
{
if (!d->m_webSettings->isFormAutofillEnabled())
return;
d->m_autofillManager->autofillTextField(item);
}
void WebPage::enableQnxJavaScriptObject(bool enabled)
{
d->m_enableQnxJavaScriptObject = enabled;
}
BlackBerry::Platform::String WebPage::renderTreeAsText()
{
return externalRepresentation(d->m_mainFrame);
}
WebPagePrivate::WebPagePrivate(WebPage* webPage, WebPageClient* client, const IntRect& rect)
: m_webPage(webPage)
, m_client(client)
, m_inspectorClient(0)
, m_page(0) // Initialized by init.
, m_mainFrame(0) // Initialized by init.
, m_currentContextNode(0)
, m_webSettings(0) // Initialized by init.
, m_cookieJar(0)
, m_visible(false)
, m_activationState(ActivationActive)
, m_shouldResetTilesWhenShown(false)
, m_shouldZoomToInitialScaleAfterLoadFinished(false)
, m_userScalable(true)
, m_userPerformedManualZoom(false)
, m_userPerformedManualScroll(false)
, m_contentsSizeChanged(false)
, m_overflowExceedsContentsSize(false)
, m_resetVirtualViewportOnCommitted(true)
, m_shouldUseFixedDesktopMode(false)
, m_needTouchEvents(false)
, m_preventIdleDimmingCount(0)
#if ENABLE(TOUCH_EVENTS)
, m_preventDefaultOnTouchStart(false)
#endif
, m_nestedLayoutFinishedCount(0)
, m_actualVisibleWidth(rect.width())
, m_actualVisibleHeight(rect.height())
, m_defaultLayoutSize(minimumLayoutSize)
, m_didRestoreFromPageCache(false)
, m_viewMode(WebPagePrivate::Desktop) // Default to Desktop mode for PB.
, m_loadState(WebPagePrivate::None)
, m_transformationMatrix(new TransformationMatrix())
, m_backingStore(0) // Initialized by init.
, m_backingStoreClient(0) // Initialized by init.
, m_webkitThreadViewportAccessor(0) // Initialized by init.
, m_inPageSearchManager(new InPageSearchManager(this))
, m_inputHandler(new InputHandler(this))
, m_selectionHandler(new SelectionHandler(this))
, m_touchEventHandler(new TouchEventHandler(this))
#if ENABLE(EVENT_MODE_METATAGS)
, m_cursorEventMode(ProcessedCursorEvents)
, m_touchEventMode(ProcessedTouchEvents)
#endif
#if ENABLE(FULLSCREEN_API) && ENABLE(VIDEO)
, m_scaleBeforeFullScreen(-1.0)
, m_xScrollOffsetBeforeFullScreen(-1)
#endif
, m_currentCursor(Platform::CursorNone)
, m_dumpRenderTree(0) // Lazy initialization.
, m_initialScale(-1.0)
, m_minimumScale(-1.0)
, m_maximumScale(-1.0)
, m_blockZoomFinalScale(1.0)
, m_anchorInNodeRectRatio(-1, -1)
, m_currentBlockZoomNode(0)
, m_currentBlockZoomAdjustedNode(0)
, m_shouldReflowBlock(false)
, m_lastUserEventTimestamp(0.0)
, m_pluginMouseButtonPressed(false)
, m_pluginMayOpenNewTab(false)
#if USE(ACCELERATED_COMPOSITING)
, m_rootLayerCommitTimer(adoptPtr(new Timer<WebPagePrivate>(this, &WebPagePrivate::rootLayerCommitTimerFired)))
, m_needsOneShotDrawingSynchronization(false)
, m_needsCommit(false)
, m_suspendRootLayerCommit(false)
#endif
, m_hasPendingSurfaceSizeChange(false)
, m_pendingOrientation(-1)
, m_fullscreenVideoNode(0)
, m_hasInRegionScrollableAreas(false)
, m_updateDelegatedOverlaysDispatched(false)
, m_enableQnxJavaScriptObject(false)
, m_deferredTasksTimer(this, &WebPagePrivate::deferredTasksTimerFired)
, m_selectPopup(0)
, m_autofillManager(AutofillManager::create(this))
, m_documentStyleRecalcPostponed(false)
, m_documentChildNeedsStyleRecalc(false)
{
static bool isInitialized = false;
if (!isInitialized) {
isInitialized = true;
BlackBerry::Platform::DeviceInfo::instance();
defaultUserAgent();
}
AuthenticationChallengeManager::instance()->pageCreated(this);
clearCachedHitTestResult();
}
WebPage::WebPage(WebPageClient* client, const BlackBerry::Platform::String& pageGroupName, const Platform::IntRect& rect)
{
globalInitialize();
d = new WebPagePrivate(this, client, rect);
d->init(pageGroupName);
}
WebPagePrivate::~WebPagePrivate()
{
// Hand the backingstore back to another owner if necessary.
m_webPage->setVisible(false);
if (BackingStorePrivate::currentBackingStoreOwner() == m_webPage)
BackingStorePrivate::setCurrentBackingStoreOwner(0);
delete m_webSettings;
m_webSettings = 0;
delete m_cookieJar;
m_cookieJar = 0;
delete m_webkitThreadViewportAccessor;
m_webkitThreadViewportAccessor = 0;
delete m_backingStoreClient;
m_backingStoreClient = 0;
m_backingStore = 0;
delete m_page;
m_page = 0;
delete m_transformationMatrix;
m_transformationMatrix = 0;
delete m_inPageSearchManager;
m_inPageSearchManager = 0;
delete m_selectionHandler;
m_selectionHandler = 0;
delete m_inputHandler;
m_inputHandler = 0;
delete m_touchEventHandler;
m_touchEventHandler = 0;
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
delete m_dumpRenderTree;
m_dumpRenderTree = 0;
#endif
AuthenticationChallengeManager::instance()->pageDeleted(this);
}
WebPage::~WebPage()
{
deleteGuardedObject(d);
d = 0;
}
Page* WebPagePrivate::core(const WebPage* webPage)
{
return webPage->d->m_page;
}
void WebPagePrivate::init(const BlackBerry::Platform::String& pageGroupName)
{
ChromeClientBlackBerry* chromeClient = new ChromeClientBlackBerry(this);
ContextMenuClientBlackBerry* contextMenuClient = 0;
#if ENABLE(CONTEXT_MENUS)
contextMenuClient = new ContextMenuClientBlackBerry();
#endif
EditorClientBlackBerry* editorClient = new EditorClientBlackBerry(this);
DragClientBlackBerry* dragClient = 0;
#if ENABLE(DRAG_SUPPORT)
dragClient = new DragClientBlackBerry();
#endif
#if ENABLE(INSPECTOR)
m_inspectorClient = new InspectorClientBlackBerry(this);
#endif
FrameLoaderClientBlackBerry* frameLoaderClient = new FrameLoaderClientBlackBerry();
Page::PageClients pageClients;
pageClients.chromeClient = chromeClient;
pageClients.contextMenuClient = contextMenuClient;
pageClients.editorClient = editorClient;
pageClients.dragClient = dragClient;
pageClients.inspectorClient = m_inspectorClient;
m_page = new Page(pageClients);
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
if (isRunningDrt()) {
// In case running in DumpRenderTree mode set the controller to mock provider.
GeolocationClientMock* mock = new GeolocationClientMock();
WebCore::provideGeolocationTo(m_page, mock);
mock->setController(WebCore::GeolocationController::from(m_page));
} else
#endif
WebCore::provideGeolocationTo(m_page, new GeolocationClientBlackBerry(this));
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
if (getenv("drtRun"))
WebCore::provideDeviceOrientationTo(m_page, new DeviceOrientationClientMock);
else
#endif
WebCore::provideDeviceOrientationTo(m_page, new DeviceOrientationClientBlackBerry(this));
WebCore::provideDeviceMotionTo(m_page, new DeviceMotionClientBlackBerry(this));
#if ENABLE(VIBRATION)
WebCore::provideVibrationTo(m_page, new VibrationClientBlackBerry());
#endif
#if ENABLE(BATTERY_STATUS)
WebCore::provideBatteryTo(m_page, new WebCore::BatteryClientBlackBerry(this));
#endif
#if ENABLE(MEDIA_STREAM)
WebCore::provideUserMediaTo(m_page, new UserMediaClientImpl(m_webPage));
#endif
#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
WebCore::provideNotification(m_page, NotificationPresenterImpl::instance());
#endif
#if ENABLE(NAVIGATOR_CONTENT_UTILS)
WebCore::provideNavigatorContentUtilsTo(m_page, new NavigatorContentUtilsClientBlackBerry(this));
#endif
#if ENABLE(NETWORK_INFO)
WebCore::provideNetworkInfoTo(m_page, new WebCore::NetworkInfoClientBlackBerry(this));
#endif
m_webSettings = WebSettings::createFromStandardSettings();
m_webSettings->setUserAgentString(defaultUserAgent());
m_page->setDeviceScaleFactor(m_webSettings->devicePixelRatio());
m_page->addLayoutMilestones(DidFirstVisuallyNonEmptyLayout);
#if USE(ACCELERATED_COMPOSITING)
m_tapHighlight = DefaultTapHighlight::create(this);
m_selectionOverlay = SelectionOverlay::create(this);
m_page->settings()->setAcceleratedCompositingForFixedPositionEnabled(true);
#endif
// FIXME: We explicitly call setDelegate() instead of passing ourself in createFromStandardSettings()
// so that we only get one didChangeSettings() callback when we set the page group name. This causes us
// to make a copy of the WebSettings since some WebSettings method make use of the page group name.
// Instead, we shouldn't be storing the page group name in WebSettings.
m_webSettings->setPageGroupName(pageGroupName);
m_webSettings->setDelegate(this);
didChangeSettings(m_webSettings);
RefPtr<Frame> newFrame = Frame::create(m_page, /* HTMLFrameOwnerElement* */ 0, frameLoaderClient);
m_mainFrame = newFrame.get();
frameLoaderClient->setFrame(m_mainFrame, this);
m_mainFrame->init();
m_inRegionScroller = adoptPtr(new InRegionScroller(this));
#if ENABLE(WEBGL)
m_page->settings()->setWebGLEnabled(true);
#endif
#if ENABLE(ACCELERATED_2D_CANVAS)
m_page->settings()->setCanvasUsesAcceleratedDrawing(true);
m_page->settings()->setAccelerated2dCanvasEnabled(true);
#endif
m_page->settings()->setInteractiveFormValidationEnabled(true);
m_page->settings()->setAllowUniversalAccessFromFileURLs(false);
m_page->settings()->setAllowFileAccessFromFileURLs(false);
m_page->settings()->setFixedPositionCreatesStackingContext(true);
m_backingStoreClient = BackingStoreClient::create(m_mainFrame, /* parent frame */ 0, m_webPage);
// The direct access to BackingStore is left here for convenience since it
// is owned by BackingStoreClient and then deleted by its destructor.
m_backingStore = m_backingStoreClient->backingStore();
m_webkitThreadViewportAccessor = new WebKitThreadViewportAccessor(this);
blockClickRadius = int(roundf(0.35 * Platform::Graphics::Screen::primaryScreen()->pixelsPerInch(0).width())); // The clicked rectangle area should be a fixed unit of measurement.
m_page->settings()->setDelegateSelectionPaint(true);
#if ENABLE(REQUEST_ANIMATION_FRAME)
m_page->windowScreenDidChange((PlatformDisplayID)0);
#endif
#if ENABLE(WEB_TIMING)
m_page->settings()->setMemoryInfoEnabled(true);
#endif
#if USE(ACCELERATED_COMPOSITING)
// The compositor will be needed for overlay rendering, so create it
// unconditionally. It will allocate OpenGL objects lazily, so this incurs
// no overhead in the unlikely case where the compositor is not needed.
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
createMethodCallMessage(&WebPagePrivate::createCompositor, this));
#endif
}
class DeferredTaskLoadManualScript: public DeferredTask<&WebPagePrivate::m_wouldLoadManualScript> {
public:
explicit DeferredTaskLoadManualScript(WebPagePrivate* webPagePrivate, const KURL& url)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedManualScript = url;
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_mainFrame->script()->executeIfJavaScriptURL(webPagePrivate->m_cachedManualScript, DoNotReplaceDocumentIfJavaScriptURL);
}
};
void WebPagePrivate::load(const BlackBerry::Platform::String& url, const BlackBerry::Platform::String& networkToken, const BlackBerry::Platform::String& method, Platform::NetworkRequest::CachePolicy cachePolicy, const char* data, size_t dataLength, const char* const* headers, size_t headersLength, bool isInitial, bool mustHandleInternally, bool forceDownload, const BlackBerry::Platform::String& overrideContentType, const BlackBerry::Platform::String& suggestedSaveName)
{
stopCurrentLoad();
DeferredTaskLoadManualScript::finishOrCancel(this);
String urlString(url);
if (urlString.startsWith("vs:", false)) {
urlString = urlString.substring(3);
m_mainFrame->setInViewSourceMode(true);
} else
m_mainFrame->setInViewSourceMode(false);
KURL kurl = parseUrl(urlString);
if (protocolIs(kurl, "javascript")) {
// Never run javascript while loading is deferred.
if (m_page->defersLoading())
m_deferredTasks.append(adoptPtr(new DeferredTaskLoadManualScript(this, kurl)));
else
m_mainFrame->script()->executeIfJavaScriptURL(kurl, DoNotReplaceDocumentIfJavaScriptURL);
return;
}
if (isInitial)
NetworkManager::instance()->setInitialURL(kurl);
ResourceRequest request(kurl);
request.setToken(networkToken);
if (isInitial || mustHandleInternally)
request.setMustHandleInternally(true);
request.setHTTPMethod(method);
request.setCachePolicy(toWebCoreCachePolicy(cachePolicy));
if (!overrideContentType.empty())
request.setOverrideContentType(overrideContentType);
if (data)
request.setHTTPBody(FormData::create(data, dataLength));
for (unsigned i = 0; i + 1 < headersLength; i += 2)
request.addHTTPHeaderField(headers[i], headers[i + 1]);
if (forceDownload)
request.setForceDownload(true);
request.setSuggestedSaveName(suggestedSaveName);
m_mainFrame->loader()->load(request, "" /* name */, false);
}
void WebPage::load(const BlackBerry::Platform::String& url, const BlackBerry::Platform::String& networkToken, bool isInitial)
{
d->load(url, networkToken, "GET", Platform::NetworkRequest::UseProtocolCachePolicy, 0, 0, 0, 0, isInitial, false);
}
void WebPage::loadExtended(const char* url, const char* networkToken, const char* method, Platform::NetworkRequest::CachePolicy cachePolicy, const char* data, size_t dataLength, const char* const* headers, size_t headersLength, bool mustHandleInternally)
{
d->load(url, networkToken, method, cachePolicy, data, dataLength, headers, headersLength, false, mustHandleInternally, false, "");
}
void WebPage::loadFile(const BlackBerry::Platform::String& path, const BlackBerry::Platform::String& overrideContentType)
{
BlackBerry::Platform::String fileUrl(path);
if (fileUrl.startsWith("/"))
fileUrl = BlackBerry::Platform::String("file://", 7) + fileUrl;
else if (!fileUrl.startsWith("file:///"))
return;
d->load(fileUrl, BlackBerry::Platform::String::emptyString(), BlackBerry::Platform::String("GET", 3), Platform::NetworkRequest::UseProtocolCachePolicy, 0, 0, 0, 0, false, false, false, overrideContentType.c_str());
}
void WebPage::download(const Platform::NetworkRequest& request)
{
vector<const char*> headers;
Platform::NetworkRequest::HeaderList& list = request.getHeaderListRef();
for (unsigned i = 0; i < list.size(); i++) {
headers.push_back(list[i].first.c_str());
headers.push_back(list[i].second.c_str());
}
d->load(request.getUrlRef(), BlackBerry::Platform::String::emptyString(), "GET", Platform::NetworkRequest::UseProtocolCachePolicy, 0, 0, headers.empty() ? 0 : &headers[0], headers.size(), false, false, true, "", request.getSuggestedSaveName().c_str());
}
void WebPagePrivate::loadString(const BlackBerry::Platform::String& string, const BlackBerry::Platform::String& baseURL, const BlackBerry::Platform::String& contentType, const BlackBerry::Platform::String& failingURL)
{
KURL kurl = parseUrl(baseURL);
ResourceRequest request(kurl);
WTF::RefPtr<SharedBuffer> buffer
= SharedBuffer::create(string.c_str(), string.length());
SubstituteData substituteData(buffer,
extractMIMETypeFromMediaType(contentType),
extractCharsetFromMediaType(contentType),
!failingURL.empty() ? parseUrl(failingURL) : KURL());
m_mainFrame->loader()->load(request, substituteData, false);
}
void WebPage::loadString(const BlackBerry::Platform::String& string, const BlackBerry::Platform::String& baseURL, const BlackBerry::Platform::String& mimeType, const BlackBerry::Platform::String& failingURL)
{
d->loadString(string, baseURL, mimeType, failingURL);
}
bool WebPagePrivate::executeJavaScript(const BlackBerry::Platform::String& scriptUTF8, JavaScriptDataType& returnType, WebString& returnValue)
{
BLACKBERRY_ASSERT(scriptUTF8.isUtf8());
String script = scriptUTF8;
if (script.isNull()) {
returnType = JSException;
return false;
}
if (script.isEmpty()) {
returnType = JSUndefined;
return true;
}
ScriptValue result = m_mainFrame->script()->executeScript(script, false);
JSC::JSValue value = result.jsValue();
if (!value) {
returnType = JSException;
return false;
}
if (value.isUndefined())
returnType = JSUndefined;
else if (value.isNull())
returnType = JSNull;
else if (value.isBoolean())
returnType = JSBoolean;
else if (value.isNumber())
returnType = JSNumber;
else if (value.isString())
returnType = JSString;
else if (value.isObject())
returnType = JSObject;
else
returnType = JSUndefined;
if (returnType == JSBoolean || returnType == JSNumber || returnType == JSString || returnType == JSObject) {
JSC::ExecState* exec = m_mainFrame->script()->globalObject(mainThreadNormalWorld())->globalExec();
returnValue = result.toString(exec);
}
return true;
}
bool WebPage::executeJavaScript(const BlackBerry::Platform::String& script, JavaScriptDataType& returnType, BlackBerry::Platform::String& returnValue)
{
return d->executeJavaScript(script, returnType, returnValue);
}
bool WebPagePrivate::executeJavaScriptInIsolatedWorld(const ScriptSourceCode& sourceCode, JavaScriptDataType& returnType, BlackBerry::Platform::String& returnValue)
{
if (!m_isolatedWorld)
m_isolatedWorld = m_mainFrame->script()->createWorld();
// Use evaluateInWorld to avoid canExecuteScripts check.
ScriptValue result = m_mainFrame->script()->evaluateInWorld(sourceCode, m_isolatedWorld.get());
JSC::JSValue value = result.jsValue();
if (!value) {
returnType = JSException;
return false;
}
if (value.isUndefined())
returnType = JSUndefined;
else if (value.isNull())
returnType = JSNull;
else if (value.isBoolean())
returnType = JSBoolean;
else if (value.isNumber())
returnType = JSNumber;
else if (value.isString())
returnType = JSString;
else if (value.isObject())
returnType = JSObject;
else
returnType = JSUndefined;
if (returnType == JSBoolean || returnType == JSNumber || returnType == JSString || returnType == JSObject) {
JSC::ExecState* exec = m_mainFrame->script()->globalObject(mainThreadNormalWorld())->globalExec();
returnValue = result.toString(exec);
}
return true;
}
bool WebPage::executeJavaScriptInIsolatedWorld(const std::wstring& script, JavaScriptDataType& returnType, BlackBerry::Platform::String& returnValue)
{
// On our platform wchar_t is unsigned int and UChar is unsigned short
// so we have to convert using ICU conversion function
int lengthCopied = 0;
UErrorCode error = U_ZERO_ERROR;
const int length = script.length() + 1 /*null termination char*/;
UChar data[length];
// FIXME: PR 138162 is giving U_INVALID_CHAR_FOUND error.
u_strFromUTF32(data, length, &lengthCopied, reinterpret_cast<const UChar32*>(script.c_str()), script.length(), &error);
BLACKBERRY_ASSERT(error == U_ZERO_ERROR);
if (error != U_ZERO_ERROR) {
Platform::logAlways(Platform::LogLevelCritical, "WebPage::executeJavaScriptInIsolatedWorld failed to convert UTF16 to JavaScript!");
return false;
}
String str = String(data, lengthCopied);
ScriptSourceCode sourceCode(str, KURL());
return d->executeJavaScriptInIsolatedWorld(sourceCode, returnType, returnValue);
}
bool WebPage::executeJavaScriptInIsolatedWorld(const BlackBerry::Platform::String& scriptUTF8, JavaScriptDataType& returnType, BlackBerry::Platform::String& returnValue)
{
BLACKBERRY_ASSERT(scriptUTF8.isUtf8());
ScriptSourceCode sourceCode(scriptUTF8, KURL());
return d->executeJavaScriptInIsolatedWorld(sourceCode, returnType, returnValue);
}
void WebPage::executeJavaScriptFunction(const std::vector<BlackBerry::Platform::String> &function, const std::vector<JavaScriptVariant> &args, JavaScriptVariant& returnValue)
{
if (!d->m_mainFrame) {
returnValue.setType(JavaScriptVariant::Exception);
return;
}
JSC::Bindings::RootObject* root = d->m_mainFrame->script()->bindingRootObject();
if (!root) {
returnValue.setType(JavaScriptVariant::Exception);
return;
}
JSC::ExecState* exec = root->globalObject()->globalExec();
JSGlobalContextRef ctx = toGlobalRef(exec);
JSC::JSLockHolder lock(exec);
WTF::Vector<JSValueRef> argListRef(args.size());
for (unsigned i = 0; i < args.size(); ++i)
argListRef[i] = BlackBerryJavaScriptVariantToJSValueRef(ctx, args[i]);
JSValueRef windowObjectValue = windowObject();
JSObjectRef obj = JSValueToObject(ctx, windowObjectValue, 0);
JSObjectRef thisObject = obj;
for (unsigned i = 0; i < function.size(); ++i) {
JSStringRef str = JSStringCreateWithUTF8CString(function[i].c_str());
thisObject = obj;
obj = JSValueToObject(ctx, JSObjectGetProperty(ctx, obj, str, 0), 0);
JSStringRelease(str);
if (!obj)
break;
}
JSObjectRef functionObject = obj;
JSValueRef result = 0;
if (functionObject && thisObject)
result = JSObjectCallAsFunction(ctx, functionObject, thisObject, args.size(), argListRef.data(), 0);
if (!result) {
returnValue.setType(JavaScriptVariant::Exception);
return;
}
returnValue = JSValueRefToBlackBerryJavaScriptVariant(ctx, result);
}
void WebPagePrivate::stopCurrentLoad()
{
// This function should contain all common code triggered by WebPage::load
// (which stops any load in progress before starting the new load) and
// WebPage::stoploading (the entry point for the client to stop the load
// explicitly). If it should only be done while stopping the load
// explicitly, it goes in WebPage::stopLoading, not here.
m_mainFrame->loader()->stopAllLoaders();
// Cancel any deferred script that hasn't been processed yet.
DeferredTaskLoadManualScript::finishOrCancel(this);
}
void WebPage::stopLoading()
{
d->stopCurrentLoad();
}
static void closeURLRecursively(Frame* frame)
{
// Do not create more frame please.
FrameLoaderClientBlackBerry* frameLoaderClient = static_cast<FrameLoaderClientBlackBerry*>(frame->loader()->client());
frameLoaderClient->suppressChildFrameCreation();
frame->loader()->closeURL();
Vector<RefPtr<Frame>, 10> childFrames;
for (RefPtr<Frame> childFrame = frame->tree()->firstChild(); childFrame; childFrame = childFrame->tree()->nextSibling())
childFrames.append(childFrame);
unsigned size = childFrames.size();
for (unsigned i = 0; i < size; i++)
closeURLRecursively(childFrames[i].get());
}
void WebPagePrivate::prepareToDestroy()
{
// Before the client starts tearing itself down, dispatch the unload event
// so it can take effect while all the client's state (e.g. scroll position)
// is still present.
closeURLRecursively(m_mainFrame);
}
void WebPage::prepareToDestroy()
{
d->prepareToDestroy();
}
bool WebPage::dispatchBeforeUnloadEvent()
{
return d->m_page->mainFrame()->loader()->shouldClose();
}
static void enableCrossSiteXHRRecursively(Frame* frame)
{
frame->document()->securityOrigin()->grantUniversalAccess();
Vector<RefPtr<Frame>, 10> childFrames;
for (RefPtr<Frame> childFrame = frame->tree()->firstChild(); childFrame; childFrame = childFrame->tree()->nextSibling())
childFrames.append(childFrame);
unsigned size = childFrames.size();
for (unsigned i = 0; i < size; i++)
enableCrossSiteXHRRecursively(childFrames[i].get());
}
void WebPagePrivate::enableCrossSiteXHR()
{
enableCrossSiteXHRRecursively(m_mainFrame);
}
void WebPage::enableCrossSiteXHR()
{
d->enableCrossSiteXHR();
}
void WebPagePrivate::addOriginAccessWhitelistEntry(const BlackBerry::Platform::String& sourceOrigin, const BlackBerry::Platform::String& destinationOrigin, bool allowDestinationSubdomains)
{
RefPtr<SecurityOrigin> source = SecurityOrigin::createFromString(sourceOrigin);
if (source->isUnique())
return;
KURL destination(KURL(), destinationOrigin);
SecurityPolicy::addOriginAccessWhitelistEntry(*source, destination.protocol(), destination.host(), allowDestinationSubdomains);
}
void WebPage::addOriginAccessWhitelistEntry(const BlackBerry::Platform::String& sourceOrigin, const BlackBerry::Platform::String& destinationOrigin, bool allowDestinationSubdomains)
{
d->addOriginAccessWhitelistEntry(sourceOrigin, destinationOrigin, allowDestinationSubdomains);
}
void WebPagePrivate::removeOriginAccessWhitelistEntry(const BlackBerry::Platform::String& sourceOrigin, const BlackBerry::Platform::String& destinationOrigin, bool allowDestinationSubdomains)
{
RefPtr<SecurityOrigin> source = SecurityOrigin::createFromString(sourceOrigin);
if (source->isUnique())
return;
KURL destination(KURL(), destinationOrigin);
SecurityPolicy::removeOriginAccessWhitelistEntry(*source, destination.protocol(), destination.host(), allowDestinationSubdomains);
}
void WebPage::removeOriginAccessWhitelistEntry(const BlackBerry::Platform::String& sourceOrigin, const BlackBerry::Platform::String& destinationOrigin, bool allowDestinationSubdomains)
{
d->removeOriginAccessWhitelistEntry(sourceOrigin, destinationOrigin, allowDestinationSubdomains);
}
void WebPagePrivate::setLoadState(LoadState state)
{
if (m_loadState == state)
return;
bool isFirstLoad = m_loadState == None;
// See RIM Bug #1068.
if (state == Finished && m_mainFrame && m_mainFrame->document())
m_mainFrame->document()->updateStyleIfNeeded();
// Dispatch the backingstore background color at important state changes.
m_backingStore->d->setWebPageBackgroundColor(m_mainFrame && m_mainFrame->view()
? m_mainFrame->view()->documentBackgroundColor()
: m_webSettings->backgroundColor());
m_loadState = state;
#if DEBUG_WEBPAGE_LOAD
BBLOG(Platform::LogLevelInfo, "WebPagePrivate::setLoadState %d", state);
#endif
switch (m_loadState) {
case Provisional:
if (isFirstLoad) {
// Paints the visible backingstore as white to prevent initial checkerboard on
// the first blit.
if (m_backingStore->d->renderVisibleContents() && !m_backingStore->d->isSuspended() && !m_backingStore->d->shouldDirectRenderingToWindow())
m_backingStore->d->blitVisibleContents();
}
break;
case Committed:
{
#if ENABLE(ACCELERATED_2D_CANVAS)
if (m_page->settings()->canvasUsesAcceleratedDrawing()) {
// Free GPU resources as we're on a new page.
// This will help us to free memory pressure.
SharedGraphicsContext3D::get()->makeContextCurrent();
GrContext* grContext = Platform::Graphics::getGrContext();
grContext->freeGpuResources();
}
#endif
#if USE(ACCELERATED_COMPOSITING)
releaseLayerResources();
#endif
// Suspend screen update to avoid ui thread blitting while resetting backingstore.
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
m_previousContentsSize = IntSize();
m_backingStore->d->resetRenderQueue();
m_backingStore->d->resetTiles(true /* resetBackground */);
m_backingStore->d->setScrollingOrZooming(false, false /* shouldBlit */);
m_shouldZoomToInitialScaleAfterLoadFinished = false;
m_userPerformedManualZoom = false;
m_userPerformedManualScroll = false;
m_shouldUseFixedDesktopMode = false;
if (m_resetVirtualViewportOnCommitted) // For DRT.
m_virtualViewportSize = IntSize();
if (m_webSettings->viewportWidth() > 0)
m_virtualViewportSize = IntSize(m_webSettings->viewportWidth(), m_defaultLayoutSize.height());
// Check if we have already process the meta viewport tag, this only happens on history navigation.
// For back/forward history navigation, we should only keep these previous values if the document
// has the meta viewport tag when the state is Committed in setLoadState.
static ViewportArguments defaultViewportArguments;
bool documentHasViewportArguments = false;
if (m_mainFrame && m_mainFrame->document() && m_mainFrame->document()->viewportArguments() != defaultViewportArguments)
documentHasViewportArguments = true;
if (!(m_didRestoreFromPageCache && documentHasViewportArguments)) {
m_viewportArguments = ViewportArguments();
m_userScalable = m_webSettings->isUserScalable();
resetScales();
// At the moment we commit a new load, set the viewport arguments
// to any fallback values. If there is a meta viewport in the
// content it will overwrite the fallback arguments soon.
dispatchViewportPropertiesDidChange(m_userViewportArguments);
} else {
Platform::IntSize virtualViewport = recomputeVirtualViewportFromViewportArguments();
m_webPage->setVirtualViewportSize(virtualViewport);
}
#if ENABLE(EVENT_MODE_METATAGS)
didReceiveCursorEventMode(ProcessedCursorEvents);
didReceiveTouchEventMode(ProcessedTouchEvents);
#endif
// Reset block zoom and reflow.
resetBlockZoom();
#if ENABLE(VIEWPORT_REFLOW)
toggleTextReflowIfEnabledForBlockZoomOnly();
#endif
// Notify InputHandler of state change.
m_inputHandler->setInputModeEnabled(false);
// Set the scroll to origin here and notify the client since we'll be
// zooming below without any real contents yet thus the contents size
// we report to the client could make our current scroll position invalid.
setScrollPosition(IntPoint::zero());
notifyTransformedScrollChanged();
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::None);
// Paints the visible backingstore as white. Note it is important we do
// this strictly after re-setting the scroll position to origin and resetting
// the scales otherwise the visible contents calculation is wrong and we
// can end up blitting artifacts instead. See: RIM Bug #401.
if (m_backingStore->d->renderVisibleContents() && !m_backingStore->d->isSuspended() && !m_backingStore->d->shouldDirectRenderingToWindow())
m_backingStore->d->blitVisibleContents();
// Update cursor status.
updateCursor();
break;
}
case Finished:
case Failed:
// Notify client of the initial zoom change.
m_client->scaleChanged();
m_backingStore->d->updateTiles(true /* updateVisible */, false /* immediate */);
break;
default:
break;
}
}
double WebPagePrivate::clampedScale(double scale) const
{
if (scale < minimumScale())
return minimumScale();
if (scale > maximumScale())
return maximumScale();
return scale;
}
bool WebPagePrivate::shouldZoomAboutPoint(double scale, const FloatPoint&, bool enforceScaleClamping, double* clampedScale)
{
if (!m_mainFrame->view())
return false;
if (enforceScaleClamping)
scale = this->clampedScale(scale);
ASSERT(clampedScale);
*clampedScale = scale;
if (currentScale() == scale) {
m_client->scaleChanged();
return false;
}
return true;
}
bool WebPagePrivate::zoomAboutPoint(double unclampedScale, const FloatPoint& anchor, bool enforceScaleClamping, bool forceRendering, bool isRestoringZoomLevel)
{
if (!isRestoringZoomLevel) {
// Clear any existing block zoom. (If we are restoring a saved zoom level on page load,
// there is guaranteed to be no existing block zoom and we don't want to clear m_shouldReflowBlock.)
resetBlockZoom();
}
// The reflow and block zoom stuff here needs to happen regardless of
// whether we shouldZoomAboutPoint.
#if ENABLE(VIEWPORT_REFLOW)
toggleTextReflowIfEnabledForBlockZoomOnly(m_shouldReflowBlock);
if (m_page->settings()->isTextReflowEnabled() && m_mainFrame->view())
setNeedsLayout();
#endif
double scale;
if (!shouldZoomAboutPoint(unclampedScale, anchor, enforceScaleClamping, &scale)) {
if (m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled) {
m_currentPinchZoomNode = 0;
m_anchorInNodeRectRatio = FloatPoint(-1, -1);
}
return false;
}
TransformationMatrix zoom;
zoom.scale(scale);
#if DEBUG_WEBPAGE_LOAD
if (loadState() < Finished)
BBLOG(Platform::LogLevelInfo, "WebPagePrivate::zoomAboutPoint scale %f anchor (%f, %f)", scale, anchor.x(), anchor.y());
#endif
// Our current scroll position in float.
FloatPoint scrollPosition = this->scrollPosition();
// Anchor offset from scroll position in float.
FloatPoint anchorOffset(anchor.x() - scrollPosition.x(), anchor.y() - scrollPosition.y());
// The horizontal scaling factor and vertical scaling factor should be equal
// to preserve aspect ratio of content.
ASSERT(m_transformationMatrix->m11() == m_transformationMatrix->m22());
// Need to invert the previous transform to anchor the viewport.
double inverseScale = scale / m_transformationMatrix->m11();
// Actual zoom.
*m_transformationMatrix = zoom;
// Suspend all screen updates to the backingstore.
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
updateViewportSize();
IntPoint newScrollPosition(IntPoint(max(0, static_cast<int>(roundf(anchor.x() - anchorOffset.x() / inverseScale))),
max(0, static_cast<int>(roundf(anchor.y() - anchorOffset.y() / inverseScale)))));
if (m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled) {
// This is a hack for email which has reflow always turned on.
m_mainFrame->view()->setNeedsLayout();
requestLayoutIfNeeded();
if (m_currentPinchZoomNode)
newScrollPosition = calculateReflowedScrollPosition(anchorOffset, scale == minimumScale() ? 1 : inverseScale);
m_currentPinchZoomNode = 0;
m_anchorInNodeRectRatio = FloatPoint(-1, -1);
}
setScrollPosition(newScrollPosition);
notifyTransformChanged();
bool isLoading = this->isLoading();
// We need to invalidate all tiles both visible and non-visible if we're loading.
m_backingStore->d->updateTiles(isLoading /* updateVisible */, false /* immediate */);
bool shouldRender = !isLoading || m_userPerformedManualZoom || forceRendering;
bool shouldClearVisibleZoom = isLoading && shouldRender;
if (shouldClearVisibleZoom) {
// If we are loading and rendering then we need to clear the render queue's
// visible zoom jobs as they will be irrelevant with the render below.
m_backingStore->d->clearVisibleZoom();
}
m_client->scaleChanged();
if (m_pendingOrientation != -1)
m_client->updateInteractionViews();
// Clear window to make sure there are no artifacts.
if (shouldRender) {
// Resume all screen updates to the backingstore and render+blit visible contents to screen.
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
} else {
// Resume all screen updates to the backingstore but do not blit to the screen because we not rendering.
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::None);
}
return true;
}
IntPoint WebPagePrivate::calculateReflowedScrollPosition(const FloatPoint& anchorOffset, double inverseScale)
{
// Should only be invoked when text reflow is enabled.
ASSERT(m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled);
int offsetY = 0;
int offsetX = 0;
IntRect nodeRect = rectForNode(m_currentPinchZoomNode.get());
if (m_currentPinchZoomNode->renderer() && m_anchorInNodeRectRatio.y() >= 0) {
offsetY = nodeRect.height() * m_anchorInNodeRectRatio.y();
if (m_currentPinchZoomNode->renderer()->isImage() && m_anchorInNodeRectRatio.x() > 0)
offsetX = nodeRect.width() * m_anchorInNodeRectRatio.x() - anchorOffset.x() / inverseScale;
}
IntRect reflowedRect = adjustRectOffsetForFrameOffset(nodeRect, m_currentPinchZoomNode.get());
return IntPoint(max(0, static_cast<int>(roundf(reflowedRect.x() + offsetX))),
max(0, static_cast<int>(roundf(reflowedRect.y() + offsetY - anchorOffset.y() / inverseScale))));
}
void WebPagePrivate::setNeedsLayout()
{
FrameView* view = m_mainFrame->view();
ASSERT(view);
view->setNeedsLayout();
}
void WebPagePrivate::requestLayoutIfNeeded() const
{
FrameView* view = m_mainFrame->view();
ASSERT(view);
view->updateLayoutAndStyleIfNeededRecursive();
ASSERT(!view->needsLayout());
}
IntPoint WebPagePrivate::scrollPosition() const
{
return m_backingStoreClient->scrollPosition();
}
IntPoint WebPagePrivate::maximumScrollPosition() const
{
return m_backingStoreClient->maximumScrollPosition();
}
void WebPagePrivate::setScrollPosition(const IntPoint& pos)
{
m_backingStoreClient->setScrollPosition(pos);
}
// Setting the scroll position is in transformed coordinates.
void WebPage::setDocumentScrollPosition(const Platform::IntPoint& documentScrollPosition)
{
WebCore::IntPoint scrollPosition = documentScrollPosition;
if (scrollPosition == d->scrollPosition())
return;
// If the user recently performed an event, this new scroll position
// could possibly be a result of that. Or not, this is just a heuristic.
if (currentTime() - d->m_lastUserEventTimestamp < manualScrollInterval)
d->m_userPerformedManualScroll = true;
d->m_backingStoreClient->setIsClientGeneratedScroll(true);
// UI thread can call BackingStorePrivate::setScrollingOrZooming(false) before WebKit thread calls WebPage::setScrollPosition(),
// in which case it will set ScrollableArea::m_constrainsScrollingToContentEdge to true earlier.
// We can cache ScrollableArea::m_constrainsScrollingToContentEdge and always set it to false before we set scroll position in
// WebKit thread to avoid scroll position clamping during scrolling, and restore it to what it was after that.
bool constrainsScrollingToContentEdge = d->m_mainFrame->view()->constrainsScrollingToContentEdge();
d->m_mainFrame->view()->setConstrainsScrollingToContentEdge(false);
d->setScrollPosition(scrollPosition);
d->m_mainFrame->view()->setConstrainsScrollingToContentEdge(constrainsScrollingToContentEdge);
d->m_backingStoreClient->setIsClientGeneratedScroll(false);
}
bool WebPagePrivate::shouldSendResizeEvent()
{
if (!m_mainFrame->document())
return false;
// PR#96865 : Provide an option to always send resize events, regardless of the loading
// status. The scenario for this are Sapphire applications which tend to
// maintain an open GET request to the server. This open GET results in
// webkit thinking that content is still arriving when at the application
// level it is considered fully loaded.
//
// NOTE: Care must be exercised in the use of this option, as it bypasses
// the sanity provided in 'isLoadingInAPISense()' below.
//
static const bool unrestrictedResizeEvents = Platform::Settings::instance()->unrestrictedResizeEvents();
if (unrestrictedResizeEvents)
return true;
// Don't send the resize event if the document is loading. Some pages automatically reload
// when the window is resized; Safari on iPhone often resizes the window while setting up its
// viewport. This obviously can cause problems.
DocumentLoader* documentLoader = m_mainFrame->loader()->documentLoader();
if (documentLoader && documentLoader->isLoadingInAPISense())
return false;
return true;
}
void WebPagePrivate::willDeferLoading()
{
m_deferredTasksTimer.stop();
m_client->willDeferLoading();
}
void WebPagePrivate::didResumeLoading()
{
if (!m_deferredTasks.isEmpty())
m_deferredTasksTimer.startOneShot(0);
m_client->didResumeLoading();
}
void WebPagePrivate::deferredTasksTimerFired(WebCore::Timer<WebPagePrivate>*)
{
ASSERT(!m_deferredTasks.isEmpty());
if (!m_deferredTasks.isEmpty())
return;
OwnPtr<DeferredTaskBase> task = m_deferredTasks[0].release();
m_deferredTasks.remove(0);
if (!m_deferredTasks.isEmpty())
m_deferredTasksTimer.startOneShot(0);
task->perform(this);
}
void WebPagePrivate::notifyInRegionScrollStopped()
{
if (m_inRegionScroller->d->isActive()) {
// Notify the client side to clear InRegion scrollable areas before we destroy them here.
std::vector<Platform::ScrollViewBase*> emptyInRegionScrollableAreas;
m_client->notifyInRegionScrollableAreasChanged(emptyInRegionScrollableAreas);
m_inRegionScroller->d->reset();
}
}
void WebPage::notifyInRegionScrollStopped()
{
d->notifyInRegionScrollStopped();
}
void WebPagePrivate::setHasInRegionScrollableAreas(bool b)
{
if (b != m_hasInRegionScrollableAreas)
m_hasInRegionScrollableAreas = b;
}
IntSize WebPagePrivate::viewportSize() const
{
return mapFromTransformed(transformedViewportSize());
}
IntSize WebPagePrivate::actualVisibleSize() const
{
return mapFromTransformed(transformedActualVisibleSize());
}
bool WebPagePrivate::hasVirtualViewport() const
{
return !m_virtualViewportSize.isEmpty();
}
void WebPagePrivate::updateViewportSize(bool setFixedReportedSize, bool sendResizeEvent)
{
// This checks to make sure we're not calling updateViewportSize
// during WebPagePrivate::init().
if (!m_backingStore)
return;
ASSERT(m_mainFrame->view());
IntSize visibleSize = actualVisibleSize();
if (setFixedReportedSize)
m_mainFrame->view()->setFixedReportedSize(visibleSize);
IntRect frameRect = IntRect(scrollPosition(), visibleSize);
if (frameRect != m_mainFrame->view()->frameRect()) {
m_mainFrame->view()->setFrameRect(frameRect);
m_mainFrame->view()->adjustViewSize();
#if ENABLE(FULLSCREEN_API)
// If we are in fullscreen video mode, and we change the FrameView::viewportRect,
// we need to adjust the media container to the new size.
if (m_fullscreenVideoNode) {
Document* document = m_fullscreenVideoNode->document();
ASSERT(document);
ASSERT(document->fullScreenRenderer());
int width = m_mainFrame->view()->visibleContentRect().size().width();
document->fullScreenRenderer()->style()->setWidth(Length(width, Fixed));
}
#endif
}
// We're going to need to send a resize event to JavaScript because
// innerWidth and innerHeight depend on fixed reported size.
// This is how we support mobile pages where JavaScript resizes
// the page in order to get around the fixed layout size, e.g.
// google maps when it detects a mobile user agent.
if (sendResizeEvent && shouldSendResizeEvent())
m_mainFrame->eventHandler()->sendResizeEvent();
// When the actual visible size changes, we also
// need to reposition fixed elements.
m_mainFrame->view()->repaintFixedElementsAfterScrolling();
}
FloatPoint WebPagePrivate::centerOfVisibleContentsRect() const
{
// The visible contents rect in float.
FloatRect visibleContentsRect = this->visibleContentsRect();
// The center of the visible contents rect in float.
return FloatPoint(visibleContentsRect.x() + visibleContentsRect.width() / 2.0,
visibleContentsRect.y() + visibleContentsRect.height() / 2.0);
}
IntRect WebPagePrivate::visibleContentsRect() const
{
return m_backingStoreClient->visibleContentsRect();
}
IntSize WebPagePrivate::contentsSize() const
{
if (!m_mainFrame->view())
return IntSize();
return m_backingStoreClient->contentsSize();
}
IntSize WebPagePrivate::absoluteVisibleOverflowSize() const
{
if (!m_mainFrame->contentRenderer())
return IntSize();
return IntSize(m_mainFrame->contentRenderer()->rightAbsoluteVisibleOverflow(), m_mainFrame->contentRenderer()->bottomAbsoluteVisibleOverflow());
}
void WebPagePrivate::contentsSizeChanged(const IntSize& contentsSize)
{
if (m_previousContentsSize == contentsSize)
return;
// This should only occur in the middle of layout so we set a flag here and
// handle it at the end of the layout.
m_contentsSizeChanged = true;
#if DEBUG_WEBPAGE_LOAD
BBLOG(Platform::LogLevelInfo, "WebPagePrivate::contentsSizeChanged %dx%d", contentsSize.width(), contentsSize.height());
#endif
}
void WebPagePrivate::layoutFinished()
{
if (!m_contentsSizeChanged && !m_overflowExceedsContentsSize)
return;
m_contentsSizeChanged = false; // Toggle to turn off notification again.
m_overflowExceedsContentsSize = false;
if (contentsSize().isEmpty())
return;
// The call to zoomToInitialScaleOnLoad can cause recursive layout when called from
// the middle of a layout, but the recursion is limited by detection code in
// setViewMode() and mitigation code in fixedLayoutSize().
if (didLayoutExceedMaximumIterations()) {
notifyTransformedContentsSizeChanged();
return;
}
// Temporarily save the m_previousContentsSize here before updating it (in
// notifyTransformedContentsSizeChanged()) so we can compare if our contents
// shrunk afterwards.
IntSize previousContentsSize = m_previousContentsSize;
m_nestedLayoutFinishedCount++;
if (shouldZoomToInitialScaleOnLoad()) {
zoomToInitialScaleOnLoad();
m_shouldZoomToInitialScaleAfterLoadFinished = false;
} else if (loadState() != None)
notifyTransformedContentsSizeChanged();
m_nestedLayoutFinishedCount--;
if (!m_nestedLayoutFinishedCount) {
// When the contents shrinks, there is a risk that we
// will be left at a scroll position that lies outside of the
// contents rect. Since we allow overscrolling and neglect
// to clamp overscroll in order to retain input focus (RIM Bug #414)
// we need to clamp somewhere, and this is where we know the
// contents size has changed.
if (contentsSize() != previousContentsSize) {
IntPoint newScrollPosition = scrollPosition();
if (contentsSize().height() < previousContentsSize.height()) {
IntPoint scrollPositionWithHeightShrunk = IntPoint(newScrollPosition.x(), maximumScrollPosition().y());
newScrollPosition = newScrollPosition.shrunkTo(scrollPositionWithHeightShrunk);
}
if (contentsSize().width() < previousContentsSize.width()) {
IntPoint scrollPositionWithWidthShrunk = IntPoint(maximumScrollPosition().x(), newScrollPosition.y());
newScrollPosition = newScrollPosition.shrunkTo(scrollPositionWithWidthShrunk);
}
if (newScrollPosition != scrollPosition()) {
setScrollPosition(newScrollPosition);
notifyTransformedScrollChanged();
}
}
}
}
void WebPagePrivate::zoomToInitialScaleOnLoad()
{
#if DEBUG_WEBPAGE_LOAD
BBLOG(Platform::LogLevelInfo, "WebPagePrivate::zoomToInitialScaleOnLoad");
#endif
bool needsLayout = false;
// If the contents width exceeds the viewport width set to desktop mode.
if (m_shouldUseFixedDesktopMode)
needsLayout = setViewMode(FixedDesktop);
else
needsLayout = setViewMode(Desktop);
if (needsLayout) {
// This can cause recursive layout...
setNeedsLayout();
}
if (contentsSize().isEmpty()) {
#if DEBUG_WEBPAGE_LOAD
BBLOG(Platform::LogLevelInfo, "WebPagePrivate::zoomToInitialScaleOnLoad content is empty!");
#endif
requestLayoutIfNeeded();
m_client->resetBitmapZoomScale(currentScale());
notifyTransformedContentsSizeChanged();
return;
}
bool performedZoom = false;
bool shouldZoom = !m_userPerformedManualZoom;
// If this is a back/forward type navigation, don't zoom to initial scale
// but instead let the HistoryItem's saved viewport reign supreme.
if (m_mainFrame && m_mainFrame->loader() && isBackForwardLoadType(m_mainFrame->loader()->loadType()))
shouldZoom = false;
if (shouldZoom && shouldZoomToInitialScaleOnLoad()) {
// Preserve at top and at left position, to avoid scrolling
// to a non top-left position for web page with viewport meta tag
// that specifies an initial-scale that is zoomed in.
FloatPoint anchor = centerOfVisibleContentsRect();
if (!scrollPosition().x())
anchor.setX(0);
if (!scrollPosition().y())
anchor.setY(0);
performedZoom = zoomAboutPoint(initialScale(), anchor);
}
// zoomAboutPoint above can also toggle setNeedsLayout and cause recursive layout...
requestLayoutIfNeeded();
if (!performedZoom) {
// We only notify if we didn't perform zoom, because zoom will notify on
// its own...
m_client->resetBitmapZoomScale(currentScale());
notifyTransformedContentsSizeChanged();
}
}
double WebPagePrivate::zoomToFitScale() const
{
int contentWidth = contentsSize().width();
int contentHeight = contentsSize().height();
double zoomToFitScale = contentWidth > 0.0 ? static_cast<double>(m_actualVisibleWidth) / contentWidth : 1.0;
if (contentHeight * zoomToFitScale < static_cast<double>(m_defaultLayoutSize.height()))
zoomToFitScale = contentHeight > 0 ? static_cast<double>(m_defaultLayoutSize.height()) / contentHeight : 1.0;
return std::max(zoomToFitScale, minimumZoomToFitScale);
}
double WebPagePrivate::initialScale() const
{
if (m_initialScale > 0.0)
return m_initialScale;
if (m_webSettings->isZoomToFitOnLoad())
return zoomToFitScale();
return 1.0;
}
double WebPage::initialScale() const
{
return d->initialScale();
}
void WebPage::initializeIconDataBase()
{
IconDatabaseClientBlackBerry::instance()->initIconDatabase(d->m_webSettings);
}
bool WebPage::isUserScalable() const
{
return d->isUserScalable();
}
void WebPage::setUserScalable(bool userScalable)
{
d->setUserScalable(userScalable);
}
double WebPage::currentScale() const
{
return d->currentScale();
}
void WebPage::setInitialScale(double initialScale)
{
d->setInitialScale(initialScale);
}
double WebPage::minimumScale() const
{
return d->minimumScale();
}
void WebPage::setMinimumScale(double minimumScale)
{
d->setMinimumScale(minimumScale);
}
void WebPage::setMaximumScale(double maximumScale)
{
d->setMaximumScale(maximumScale);
}
double WebPagePrivate::maximumScale() const
{
if (m_maximumScale >= zoomToFitScale() && m_maximumScale >= m_minimumScale)
return m_maximumScale;
return hasVirtualViewport() ? std::max<double>(zoomToFitScale(), 4.0) : 4.0;
}
double WebPage::maximumScale() const
{
return d->maximumScale();
}
void WebPagePrivate::resetScales()
{
TransformationMatrix identity;
*m_transformationMatrix = identity;
m_initialScale = m_webSettings->initialScale() > 0 ? m_webSettings->initialScale() : -1.0;
m_minimumScale = -1.0;
m_maximumScale = -1.0;
// We have to let WebCore know about updated framerect now that we've
// reset our scales. See: RIM Bug #401.
updateViewportSize();
}
IntPoint WebPagePrivate::transformedScrollPosition() const
{
return m_backingStoreClient->transformedScrollPosition();
}
IntPoint WebPagePrivate::transformedMaximumScrollPosition() const
{
return m_backingStoreClient->transformedMaximumScrollPosition();
}
IntSize WebPagePrivate::transformedActualVisibleSize() const
{
return IntSize(m_actualVisibleWidth, m_actualVisibleHeight);
}
Platform::ViewportAccessor* WebPage::webkitThreadViewportAccessor() const
{
return d->m_webkitThreadViewportAccessor;
}
Platform::IntSize WebPage::viewportSize() const
{
return d->transformedActualVisibleSize();
}
IntSize WebPagePrivate::transformedViewportSize() const
{
return Platform::Graphics::Screen::primaryScreen()->size();
}
IntRect WebPagePrivate::transformedVisibleContentsRect() const
{
// Usually this would be mapToTransformed(visibleContentsRect()), but
// that results in rounding errors because we already set the WebCore
// viewport size from our original transformedViewportSize().
// Instead, we only transform the scroll position and take the
// viewport size as it is, which ensures that e.g. blitting operations
// always cover the whole widget/screen.
return IntRect(transformedScrollPosition(), transformedViewportSize());
}
IntSize WebPagePrivate::transformedContentsSize() const
{
// mapToTransformed() functions use this method to crop their results,
// so we can't make use of them here. While we want rounding inside page
// boundaries to extend rectangles and round points, we need to crop the
// contents size to the floored values so that we don't try to display
// or report points that are not fully covered by the actual float-point
// contents rectangle.
const IntSize untransformedContentsSize = contentsSize();
const FloatPoint transformedBottomRight = m_transformationMatrix->mapPoint(
FloatPoint(untransformedContentsSize.width(), untransformedContentsSize.height()));
return IntSize(floorf(transformedBottomRight.x()), floorf(transformedBottomRight.y()));
}
IntPoint WebPagePrivate::mapFromContentsToViewport(const IntPoint& point) const
{
return m_backingStoreClient->mapFromContentsToViewport(point);
}
IntPoint WebPagePrivate::mapFromViewportToContents(const IntPoint& point) const
{
return m_backingStoreClient->mapFromViewportToContents(point);
}
IntRect WebPagePrivate::mapFromContentsToViewport(const IntRect& rect) const
{
return m_backingStoreClient->mapFromContentsToViewport(rect);
}
IntRect WebPagePrivate::mapFromViewportToContents(const IntRect& rect) const
{
return m_backingStoreClient->mapFromViewportToContents(rect);
}
IntPoint WebPagePrivate::mapFromTransformedContentsToTransformedViewport(const IntPoint& point) const
{
return m_backingStoreClient->mapFromTransformedContentsToTransformedViewport(point);
}
IntPoint WebPagePrivate::mapFromTransformedViewportToTransformedContents(const IntPoint& point) const
{
return m_backingStoreClient->mapFromTransformedViewportToTransformedContents(point);
}
IntRect WebPagePrivate::mapFromTransformedContentsToTransformedViewport(const IntRect& rect) const
{
return m_backingStoreClient->mapFromTransformedContentsToTransformedViewport(rect);
}
IntRect WebPagePrivate::mapFromTransformedViewportToTransformedContents(const IntRect& rect) const
{
return m_backingStoreClient->mapFromTransformedViewportToTransformedContents(rect);
}
// NOTE: PIXEL ROUNDING!
// Accurate back-and-forth rounding is not possible with information loss
// by integer points and sizes, so we always expand the resulting mapped
// float rectangles to the nearest integer. For points, we always use
// floor-rounding in mapToTransformed() so that we don't have to crop to
// the (floor'd) transformed contents size.
static inline IntPoint roundTransformedPoint(const FloatPoint &point)
{
// Maps by rounding half towards zero.
return IntPoint(static_cast<int>(floorf(point.x())), static_cast<int>(floorf(point.y())));
}
static inline IntPoint roundUntransformedPoint(const FloatPoint &point)
{
// Maps by rounding half away from zero.
return IntPoint(static_cast<int>(ceilf(point.x())), static_cast<int>(ceilf(point.y())));
}
IntPoint WebPagePrivate::mapToTransformed(const IntPoint& point) const
{
return roundTransformedPoint(m_transformationMatrix->mapPoint(FloatPoint(point)));
}
FloatPoint WebPagePrivate::mapToTransformedFloatPoint(const FloatPoint& point) const
{
return m_transformationMatrix->mapPoint(point);
}
IntPoint WebPagePrivate::mapFromTransformed(const IntPoint& point) const
{
return roundUntransformedPoint(m_transformationMatrix->inverse().mapPoint(FloatPoint(point)));
}
FloatPoint WebPagePrivate::mapFromTransformedFloatPoint(const FloatPoint& point) const
{
return m_transformationMatrix->inverse().mapPoint(point);
}
FloatRect WebPagePrivate::mapFromTransformedFloatRect(const FloatRect& rect) const
{
return m_transformationMatrix->inverse().mapRect(rect);
}
IntSize WebPagePrivate::mapToTransformed(const IntSize& size) const
{
return mapToTransformed(IntRect(IntPoint::zero(), size)).size();
}
IntSize WebPagePrivate::mapFromTransformed(const IntSize& size) const
{
return mapFromTransformed(IntRect(IntPoint::zero(), size)).size();
}
IntRect WebPagePrivate::mapToTransformed(const IntRect& rect) const
{
return enclosingIntRect(m_transformationMatrix->mapRect(FloatRect(rect)));
}
// Use this in conjunction with mapToTransformed(IntRect), in most cases.
void WebPagePrivate::clipToTransformedContentsRect(IntRect& rect) const
{
rect.intersect(IntRect(IntPoint::zero(), transformedContentsSize()));
}
IntRect WebPagePrivate::mapFromTransformed(const IntRect& rect) const
{
return enclosingIntRect(m_transformationMatrix->inverse().mapRect(FloatRect(rect)));
}
bool WebPagePrivate::transformedPointEqualsUntransformedPoint(const IntPoint& transformedPoint, const IntPoint& untransformedPoint)
{
// Scaling down is always more accurate than scaling up.
if (m_transformationMatrix->a() > 1.0)
return transformedPoint == mapToTransformed(untransformedPoint);
return mapFromTransformed(transformedPoint) == untransformedPoint;
}
void WebPagePrivate::notifyTransformChanged()
{
notifyTransformedContentsSizeChanged();
notifyTransformedScrollChanged();
m_backingStore->d->transformChanged();
}
void WebPagePrivate::notifyTransformedContentsSizeChanged()
{
// We mark here as the last reported content size we sent to the client.
m_previousContentsSize = contentsSize();
const IntSize size = transformedContentsSize();
m_backingStore->d->contentsSizeChanged(size);
m_client->contentsSizeChanged();
}
void WebPagePrivate::notifyTransformedScrollChanged()
{
const IntPoint pos = transformedScrollPosition();
m_backingStore->d->scrollChanged(pos);
m_client->scrollChanged();
}
bool WebPagePrivate::setViewMode(ViewMode mode)
{
if (!m_mainFrame->view())
return false;
m_viewMode = mode;
// If we're in the middle of a nested layout with a recursion count above
// some maximum threshold, then our algorithm for finding the minimum content
// width of a given page has become dependent on the visible width.
//
// We need to find some method to ensure that we don't experience excessive
// and even infinite recursion. This can even happen with valid html. The
// former can happen when we run into inline text with few candidates for line
// break. The latter can happen for instance if the page has a negative margin
// set against the right border. Note: this is valid by spec and can lead to
// a situation where there is no value for which the content width will ensure
// no horizontal scrollbar.
// Example: LayoutTests/css1/box_properties/margin.html
//
// In order to address such situations when we detect a recursion above some
// maximum threshold we snap our fixed layout size to a defined quantum increment.
// Eventually, either the content width will be satisfied to ensure no horizontal
// scrollbar or this increment will run into the maximum layout size and the
// recursion will necessarily end.
bool snapToIncrement = didLayoutExceedMaximumIterations();
IntSize currentSize = m_mainFrame->view()->fixedLayoutSize();
IntSize newSize = fixedLayoutSize(snapToIncrement);
if (currentSize == newSize)
return false;
// FIXME: Temp solution. We'll get back to this.
if (m_nestedLayoutFinishedCount) {
double widthChange = fabs(double(newSize.width() - currentSize.width()) / currentSize.width());
double heightChange = fabs(double(newSize.height() - currentSize.height()) / currentSize.height());
if (widthChange < 0.05 && heightChange < 0.05)
return false;
}
m_mainFrame->view()->setUseFixedLayout(useFixedLayout());
m_mainFrame->view()->setFixedLayoutSize(newSize);
return true; // Needs re-layout!
}
int WebPagePrivate::playerID() const
{
return m_client ? m_client->getInstanceId() : 0;
}
void WebPagePrivate::setCursor(PlatformCursor handle)
{
if (m_currentCursor.type() != handle.type()) {
m_currentCursor = handle;
m_client->cursorChanged(handle.type(), handle.url().c_str(), handle.hotspot());
}
}
Platform::NetworkStreamFactory* WebPagePrivate::networkStreamFactory()
{
return m_client->networkStreamFactory();
}
Platform::Graphics::Window* WebPagePrivate::platformWindow() const
{
return m_client->window();
}
void WebPagePrivate::setPreventsScreenDimming(bool keepAwake)
{
if (keepAwake) {
if (!m_preventIdleDimmingCount)
m_client->setPreventsScreenIdleDimming(true);
m_preventIdleDimmingCount++;
} else if (m_preventIdleDimmingCount > 0) {
m_preventIdleDimmingCount--;
if (!m_preventIdleDimmingCount)
m_client->setPreventsScreenIdleDimming(false);
} else
ASSERT_NOT_REACHED(); // SetPreventsScreenIdleDimming(false) called too many times.
}
void WebPagePrivate::showVirtualKeyboard(bool showKeyboard)
{
m_client->showVirtualKeyboard(showKeyboard);
}
void WebPagePrivate::ensureContentVisible(bool centerInView)
{
m_inputHandler->ensureFocusElementVisible(centerInView);
}
void WebPagePrivate::zoomToContentRect(const IntRect& rect)
{
// Don't scale if the user is not supposed to scale.
if (!isUserScalable())
return;
FloatPoint anchor = FloatPoint(rect.width() / 2.0 + rect.x(), rect.height() / 2.0 + rect.y());
IntSize viewSize = viewportSize();
// Calculate the scale required to scale that dimension to fit.
double scaleH = (double)viewSize.width() / (double)rect.width();
double scaleV = (double)viewSize.height() / (double)rect.height();
// Choose the smaller scale factor so that all of the content is visible.
zoomAboutPoint(min(scaleH, scaleV), anchor);
}
void WebPagePrivate::registerPlugin(PluginView* plugin, bool shouldRegister)
{
if (shouldRegister)
m_pluginViews.add(plugin);
else
m_pluginViews.remove(plugin);
}
#define FOR_EACH_PLUGINVIEW(pluginViews) \
HashSet<PluginView*>::const_iterator it = pluginViews.begin(); \
HashSet<PluginView*>::const_iterator last = pluginViews.end(); \
for (; it != last; ++it)
void WebPagePrivate::notifyPageOnLoad()
{
FOR_EACH_PLUGINVIEW(m_pluginViews)
(*it)->handleOnLoadEvent();
}
bool WebPagePrivate::shouldPluginEnterFullScreen(PluginView* plugin, const char* windowUniquePrefix)
{
return m_client->shouldPluginEnterFullScreen();
}
void WebPagePrivate::didPluginEnterFullScreen(PluginView* plugin, const char* windowUniquePrefix)
{
m_fullScreenPluginView = plugin;
m_client->didPluginEnterFullScreen();
if (!m_client->window())
return;
Platform::Graphics::Window::setTransparencyDiscardFilter(windowUniquePrefix);
m_client->window()->setSensitivityFullscreenOverride(true);
}
void WebPagePrivate::didPluginExitFullScreen(PluginView* plugin, const char* windowUniquePrefix)
{
m_fullScreenPluginView = 0;
m_client->didPluginExitFullScreen();
if (!m_client->window())
return;
Platform::Graphics::Window::setTransparencyDiscardFilter(0);
m_client->window()->setSensitivityFullscreenOverride(false);
}
void WebPagePrivate::onPluginStartBackgroundPlay(PluginView* plugin, const char* windowUniquePrefix)
{
m_client->onPluginStartBackgroundPlay();
}
void WebPagePrivate::onPluginStopBackgroundPlay(PluginView* plugin, const char* windowUniquePrefix)
{
m_client->onPluginStopBackgroundPlay();
}
bool WebPagePrivate::lockOrientation(bool landscape)
{
return m_client->lockOrientation(landscape);
}
void WebPagePrivate::unlockOrientation()
{
return m_client->unlockOrientation();
}
int WebPagePrivate::orientation() const
{
#if ENABLE(ORIENTATION_EVENTS)
return m_mainFrame->orientation();
#else
#error ORIENTATION_EVENTS must be defined.
// Or a copy of the orientation value will have to be stored in these objects.
#endif
}
double WebPagePrivate::currentZoomFactor() const
{
return currentScale();
}
int WebPagePrivate::showAlertDialog(WebPageClient::AlertType atype)
{
return m_client->showAlertDialog(atype);
}
bool WebPagePrivate::isActive() const
{
return m_client->isActive();
}
void WebPagePrivate::authenticationChallenge(const KURL& url, const ProtectionSpace& protectionSpace, const Credential& inputCredential)
{
AuthenticationChallengeManager* authmgr = AuthenticationChallengeManager::instance();
BlackBerry::Platform::String username;
BlackBerry::Platform::String password;
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
if (m_dumpRenderTree) {
Credential credential(inputCredential, inputCredential.persistence());
if (m_dumpRenderTree->didReceiveAuthenticationChallenge(credential))
authmgr->notifyChallengeResult(url, protectionSpace, AuthenticationChallengeSuccess, credential);
else
authmgr->notifyChallengeResult(url, protectionSpace, AuthenticationChallengeCancelled, inputCredential);
return;
}
#endif
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
if (m_webSettings->isCredentialAutofillEnabled() && !m_webSettings->isPrivateBrowsingEnabled())
credentialManager().autofillAuthenticationChallenge(protectionSpace, username, password);
#endif
bool isConfirmed = m_client->authenticationChallenge(protectionSpace.realm().characters(), protectionSpace.realm().length(), username, password);
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
Credential credential(username, password, CredentialPersistencePermanent);
if (m_webSettings->isCredentialAutofillEnabled() && !m_webSettings->isPrivateBrowsingEnabled() && isConfirmed)
credentialManager().saveCredentialIfConfirmed(this, CredentialTransformData(url, protectionSpace, credential));
#else
Credential credential(username, password, CredentialPersistenceNone);
#endif
if (isConfirmed)
authmgr->notifyChallengeResult(url, protectionSpace, AuthenticationChallengeSuccess, credential);
else
authmgr->notifyChallengeResult(url, protectionSpace, AuthenticationChallengeCancelled, inputCredential);
}
PageClientBlackBerry::SaveCredentialType WebPagePrivate::notifyShouldSaveCredential(bool isNew)
{
return static_cast<PageClientBlackBerry::SaveCredentialType>(m_client->notifyShouldSaveCredential(isNew));
}
void WebPagePrivate::syncProxyCredential(const WebCore::Credential& credential)
{
m_client->syncProxyCredential(credential.user(), credential.password());
}
void WebPagePrivate::notifyPopupAutofillDialog(const Vector<String>& candidates)
{
vector<BlackBerry::Platform::String> textItems;
for (size_t i = 0; i < candidates.size(); i++)
textItems.push_back(candidates[i]);
m_client->notifyPopupAutofillDialog(textItems);
}
void WebPagePrivate::notifyDismissAutofillDialog()
{
m_client->notifyDismissAutofillDialog();
}
bool WebPagePrivate::useFixedLayout() const
{
return true;
}
Platform::WebContext WebPagePrivate::webContext(TargetDetectionStrategy strategy)
{
Platform::WebContext context;
RefPtr<Node> node = contextNode(strategy);
m_currentContextNode = node;
if (!m_currentContextNode)
return context;
// Send an onContextMenu event to the current context ndoe and get the result. Since we've already figured out
// which node we want, we can send it directly to the node and not do a hit test. The onContextMenu event doesn't require
// mouse positions so we just set the position at (0,0)
PlatformMouseEvent mouseEvent(IntPoint(), IntPoint(), PlatformEvent::MouseMoved, 0, NoButton, TouchScreen);
if (m_currentContextNode->dispatchMouseEvent(mouseEvent, eventNames().contextmenuEvent, 0)) {
context.setFlag(Platform::WebContext::IsOnContextMenuPrevented);
return context;
}
// Unpress the mouse button if we're actually getting context.
EventHandler* eventHandler = focusedOrMainFrame()->eventHandler();
if (eventHandler->mousePressed())
eventHandler->setMousePressed(false);
requestLayoutIfNeeded();
bool nodeAllowSelectionOverride = false;
if (Node* linkNode = node->enclosingLinkEventParentOrSelf()) {
KURL href;
if (linkNode->isLink() && linkNode->hasAttributes()) {
if (Attribute* attribute = static_cast<Element*>(linkNode)->getAttributeItem(HTMLNames::hrefAttr))
href = linkNode->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(attribute->value()));
}
String pattern = findPatternStringForUrl(href);
if (!pattern.isEmpty())
context.setPattern(pattern);
if (!href.string().isEmpty()) {
context.setUrl(href.string());
// Links are non-selectable by default, but selection should be allowed
// providing the page is selectable, use the parent to determine it.
if (linkNode->parentNode() && linkNode->parentNode()->canStartSelection())
nodeAllowSelectionOverride = true;
}
}
if (node->isHTMLElement()) {
HTMLImageElement* imageElement = 0;
HTMLMediaElement* mediaElement = 0;
if (node->hasTagName(HTMLNames::imgTag))
imageElement = static_cast<HTMLImageElement*>(node.get());
else if (node->hasTagName(HTMLNames::areaTag))
imageElement = static_cast<HTMLAreaElement*>(node.get())->imageElement();
if (static_cast<HTMLElement*>(node.get())->isMediaElement())
mediaElement = static_cast<HTMLMediaElement*>(node.get());
if (imageElement && imageElement->renderer()) {
context.setFlag(Platform::WebContext::IsImage);
// FIXME: At the mean time, we only show "Save Image" when the image data is available.
if (CachedResource* cachedResource = imageElement->cachedImage()) {
if (cachedResource->isLoaded() && cachedResource->data()) {
String url = stripLeadingAndTrailingHTMLSpaces(imageElement->getAttribute(HTMLNames::srcAttr).string());
context.setSrc(node->document()->completeURL(url).string());
}
}
String alt = imageElement->altText();
if (!alt.isNull())
context.setAlt(alt);
}
if (mediaElement) {
if (mediaElement->hasAudio())
context.setFlag(Platform::WebContext::IsAudio);
if (mediaElement->hasVideo())
context.setFlag(Platform::WebContext::IsVideo);
String src = stripLeadingAndTrailingHTMLSpaces(mediaElement->getAttribute(HTMLNames::srcAttr).string());
context.setSrc(node->document()->completeURL(src).string());
}
}
if (node->isTextNode()) {
Text* curText = toText(node.get());
if (!curText->wholeText().isEmpty())
context.setText(curText->wholeText());
}
bool canStartSelection = node->canStartSelection();
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node->shadowAncestorNode());
if (DOMSupport::isTextBasedContentEditableElement(element)) {
if (!canStartSelection) {
// Input fields host node is by spec non-editable unless the field itself has content editable enabled.
// Enable selection if the shadow tree for the input field is selectable.
Node* nodeUnderFinger = m_touchEventHandler->lastFatFingersResult().isValid() ? m_touchEventHandler->lastFatFingersResult().node(FatFingersResult::ShadowContentAllowed) : 0;
if (nodeUnderFinger)
canStartSelection = nodeUnderFinger->canStartSelection();
}
context.setFlag(Platform::WebContext::IsInput);
if (element->hasTagName(HTMLNames::inputTag))
context.setFlag(Platform::WebContext::IsSingleLine);
if (DOMSupport::isPasswordElement(element))
context.setFlag(Platform::WebContext::IsPassword);
String elementText(DOMSupport::inputElementText(element));
if (!elementText.stripWhiteSpace().isEmpty())
context.setText(elementText);
}
}
if (!nodeAllowSelectionOverride && !canStartSelection)
context.resetFlag(Platform::WebContext::IsSelectable);
if (node->isFocusable())
context.setFlag(Platform::WebContext::IsFocusable);
// Walk up the node tree looking for our custom webworks context attribute.
while (node) {
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node->shadowAncestorNode());
String webWorksContext(DOMSupport::webWorksContext(element));
if (!webWorksContext.stripWhiteSpace().isEmpty()) {
context.setFlag(Platform::WebContext::IsWebWorksContext);
context.setWebWorksContext(webWorksContext);
break;
}
}
node = node->parentNode();
}
return context;
}
Platform::WebContext WebPage::webContext(TargetDetectionStrategy strategy) const
{
return d->webContext(strategy);
}
void WebPagePrivate::updateCursor()
{
int buttonMask = 0;
if (m_lastMouseEvent.button() == LeftButton)
buttonMask = Platform::MouseEvent::ScreenLeftMouseButton;
else if (m_lastMouseEvent.button() == MiddleButton)
buttonMask = Platform::MouseEvent::ScreenMiddleMouseButton;
else if (m_lastMouseEvent.button() == RightButton)
buttonMask = Platform::MouseEvent::ScreenRightMouseButton;
BlackBerry::Platform::MouseEvent event(buttonMask, buttonMask, mapToTransformed(m_lastMouseEvent.position()), mapToTransformed(m_lastMouseEvent.globalPosition()), 0, 0);
m_webPage->mouseEvent(event);
}
IntSize WebPagePrivate::fixedLayoutSize(bool snapToIncrement) const
{
if (hasVirtualViewport())
return m_virtualViewportSize;
const int defaultLayoutWidth = m_defaultLayoutSize.width();
const int defaultLayoutHeight = m_defaultLayoutSize.height();
int minWidth = defaultLayoutWidth;
int maxWidth = DEFAULT_MAX_LAYOUT_WIDTH;
int maxHeight = DEFAULT_MAX_LAYOUT_HEIGHT;
// If the load state is none then we haven't actually got anything yet, but we need to layout
// the entire page so that the user sees the entire page (unrendered) instead of just part of it.
if (m_loadState == None)
return IntSize(defaultLayoutWidth, defaultLayoutHeight);
if (m_viewMode == FixedDesktop) {
int width = maxWidth;
// if the defaultLayoutHeight is at minimum, it probably was set as 0
// and clamped, meaning it's effectively not set. (Even if it happened
// to be set exactly to the minimum, it's too small to be useful.) So
// ignore it.
int height;
if (defaultLayoutHeight <= minimumLayoutSize.height())
height = maxHeight;
else
height = ceilf(static_cast<float>(width) / static_cast<float>(defaultLayoutWidth) * static_cast<float>(defaultLayoutHeight));
return IntSize(width, height);
}
if (m_viewMode == Desktop) {
// If we detect an overflow larger than the contents size then use that instead since
// it'll still be clamped by the maxWidth below...
int width = std::max(absoluteVisibleOverflowSize().width(), contentsSize().width());
if (m_pendingOrientation != -1 && !m_nestedLayoutFinishedCount)
width = 0;
if (snapToIncrement) {
// Snap to increments of defaultLayoutWidth / 2.0.
float factor = static_cast<float>(width) / (defaultLayoutWidth / 2.0);
factor = ceilf(factor);
width = (defaultLayoutWidth / 2.0) * factor;
}
if (width < minWidth)
width = minWidth;
if (width > maxWidth)
width = maxWidth;
int height = ceilf(static_cast<float>(width) / static_cast<float>(defaultLayoutWidth) * static_cast<float>(defaultLayoutHeight));
return IntSize(width, height);
}
ASSERT_NOT_REACHED();
return IntSize(defaultLayoutWidth, defaultLayoutHeight);
}
BackingStoreClient* WebPagePrivate::backingStoreClient() const
{
return m_backingStoreClient;
}
void WebPagePrivate::clearDocumentData(const Document* documentGoingAway)
{
ASSERT(documentGoingAway);
if (m_currentContextNode && m_currentContextNode->document() == documentGoingAway)
m_currentContextNode = 0;
if (m_currentPinchZoomNode && m_currentPinchZoomNode->document() == documentGoingAway)
m_currentPinchZoomNode = 0;
if (m_currentBlockZoomAdjustedNode && m_currentBlockZoomAdjustedNode->document() == documentGoingAway)
m_currentBlockZoomAdjustedNode = 0;
if (m_inRegionScroller->d->isActive())
m_inRegionScroller->d->clearDocumentData(documentGoingAway);
if (documentGoingAway->frame())
m_inputHandler->frameUnloaded(documentGoingAway->frame());
Node* nodeUnderFatFinger = m_touchEventHandler->lastFatFingersResult().node();
if (nodeUnderFatFinger && nodeUnderFatFinger->document() == documentGoingAway)
m_touchEventHandler->resetLastFatFingersResult();
// NOTE: m_fullscreenVideoNode, m_fullScreenPluginView and m_pluginViews
// are cleared in other methods already.
}
typedef bool (*PredicateFunction)(RenderLayer*);
static bool isPositionedContainer(RenderLayer* layer)
{
RenderObject* o = layer->renderer();
return o->isRenderView() || o->isOutOfFlowPositioned() || o->isRelPositioned() || layer->hasTransform();
}
static bool isFixedPositionedContainer(RenderLayer* layer)
{
RenderObject* o = layer->renderer();
return o->isRenderView() || (o->isOutOfFlowPositioned() && o->style()->position() == FixedPosition);
}
static RenderLayer* findAncestorOrSelfNotMatching(PredicateFunction predicate, RenderLayer* layer)
{
RenderLayer* curr = layer;
while (curr && !predicate(curr))
curr = curr->parent();
return curr;
}
RenderLayer* WebPagePrivate::enclosingFixedPositionedAncestorOrSelfIfFixedPositioned(RenderLayer* layer)
{
return findAncestorOrSelfNotMatching(&isFixedPositionedContainer, layer);
}
RenderLayer* WebPagePrivate::enclosingPositionedAncestorOrSelfIfPositioned(RenderLayer* layer)
{
return findAncestorOrSelfNotMatching(&isPositionedContainer, layer);
}
static inline Frame* frameForNode(Node* node)
{
Node* origNode = node;
for (; node; node = node->parentNode()) {
if (RenderObject* renderer = node->renderer()) {
if (renderer->isRenderView()) {
if (FrameView* view = toRenderView(renderer)->frameView()) {
if (Frame* frame = view->frame())
return frame;
}
}
if (renderer->isWidget()) {
Widget* widget = toRenderWidget(renderer)->widget();
if (widget && widget->isFrameView()) {
if (Frame* frame = static_cast<FrameView*>(widget)->frame())
return frame;
}
}
}
}
for (node = origNode; node; node = node->parentNode()) {
if (Document* doc = node->document()) {
if (Frame* frame = doc->frame())
return frame;
}
}
return 0;
}
static IntRect getNodeWindowRect(Node* node)
{
if (Frame* frame = frameForNode(node)) {
if (FrameView* view = frame->view())
return view->contentsToWindow(node->getRect());
}
ASSERT_NOT_REACHED();
return IntRect();
}
IntRect WebPagePrivate::getRecursiveVisibleWindowRect(ScrollView* view, bool noClipOfMainFrame)
{
ASSERT(m_mainFrame);
// Don't call this function asking to not clip the main frame providing only
// the main frame. All that can be returned is the content rect which
// isn't what this function is for.
if (noClipOfMainFrame && view == m_mainFrame->view()) {
ASSERT_NOT_REACHED();
return IntRect(IntPoint::zero(), view->contentsSize());
}
IntRect visibleWindowRect(view->contentsToWindow(view->visibleContentRect(false)));
if (view->parent() && !(noClipOfMainFrame && view->parent() == m_mainFrame->view())) {
// Intersect with parent visible rect.
visibleWindowRect.intersect(getRecursiveVisibleWindowRect(view->parent(), noClipOfMainFrame));
}
return visibleWindowRect;
}
void WebPagePrivate::assignFocus(Platform::FocusDirection direction)
{
ASSERT((int) Platform::FocusDirectionNone == (int) FocusDirectionNone);
ASSERT((int) Platform::FocusDirectionForward == (int) FocusDirectionForward);
ASSERT((int) Platform::FocusDirectionBackward == (int) FocusDirectionBackward);
// First we clear the focus, since we want to focus either initial or the last
// focusable element in the webpage (according to the TABINDEX), or simply clear
// the focus.
clearFocusNode();
switch (direction) {
case FocusDirectionForward:
case FocusDirectionBackward:
m_page->focusController()->setInitialFocus((FocusDirection) direction, 0);
break;
case FocusDirectionNone:
break;
default:
ASSERT_NOT_REACHED();
}
}
void WebPage::assignFocus(Platform::FocusDirection direction)
{
if (d->m_page->defersLoading())
return;
d->assignFocus(direction);
}
Platform::IntRect WebPagePrivate::focusNodeRect()
{
Frame* frame = focusedOrMainFrame();
if (!frame)
return Platform::IntRect();
Document* doc = frame->document();
FrameView* view = frame->view();
if (!doc || !view || view->needsLayout())
return Platform::IntRect();
IntRect focusRect = rectForNode(doc->focusedNode());
focusRect = adjustRectOffsetForFrameOffset(focusRect, doc->focusedNode());
focusRect = mapToTransformed(focusRect);
clipToTransformedContentsRect(focusRect);
return focusRect;
}
PassRefPtr<Node> WebPagePrivate::contextNode(TargetDetectionStrategy strategy)
{
EventHandler* eventHandler = focusedOrMainFrame()->eventHandler();
const FatFingersResult lastFatFingersResult = m_touchEventHandler->lastFatFingersResult();
bool isTouching = lastFatFingersResult.isValid() && strategy == RectBased;
// Check if we're using LinkToLink and the user is not touching the screen.
if (m_webSettings->doesGetFocusNodeContext() && !isTouching) {
RefPtr<Node> node;
node = m_page->focusController()->focusedOrMainFrame()->document()->focusedNode();
if (node) {
IntRect visibleRect = IntRect(IntPoint(), actualVisibleSize());
if (!visibleRect.intersects(getNodeWindowRect(node.get())))
return 0;
}
return node.release();
}
// Check for text input.
if (isTouching && lastFatFingersResult.isTextInput())
return lastFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
IntPoint contentPos;
if (isTouching)
contentPos = lastFatFingersResult.adjustedPosition();
else
contentPos = mapFromViewportToContents(m_lastMouseEvent.position());
if (strategy == RectBased) {
FatFingersResult result = FatFingers(this, lastFatFingersResult.adjustedPosition(), FatFingers::Text).findBestPoint();
return result.node(FatFingersResult::ShadowContentNotAllowed);
}
HitTestResult result = eventHandler->hitTestResultAtPoint(contentPos, false /*allowShadowContent*/);
return result.innerNode();
}
static inline int distanceBetweenPoints(IntPoint p1, IntPoint p2)
{
// Change int to double, because (dy * dy) can cause int overflow in reality, e.g, (-46709 * -46709).
double dx = static_cast<double>(p1.x() - p2.x());
double dy = static_cast<double>(p1.y() - p2.y());
return sqrt((dx * dx) + (dy * dy));
}
Node* WebPagePrivate::bestNodeForZoomUnderPoint(const IntPoint& documentPoint)
{
IntRect clickRect(documentPoint.x() - blockClickRadius, documentPoint.y() - blockClickRadius, 2 * blockClickRadius, 2 * blockClickRadius);
Node* originalNode = nodeForZoomUnderPoint(documentPoint);
if (!originalNode)
return 0;
Node* node = bestChildNodeForClickRect(originalNode, clickRect);
return node ? adjustedBlockZoomNodeForZoomAndExpandingRatioLimits(node) : adjustedBlockZoomNodeForZoomAndExpandingRatioLimits(originalNode);
}
Node* WebPagePrivate::bestChildNodeForClickRect(Node* parentNode, const IntRect& clickRect)
{
if (!parentNode)
return 0;
int bestDistance = std::numeric_limits<int>::max();
Node* node = parentNode->firstChild();
Node* bestNode = 0;
for (; node; node = node->nextSibling()) {
IntRect rect = rectForNode(node);
if (!clickRect.intersects(rect))
continue;
int distance = distanceBetweenPoints(rect.center(), clickRect.center());
Node* bestChildNode = bestChildNodeForClickRect(node, clickRect);
if (bestChildNode) {
IntRect bestChildRect = rectForNode(bestChildNode);
int bestChildDistance = distanceBetweenPoints(bestChildRect.center(), clickRect.center());
if (bestChildDistance < distance && bestChildDistance < bestDistance) {
bestNode = bestChildNode;
bestDistance = bestChildDistance;
} else {
if (distance < bestDistance) {
bestNode = node;
bestDistance = distance;
}
}
} else {
if (distance < bestDistance) {
bestNode = node;
bestDistance = distance;
}
}
}
return bestNode;
}
double WebPagePrivate::maxBlockZoomScale() const
{
return std::min(maximumBlockZoomScale, maximumScale());
}
Node* WebPagePrivate::nodeForZoomUnderPoint(const IntPoint& point)
{
if (!m_mainFrame)
return 0;
HitTestResult result = m_mainFrame->eventHandler()->hitTestResultAtPoint(mapFromTransformed(point), false);
Node* node = result.innerNonSharedNode();
if (!node)
return 0;
RenderObject* renderer = node->renderer();
while (!renderer) {
node = node->parentNode();
renderer = node->renderer();
}
return node;
}
Node* WebPagePrivate::adjustedBlockZoomNodeForZoomAndExpandingRatioLimits(Node* node)
{
Node* initialNode = node;
RenderObject* renderer = node->renderer();
bool acceptableNodeSize = newScaleForBlockZoomRect(rectForNode(node), 1.0, 0) < maxBlockZoomScale();
IntSize actualVisibleSize = this->actualVisibleSize();
while (!renderer || !acceptableNodeSize) {
node = node->parentNode();
IntRect nodeRect = rectForNode(node);
// Don't choose a node if the width of the node size is very close to the width of the actual visible size,
// as block zoom can do nothing on such kind of node.
if (!node || static_cast<double>(actualVisibleSize.width() - nodeRect.width()) / actualVisibleSize.width() < minimumExpandingRatio)
return initialNode;
renderer = node->renderer();
acceptableNodeSize = newScaleForBlockZoomRect(rectForNode(node), 1.0, 0) < maxBlockZoomScale();
}
return node;
}
bool WebPagePrivate::compareNodesForBlockZoom(Node* n1, Node* n2)
{
if (!n1 || !n2)
return false;
return (n2 == n1) || n2->isDescendantOf(n1);
}
double WebPagePrivate::newScaleForBlockZoomRect(const IntRect& rect, double oldScale, double margin)
{
if (rect.isEmpty())
return std::numeric_limits<double>::max();
ASSERT(rect.width() + margin);
double newScale = oldScale * static_cast<double>(transformedActualVisibleSize().width()) / (rect.width() + margin);
return newScale;
}
IntRect WebPagePrivate::rectForNode(Node* node)
{
if (!node)
return IntRect();
RenderObject* renderer = node->renderer();
if (!renderer)
return IntRect();
// Return rect in un-transformed content coordinates.
IntRect blockRect;
// FIXME: Ensure this works with iframes.
if (m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled && renderer->isText()) {
RenderBlock* renderBlock = renderer->containingBlock();
int xOffset = 0;
int yOffset = 0;
while (!renderBlock->isRoot()) {
xOffset += renderBlock->x();
yOffset += renderBlock->y();
renderBlock = renderBlock->containingBlock();
}
const RenderText* renderText = toRenderText(renderer);
IntRect linesBox = renderText->linesBoundingBox();
blockRect = IntRect(xOffset + linesBox.x(), yOffset + linesBox.y(), linesBox.width(), linesBox.height());
} else
blockRect = renderer->absoluteClippedOverflowRect();
if (renderer->isText()) {
RenderBlock* rb = renderer->containingBlock();
// Inefficient? Way to find width when floats intersect a block.
int blockWidth = 0;
int lineCount = rb->lineCount();
for (int i = 0; i < lineCount; i++)
blockWidth = max(blockWidth, rb->availableLogicalWidthForLine(i, false));
blockRect.setWidth(blockWidth);
blockRect.setX(blockRect.x() + rb->logicalLeftOffsetForLine(1, false));
}
// Strip off padding.
if (renderer->style()->hasPadding()) {
blockRect.setX(blockRect.x() + renderer->style()->paddingLeft().value());
blockRect.setY(blockRect.y() + renderer->style()->paddingTop().value());
blockRect.setWidth(blockRect.width() - renderer->style()->paddingRight().value());
blockRect.setHeight(blockRect.height() - renderer->style()->paddingBottom().value());
}
return blockRect;
}
IntPoint WebPagePrivate::frameOffset(const Frame* frame) const
{
ASSERT(frame);
// FIXME: This function can be called when page is being destroyed and JS triggers selection change.
// We could break the call chain at upper levels, but I think it is better to check the frame pointer
// here because the pointer is explicitly cleared in WebPage::destroy().
if (!mainFrame())
return IntPoint();
// Convert 0,0 in the frame's coordinate system to window coordinates to
// get the frame's global position, and return this position in the main
// frame's coordinates. (So the main frame's coordinates will be 0,0.)
return mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(IntPoint::zero()));
}
IntRect WebPagePrivate::adjustRectOffsetForFrameOffset(const IntRect& rect, const Node* node)
{
if (!node)
return rect;
// Adjust the offset of the rect if it is in an iFrame/frame or set of iFrames/frames.
// FIXME: can we just use frameOffset instead of this big routine?
const Node* tnode = node;
IntRect adjustedRect = rect;
do {
Frame* frame = tnode->document()->frame();
if (!frame)
continue;
Node* ownerNode = static_cast<Node*>(frame->ownerElement());
tnode = ownerNode;
if (ownerNode && (ownerNode->hasTagName(HTMLNames::iframeTag) || ownerNode->hasTagName(HTMLNames::frameTag))) {
IntRect iFrameRect;
do {
iFrameRect = rectForNode(ownerNode);
adjustedRect.move(iFrameRect.x(), iFrameRect.y());
adjustedRect.intersect(iFrameRect);
ownerNode = ownerNode->parentNode();
} while (iFrameRect.isEmpty() && ownerNode);
} else
break;
} while (tnode = tnode->parentNode());
return adjustedRect;
}
IntRect WebPagePrivate::blockZoomRectForNode(Node* node)
{
if (!node || contentsSize().isEmpty())
return IntRect();
Node* tnode = node;
m_currentBlockZoomAdjustedNode = tnode;
IntRect blockRect = rectForNode(tnode);
IntRect originalRect = blockRect;
int originalArea = originalRect.width() * originalRect.height();
int pageArea = contentsSize().width() * contentsSize().height();
double blockToPageRatio = static_cast<double>(pageArea - originalArea) / pageArea;
double blockExpansionRatio = 5.0 * blockToPageRatio * blockToPageRatio;
if (!tnode->hasTagName(HTMLNames::imgTag) && !tnode->hasTagName(HTMLNames::inputTag) && !tnode->hasTagName(HTMLNames::textareaTag)) {
while (tnode = tnode->parentNode()) {
ASSERT(tnode);
IntRect tRect = rectForNode(tnode);
int tempBlockArea = tRect.width() * tRect.height();
// Don't expand the block if it will be too large relative to the content.
if (static_cast<double>(pageArea - tempBlockArea) / pageArea < minimumExpandingRatio)
break;
if (tRect.isEmpty())
continue; // No renderer.
if (tempBlockArea < 1.1 * originalArea)
continue; // The size of this parent is very close to the child, no need to go to this parent.
// Don't expand the block if the parent node size is already almost the size of actual visible size.
IntSize actualSize = actualVisibleSize();
if (static_cast<double>(actualSize.width() - tRect.width()) / actualSize.width() < minimumExpandingRatio)
break;
if (tempBlockArea < blockExpansionRatio * originalArea) {
blockRect = tRect;
m_currentBlockZoomAdjustedNode = tnode;
} else
break;
}
}
blockRect = adjustRectOffsetForFrameOffset(blockRect, node);
blockRect = mapToTransformed(blockRect);
clipToTransformedContentsRect(blockRect);
return blockRect;
}
// This function should not be called directly.
// It is called after the animation ends (see above).
void WebPagePrivate::zoomBlock()
{
if (!m_mainFrame)
return;
IntPoint anchor(roundUntransformedPoint(m_finalBlockPoint));
bool willUseTextReflow = false;
#if ENABLE(VIEWPORT_REFLOW)
willUseTextReflow = m_webPage->settings()->textReflowMode() != WebSettings::TextReflowDisabled;
toggleTextReflowIfEnabledForBlockZoomOnly(m_shouldReflowBlock);
setNeedsLayout();
#endif
TransformationMatrix zoom;
zoom.scale(m_blockZoomFinalScale);
*m_transformationMatrix = zoom;
m_client->resetBitmapZoomScale(m_blockZoomFinalScale);
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
updateViewportSize();
#if ENABLE(VIEWPORT_REFLOW)
requestLayoutIfNeeded();
if (willUseTextReflow && m_shouldReflowBlock) {
IntRect reflowedRect = rectForNode(m_currentBlockZoomAdjustedNode.get());
reflowedRect = adjustRectOffsetForFrameOffset(reflowedRect, m_currentBlockZoomAdjustedNode.get());
reflowedRect.move(roundTransformedPoint(m_finalBlockPointReflowOffset).x(), roundTransformedPoint(m_finalBlockPointReflowOffset).y());
RenderObject* renderer = m_currentBlockZoomAdjustedNode->renderer();
IntPoint topLeftPoint(reflowedRect.location());
if (renderer && renderer->isText()) {
ETextAlign textAlign = renderer->style()->textAlign();
IntPoint textAnchor;
switch (textAlign) {
case CENTER:
case WEBKIT_CENTER:
textAnchor = IntPoint(reflowedRect.x() + (reflowedRect.width() - actualVisibleSize().width()) / 2, topLeftPoint.y());
break;
case LEFT:
case WEBKIT_LEFT:
textAnchor = topLeftPoint;
break;
case RIGHT:
case WEBKIT_RIGHT:
textAnchor = IntPoint(reflowedRect.x() + reflowedRect.width() - actualVisibleSize().width(), topLeftPoint.y());
break;
case TAAUTO:
case JUSTIFY:
default:
if (renderer->style()->isLeftToRightDirection())
textAnchor = topLeftPoint;
else
textAnchor = IntPoint(reflowedRect.x() + reflowedRect.width() - actualVisibleSize().width(), topLeftPoint.y());
break;
}
setScrollPosition(textAnchor);
} else {
renderer->style()->isLeftToRightDirection()
? setScrollPosition(topLeftPoint)
: setScrollPosition(IntPoint(reflowedRect.x() + reflowedRect.width() - actualVisibleSize().width(), topLeftPoint.y()));
}
} else if (willUseTextReflow) {
IntRect finalRect = rectForNode(m_currentBlockZoomAdjustedNode.get());
finalRect = adjustRectOffsetForFrameOffset(finalRect, m_currentBlockZoomAdjustedNode.get());
setScrollPosition(IntPoint(0, finalRect.y() + m_finalBlockPointReflowOffset.y()));
resetBlockZoom();
}
#endif
if (!willUseTextReflow) {
setScrollPosition(anchor);
if (!m_shouldReflowBlock)
resetBlockZoom();
}
notifyTransformChanged();
m_client->scaleChanged();
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
}
void WebPage::blockZoomAnimationFinished()
{
d->zoomBlock();
}
void WebPage::resetBlockZoom()
{
d->resetBlockZoom();
}
void WebPagePrivate::resetBlockZoom()
{
m_currentBlockZoomNode = 0;
m_currentBlockZoomAdjustedNode = 0;
m_shouldReflowBlock = false;
}
void WebPage::destroyWebPageCompositor()
{
#if USE(ACCELERATED_COMPOSITING)
// Destroy the layer renderer in a sync command before we destroy the backing store,
// to flush any pending compositing messages on the compositing thread.
// The backing store is indirectly deleted by the 'detachFromParent' call below.
d->syncDestroyCompositorOnCompositingThread();
#endif
}
void WebPage::destroy()
{
// TODO: need to verify if this call needs to be made before calling
// Close the Inspector to resume the JS engine if it's paused.
disableWebInspector();
// WebPage::destroyWebPageCompositor()
d->m_backingStore->d->suspendScreenAndBackingStoreUpdates();
// Close the backforward list and release the cached pages.
d->m_page->backForward()->close();
pageCache()->releaseAutoreleasedPagesNow();
FrameLoader* loader = d->m_mainFrame->loader();
// Set m_mainFrame to 0 to avoid calls back in to the backingstore during webpage deletion.
d->m_mainFrame = 0;
if (loader)
loader->detachFromParent();
deleteGuardedObject(this);
}
WebPageClient* WebPage::client() const
{
return d->m_client;
}
int WebPage::backForwardListLength() const
{
return d->m_page->getHistoryLength();
}
bool WebPage::canGoBackOrForward(int delta) const
{
return d->m_page->canGoBackOrForward(delta);
}
bool WebPage::goBackOrForward(int delta)
{
if (d->m_page->canGoBackOrForward(delta)) {
d->m_page->goBackOrForward(delta);
return true;
}
return false;
}
void WebPage::goToBackForwardEntry(BackForwardId id)
{
HistoryItem* item = historyItemFromBackForwardId(id);
ASSERT(item);
d->m_page->goToItem(item, FrameLoadTypeIndexedBackForward);
}
void WebPage::reload()
{
d->m_mainFrame->loader()->reload(/* bypassCache */ true);
}
void WebPage::reloadFromCache()
{
d->m_mainFrame->loader()->reload(/* bypassCache */ false);
}
WebSettings* WebPage::settings() const
{
return d->m_webSettings;
}
WebCookieJar* WebPage::cookieJar() const
{
if (!d->m_cookieJar)
d->m_cookieJar = new WebCookieJar();
return d->m_cookieJar;
}
bool WebPage::isVisible() const
{
return d->m_visible;
}
#if ENABLE(PAGE_VISIBILITY_API)
class DeferredTaskSetPageVisibilityState: public DeferredTask<&WebPagePrivate::m_wouldSetPageVisibilityState> {
public:
explicit DeferredTaskSetPageVisibilityState(WebPagePrivate* webPagePrivate)
: DeferredTaskType(webPagePrivate)
{
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->setPageVisibilityState();
}
};
void WebPagePrivate::setPageVisibilityState()
{
if (m_page->defersLoading())
m_deferredTasks.append(adoptPtr(new DeferredTaskSetPageVisibilityState(this)));
else {
DeferredTaskSetPageVisibilityState::finishOrCancel(this);
static bool s_initialVisibilityState = true;
m_page->setVisibilityState(m_visible && m_activationState == ActivationActive ? PageVisibilityStateVisible : PageVisibilityStateHidden, s_initialVisibilityState);
s_initialVisibilityState = false;
}
}
#endif
void WebPagePrivate::setVisible(bool visible)
{
if (visible != m_visible) {
if (visible) {
if (m_mainFrame)
m_mainFrame->animation()->resumeAnimations();
if (m_page->scriptedAnimationsSuspended())
m_page->resumeScriptedAnimations();
} else {
if (m_mainFrame)
m_mainFrame->animation()->suspendAnimations();
if (!m_page->scriptedAnimationsSuspended())
m_page->suspendScriptedAnimations();
}
m_visible = visible;
}
#if ENABLE(PAGE_VISIBILITY_API)
setPageVisibilityState();
#endif
}
void WebPage::setVisible(bool visible)
{
if (d->m_visible == visible)
return;
d->setVisible(visible);
AuthenticationChallengeManager::instance()->pageVisibilityChanged(d, visible);
if (!visible) {
d->suspendBackingStore();
// Remove this WebPage from the visible pages list.
size_t foundIndex = visibleWebPages()->find(this);
if (foundIndex != WTF::notFound)
visibleWebPages()->remove(foundIndex);
// Return the backing store to the last visible WebPage.
if (BackingStorePrivate::currentBackingStoreOwner() == this && !visibleWebPages()->isEmpty())
visibleWebPages()->last()->d->resumeBackingStore();
#if USE(ACCELERATED_COMPOSITING)
// Root layer commit is not necessary for invisible tabs.
// And release layer resources can reduce memory consumption.
d->suspendRootLayerCommit();
#endif
return;
}
#if USE(ACCELERATED_COMPOSITING)
d->resumeRootLayerCommit();
#endif
// Push this WebPage to the top of the visible pages list.
if (!visibleWebPages()->isEmpty() && visibleWebPages()->last() != this) {
size_t foundIndex = visibleWebPages()->find(this);
if (foundIndex != WTF::notFound)
visibleWebPages()->remove(foundIndex);
}
visibleWebPages()->append(this);
if (BackingStorePrivate::currentBackingStoreOwner()
&& BackingStorePrivate::currentBackingStoreOwner() != this)
BackingStorePrivate::currentBackingStoreOwner()->d->suspendBackingStore();
// resumeBackingStore will set the current owner to this webpage.
// If we set the owner prematurely, then the tiles will not be reset.
d->resumeBackingStore();
}
void WebPagePrivate::selectionChanged(Frame* frame)
{
m_inputHandler->selectionChanged();
// FIXME: This is a hack!
// To ensure the selection being changed has its frame 'focused', lets
// set it as focused ourselves (PR #104724).
m_page->focusController()->setFocusedFrame(frame);
}
void WebPagePrivate::updateDelegatedOverlays(bool dispatched)
{
// Track a dispatched message, we don't want to flood the webkit thread.
// There can be as many as one more message enqued as needed but never less.
if (dispatched)
m_updateDelegatedOverlaysDispatched = false;
else if (m_updateDelegatedOverlaysDispatched) {
// Early return if there is message already pending on the webkit thread.
return;
}
if (Platform::webKitThreadMessageClient()->isCurrentThread()) {
// Must be called on the WebKit thread.
if (m_selectionHandler->isSelectionActive())
m_selectionHandler->selectionPositionChanged();
if (m_inspectorOverlay)
m_inspectorOverlay->update();
} else if (m_selectionHandler->isSelectionActive()) {
// Don't bother dispatching to webkit thread if selection and tap highlight are not active.
m_updateDelegatedOverlaysDispatched = true;
Platform::webKitThreadMessageClient()->dispatchMessage(Platform::createMethodCallMessage(&WebPagePrivate::updateDelegatedOverlays, this, true /*dispatched*/));
}
}
void WebPage::setCaretHighlightStyle(Platform::CaretHighlightStyle style)
{
}
bool WebPage::setBatchEditingActive(bool active)
{
return d->m_inputHandler->setBatchEditingActive(active);
}
bool WebPage::setInputSelection(unsigned start, unsigned end)
{
if (d->m_page->defersLoading())
return false;
return d->m_inputHandler->setSelection(start, end);
}
int WebPage::inputCaretPosition() const
{
return d->m_inputHandler->caretPosition();
}
class DeferredTaskPopupListSelectMultiple: public DeferredTask<&WebPagePrivate::m_wouldPopupListSelectMultiple> {
public:
DeferredTaskPopupListSelectMultiple(WebPagePrivate* webPagePrivate, int size, const bool* selecteds)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedPopupListSelecteds.append(selecteds, size);
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->popupListClosed(webPagePrivate->m_cachedPopupListSelecteds.size(), webPagePrivate->m_cachedPopupListSelecteds.data());
}
};
class DeferredTaskPopupListSelectSingle: public DeferredTask<&WebPagePrivate::m_wouldPopupListSelectSingle> {
public:
explicit DeferredTaskPopupListSelectSingle(WebPagePrivate* webPagePrivate, int index)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedPopupListSelectedIndex = index;
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->popupListClosed(webPagePrivate->m_cachedPopupListSelectedIndex);
}
};
void WebPage::popupListClosed(int size, const bool* selecteds)
{
DeferredTaskPopupListSelectSingle::finishOrCancel(d);
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskPopupListSelectMultiple(d, size, selecteds)));
return;
}
DeferredTaskPopupListSelectMultiple::finishOrCancel(d);
d->m_inputHandler->setPopupListIndexes(size, selecteds);
}
void WebPage::popupListClosed(int index)
{
DeferredTaskPopupListSelectMultiple::finishOrCancel(d);
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskPopupListSelectSingle(d, index)));
return;
}
DeferredTaskPopupListSelectSingle::finishOrCancel(d);
d->m_inputHandler->setPopupListIndex(index);
}
class DeferredTaskSetDateTimeInput: public DeferredTask<&WebPagePrivate::m_wouldSetDateTimeInput> {
public:
explicit DeferredTaskSetDateTimeInput(WebPagePrivate* webPagePrivate, BlackBerry::Platform::String value)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedDateTimeInput = value;
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->setDateTimeInput(webPagePrivate->m_cachedDateTimeInput);
}
};
void WebPage::setDateTimeInput(const BlackBerry::Platform::String& value)
{
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskSetDateTimeInput(d, value)));
return;
}
DeferredTaskSetDateTimeInput::finishOrCancel(d);
d->m_inputHandler->setInputValue(value);
}
class DeferredTaskSetColorInput: public DeferredTask<&WebPagePrivate::m_wouldSetColorInput> {
public:
explicit DeferredTaskSetColorInput(WebPagePrivate* webPagePrivate, BlackBerry::Platform::String value)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedColorInput = value;
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->setColorInput(webPagePrivate->m_cachedColorInput);
}
};
void WebPage::setColorInput(const BlackBerry::Platform::String& value)
{
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskSetColorInput(d, value)));
return;
}
DeferredTaskSetColorInput::finishOrCancel(d);
d->m_inputHandler->setInputValue(value);
}
void WebPage::setVirtualViewportSize(const Platform::IntSize& size)
{
d->m_virtualViewportSize = WebCore::IntSize(size);
}
void WebPage::resetVirtualViewportOnCommitted(bool reset)
{
d->m_resetVirtualViewportOnCommitted = reset;
}
Platform::IntSize WebPagePrivate::recomputeVirtualViewportFromViewportArguments()
{
static const ViewportArguments defaultViewportArguments;
if (m_viewportArguments == defaultViewportArguments)
return IntSize();
int desktopWidth = DEFAULT_MAX_LAYOUT_WIDTH;
int deviceWidth = Platform::Graphics::Screen::primaryScreen()->width();
int deviceHeight = Platform::Graphics::Screen::primaryScreen()->height();
float devicePixelRatio = m_webSettings->devicePixelRatio();
ViewportAttributes result = computeViewportAttributes(m_viewportArguments, desktopWidth, deviceWidth, deviceHeight, devicePixelRatio, m_defaultLayoutSize);
m_page->setDeviceScaleFactor(devicePixelRatio);
setUserScalable(m_webSettings->isUserScalable() && result.userScalable);
if (result.initialScale > 0)
setInitialScale(result.initialScale * devicePixelRatio);
if (result.minimumScale > 0)
setMinimumScale(result.minimumScale * devicePixelRatio);
if (result.maximumScale > 0)
setMaximumScale(result.maximumScale * devicePixelRatio);
return Platform::IntSize(result.layoutSize.width(), result.layoutSize.height());
}
#if ENABLE(EVENT_MODE_METATAGS)
void WebPagePrivate::didReceiveCursorEventMode(CursorEventMode mode)
{
if (mode != m_cursorEventMode)
m_client->cursorEventModeChanged(toPlatformCursorEventMode(mode));
m_cursorEventMode = mode;
}
void WebPagePrivate::didReceiveTouchEventMode(TouchEventMode mode)
{
if (mode != m_touchEventMode)
m_client->touchEventModeChanged(toPlatformTouchEventMode(mode));
m_touchEventMode = mode;
}
#endif
void WebPagePrivate::dispatchViewportPropertiesDidChange(const ViewportArguments& arguments)
{
if (arguments == m_viewportArguments)
return;
// If the caller is trying to reset to default arguments, use the user supplied ones instead.
static const ViewportArguments defaultViewportArguments;
if (arguments == defaultViewportArguments)
m_viewportArguments = m_userViewportArguments;
else
m_viewportArguments = arguments;
// 0 width or height in viewport arguments makes no sense, and results in a very large initial scale.
// In real world, a 0 width or height is usually caused by a syntax error in "content" field of viewport
// meta tag, for example, using semicolon instead of comma as separator ("width=device-width; initial-scale=1.0").
// We don't have a plan to tolerate the semicolon separator, but we can avoid applying 0 width/height.
// I default it to ValueDeviceWidth rather than ValueAuto because in more cases the web site wants "device-width"
// when they specify the viewport width.
if (!m_viewportArguments.width)
m_viewportArguments.width = ViewportArguments::ValueDeviceWidth;
if (!m_viewportArguments.height)
m_viewportArguments.height = ViewportArguments::ValueDeviceHeight;
Platform::IntSize virtualViewport = recomputeVirtualViewportFromViewportArguments();
m_webPage->setVirtualViewportSize(virtualViewport);
if (loadState() == WebKit::WebPagePrivate::Committed)
zoomToInitialScaleOnLoad();
}
void WebPagePrivate::onInputLocaleChanged(bool isRTL)
{
if (isRTL != m_webSettings->isWritingDirectionRTL()) {
m_webSettings->setWritingDirectionRTL(isRTL);
m_inputHandler->handleInputLocaleChanged(isRTL);
}
}
void WebPage::onInputLocaleChanged(bool isRTL)
{
d->onInputLocaleChanged(isRTL);
}
void WebPagePrivate::suspendBackingStore()
{
#if USE(ACCELERATED_COMPOSITING)
if (m_backingStore->d->isOpenGLCompositing()) {
// A visible web page's backing store can be suspended when another web
// page is taking over the backing store.
if (m_visible)
setCompositorDrawsRootLayer(true);
return;
}
#endif
}
void WebPagePrivate::resumeBackingStore()
{
ASSERT(m_webPage->isVisible());
bool directRendering = m_backingStore->d->shouldDirectRenderingToWindow();
if (!m_backingStore->d->isActive()
|| shouldResetTilesWhenShown()
|| directRendering) {
// We need to reset all tiles so that we do not show any tiles whose content may
// have been replaced by another WebPage instance (i.e. another tab).
BackingStorePrivate::setCurrentBackingStoreOwner(m_webPage);
// If we're OpenGL compositing, switching to accel comp drawing of the root layer
// is a good substitute for backingstore blitting.
if (m_backingStore->d->isOpenGLCompositing())
setCompositorDrawsRootLayer(!m_backingStore->d->isActive());
m_backingStore->d->orientationChanged(); // Updates tile geometry and creates visible tile buffer.
m_backingStore->d->resetTiles(true /* resetBackground */);
m_backingStore->d->updateTiles(false /* updateVisible */, false /* immediate */);
// This value may have changed, so we need to update it.
directRendering = m_backingStore->d->shouldDirectRenderingToWindow();
if (m_backingStore->d->renderVisibleContents()) {
if (!m_backingStore->d->isSuspended() && !directRendering)
m_backingStore->d->blitVisibleContents();
m_client->notifyPixelContentRendered(m_backingStore->d->visibleContentsRect());
}
} else {
if (m_backingStore->d->isOpenGLCompositing())
setCompositorDrawsRootLayer(false);
// Rendering was disabled while we were hidden, so we need to update all tiles.
m_backingStore->d->updateTiles(true /* updateVisible */, false /* immediate */);
}
#if USE(ACCELERATED_COMPOSITING)
setNeedsOneShotDrawingSynchronization();
#endif
setShouldResetTilesWhenShown(false);
}
void WebPagePrivate::setScreenOrientation(int orientation)
{
FOR_EACH_PLUGINVIEW(m_pluginViews)
(*it)->handleOrientationEvent(orientation);
m_pendingOrientation = -1;
#if ENABLE(ORIENTATION_EVENTS)
if (m_mainFrame->orientation() == orientation)
return;
for (RefPtr<Frame> frame = m_mainFrame; frame; frame = frame->tree()->traverseNext())
frame->sendOrientationChangeEvent(orientation);
#endif
}
void WebPage::setScreenOrientation(int orientation)
{
d->m_pendingOrientation = orientation;
d->m_hasPendingSurfaceSizeChange = true;
}
void WebPage::setHasPendingSurfaceSizeChange()
{
d->m_hasPendingSurfaceSizeChange = true;
}
void WebPage::applyPendingOrientationIfNeeded()
{
if (d->m_pendingOrientation != -1)
d->setScreenOrientation(d->m_pendingOrientation);
}
void WebPagePrivate::resizeSurfaceIfNeeded()
{
// This call will cause the client to reallocate the window buffer to new size,
// which needs to be serialized with usage of the window buffer. Accomplish
// this by sending a sync message to the compositing thread. All other usage of
// the window buffer happens on the compositing thread.
if (!Platform::userInterfaceThreadMessageClient()->isCurrentThread()) {
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
Platform::createMethodCallMessage(&WebPagePrivate::resizeSurfaceIfNeeded, this));
return;
}
m_client->resizeSurfaceIfNeeded();
}
void WebPagePrivate::setViewportSize(const IntSize& transformedActualVisibleSize, bool ensureFocusElementVisible)
{
if (m_pendingOrientation == -1 && !m_hasPendingSurfaceSizeChange && transformedActualVisibleSize == this->transformedActualVisibleSize())
return;
// Suspend all screen updates to the backingstore to make sure no-one tries to blit
// while the window surface and the BackingStore are out of sync.
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
// The screen rotation is a major state transition that in this case is not properly
// communicated to the backing store, since it does early return in most methods when
// not visible.
if (!m_visible || !m_backingStore->d->isActive())
setShouldResetTilesWhenShown(true);
bool hasPendingOrientation = m_pendingOrientation != -1;
if (m_hasPendingSurfaceSizeChange) {
resizeSurfaceIfNeeded();
m_hasPendingSurfaceSizeChange = false;
}
// The window buffers might have been recreated, cleared, moved, etc., so:
m_backingStore->d->windowFrontBufferState()->clearBlittedRegion();
m_backingStore->d->windowBackBufferState()->clearBlittedRegion();
IntSize viewportSizeBefore = actualVisibleSize();
FloatPoint centerOfVisibleContentsRect = this->centerOfVisibleContentsRect();
bool newVisibleRectContainsOldVisibleRect = (m_actualVisibleHeight <= transformedActualVisibleSize.height())
&& (m_actualVisibleWidth <= transformedActualVisibleSize.width());
bool atInitialScale = m_webPage->isAtInitialZoom();
bool atTop = !scrollPosition().y();
bool atLeft = !scrollPosition().x();
// We need to reorient the visibleTileRect because the following code
// could cause BackingStore::transformChanged to be called, where it
// is used.
// It is only dependent on the transformedViewportSize which has been
// updated by now.
m_backingStore->d->createVisibleTileBuffer();
setDefaultLayoutSize(transformedActualVisibleSize);
// Recompute our virtual viewport.
bool needsLayout = false;
static ViewportArguments defaultViewportArguments;
if (m_viewportArguments != defaultViewportArguments) {
// We may need to infer the width and height for the viewport with respect to the rotation.
Platform::IntSize newVirtualViewport = recomputeVirtualViewportFromViewportArguments();
ASSERT(!newVirtualViewport.isEmpty());
m_webPage->setVirtualViewportSize(newVirtualViewport);
m_mainFrame->view()->setUseFixedLayout(useFixedLayout());
m_mainFrame->view()->setFixedLayoutSize(fixedLayoutSize());
needsLayout = true;
}
// We switch this strictly after recomputing our virtual viewport as zoomToFitScale is dependent
// upon these values and so is the virtual viewport recalculation.
m_actualVisibleWidth = transformedActualVisibleSize.width();
m_actualVisibleHeight = transformedActualVisibleSize.height();
IntSize viewportSizeAfter = actualVisibleSize();
IntSize offset(
roundf((viewportSizeBefore.width() - viewportSizeAfter.width()) / 2.0),
roundf((viewportSizeBefore.height() - viewportSizeAfter.height()) / 2.0));
// As a special case, if we were anchored to the top left position at
// the beginning of the rotation then preserve that anchor.
if (atTop)
offset.setHeight(0);
if (atLeft)
offset.setWidth(0);
// If we're about to overscroll, cap the offset to valid content.
IntPoint bottomRight(
scrollPosition().x() + viewportSizeAfter.width(),
scrollPosition().y() + viewportSizeAfter.height());
if (bottomRight.x() + offset.width() > contentsSize().width())
offset.setWidth(contentsSize().width() - bottomRight.x());
if (bottomRight.y() + offset.height() > contentsSize().height())
offset.setHeight(contentsSize().height() - bottomRight.y());
if (scrollPosition().x() + offset.width() < 0)
offset.setWidth(-scrollPosition().x());
if (scrollPosition().y() + offset.height() < 0)
offset.setHeight(-scrollPosition().y());
// ...before scrolling, because the backing store will align its
// tile matrix with the viewport as reported by the ScrollView.
setScrollPosition(scrollPosition() + offset);
notifyTransformedScrollChanged();
m_backingStore->d->orientationChanged();
m_backingStore->d->actualVisibleSizeChanged(transformedActualVisibleSize);
// Update view mode only after we have updated the actual
// visible size and reset the contents rect if necessary.
if (setViewMode(viewMode()))
needsLayout = true;
bool needsLayoutToFindContentSize = hasPendingOrientation;
// We need to update the viewport size of the WebCore::ScrollView...
updateViewportSize(!needsLayoutToFindContentSize /* setFixedReportedSize */, false /* sendResizeEvent */);
notifyTransformedContentsSizeChanged();
// If automatic zooming is disabled, prevent zooming below.
if (!m_webSettings->isZoomToFitOnLoad()) {
atInitialScale = false;
// Normally, if the contents size is smaller than the layout width,
// we would zoom in. If zoom is disabled, we need to do something else,
// or there will be artifacts due to non-rendered areas outside of the
// contents size. If there is a virtual viewport, we are not allowed
// to modify the fixed layout size, however.
if (!hasVirtualViewport() && contentsSize().width() < m_defaultLayoutSize.width()) {
m_mainFrame->view()->setUseFixedLayout(useFixedLayout());
m_mainFrame->view()->setFixedLayoutSize(m_defaultLayoutSize);
needsLayout = true;
}
}
// Need to resume so that the backingstore will start recording the invalidated
// rects from below.
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::None);
// We might need to layout here to get a correct contentsSize so that zoomToFit
// is calculated correctly.
bool stillNeedsLayout = needsLayout;
while (stillNeedsLayout) {
setNeedsLayout();
requestLayoutIfNeeded();
stillNeedsLayout = false;
// Emulate the zoomToFitWidthOnLoad algorithm if we're rotating.
++m_nestedLayoutFinishedCount;
if (needsLayoutToFindContentSize) {
if (setViewMode(viewMode()))
stillNeedsLayout = true;
}
}
m_nestedLayoutFinishedCount = 0;
// As a special case if we were zoomed to the initial scale at the beginning
// of the rotation then preserve that zoom level even when it is zoomToFit.
double scale = atInitialScale ? initialScale() : currentScale();
// Do our own clamping.
scale = clampedScale(scale);
if (needsLayoutToFindContentSize) {
// Set the fixed reported size here so that innerWidth|innerHeight works
// with this new scale.
TransformationMatrix rotationMatrix;
rotationMatrix.scale(scale);
IntRect viewportRect = IntRect(IntPoint::zero(), transformedActualVisibleSize);
IntRect actualVisibleRect = enclosingIntRect(rotationMatrix.inverse().mapRect(FloatRect(viewportRect)));
m_mainFrame->view()->setFixedReportedSize(actualVisibleRect.size());
m_mainFrame->view()->repaintFixedElementsAfterScrolling();
requestLayoutIfNeeded();
m_mainFrame->view()->updateFixedElementsAfterScrolling();
}
// We're going to need to send a resize event to JavaScript because
// innerWidth and innerHeight depend on fixed reported size.
// This is how we support mobile pages where JavaScript resizes
// the page in order to get around the fixed layout size, e.g.
// google maps when it detects a mobile user agent.
if (shouldSendResizeEvent())
m_mainFrame->eventHandler()->sendResizeEvent();
// As a special case if we were anchored to the top left position at the beginning
// of the rotation then preserve that anchor.
FloatPoint anchor = centerOfVisibleContentsRect;
if (atTop)
anchor.setY(0);
if (atLeft)
anchor.setX(0);
// Try and zoom here with clamping on.
// FIXME: Determine why the above comment says "clamping on", yet we
// don't set enforceScaleClamping to true.
// FIXME: Determine why ensureContentVisible() is only called for !success
// in the direct-rendering case, but in all cases otherwise. Chances are
// one of these is incorrect and we can unify two branches into one.
if (m_backingStore->d->shouldDirectRenderingToWindow()) {
bool success = zoomAboutPoint(scale, anchor, false /* enforceScaleClamping */, true /* forceRendering */);
if (!success && ensureFocusElementVisible)
ensureContentVisible(!newVisibleRectContainsOldVisibleRect);
} else if (zoomAboutPoint(scale, anchor, false /*enforceScaleClamping*/, true /*forceRendering*/)) {
if (ensureFocusElementVisible)
ensureContentVisible(!newVisibleRectContainsOldVisibleRect);
} else {
// Suspend all screen updates to the backingstore.
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
// If the zoom failed, then we should still preserve the special case of scroll position.
IntPoint scrollPosition = this->scrollPosition();
if (atTop)
scrollPosition.setY(0);
if (atLeft)
scrollPosition.setX(0);
setScrollPosition(scrollPosition);
// These might have been altered even if we didn't zoom so notify the client.
notifyTransformedContentsSizeChanged();
notifyTransformedScrollChanged();
if (!needsLayout) {
// The visible tiles for scroll must be up-to-date before we blit since we are not performing a layout.
m_backingStore->d->updateTilesForScrollOrNotRenderedRegion();
}
if (ensureFocusElementVisible)
ensureContentVisible(!newVisibleRectContainsOldVisibleRect);
if (needsLayout) {
m_backingStore->d->resetTiles(true);
m_backingStore->d->updateTiles(false /* updateVisible */, false /* immediate */);
}
// If we need layout then render and blit, otherwise just blit as our viewport has changed.
m_backingStore->d->resumeScreenAndBackingStoreUpdates(needsLayout ? BackingStore::RenderAndBlit : BackingStore::Blit);
}
}
void WebPage::setViewportSize(const Platform::IntSize& viewportSize, bool ensureFocusElementVisible)
{
d->setViewportSize(viewportSize, ensureFocusElementVisible);
}
void WebPagePrivate::setDefaultLayoutSize(const IntSize& size)
{
IntSize screenSize = Platform::Graphics::Screen::primaryScreen()->size();
ASSERT(size.width() <= screenSize.width() && size.height() <= screenSize.height());
m_defaultLayoutSize = size.expandedTo(minimumLayoutSize).shrunkTo(screenSize);
}
void WebPage::setDefaultLayoutSize(const Platform::IntSize& platformSize)
{
WebCore::IntSize size = platformSize;
if (size == d->m_defaultLayoutSize)
return;
d->setDefaultLayoutSize(size);
bool needsLayout = d->setViewMode(d->viewMode());
if (needsLayout) {
d->setNeedsLayout();
if (!d->isLoading())
d->requestLayoutIfNeeded();
}
}
bool WebPage::mouseEvent(const Platform::MouseEvent& mouseEvent, bool* wheelDeltaAccepted)
{
if (!d->m_mainFrame->view())
return false;
if (d->m_page->defersLoading())
return false;
PluginView* pluginView = d->m_fullScreenPluginView.get();
if (pluginView)
return d->dispatchMouseEventToFullScreenPlugin(pluginView, mouseEvent);
if (mouseEvent.type() == Platform::MouseEvent::MouseAborted) {
d->m_mainFrame->eventHandler()->setMousePressed(false);
return false;
}
d->m_pluginMayOpenNewTab = true;
d->m_lastUserEventTimestamp = currentTime();
int clickCount = (d->m_selectionHandler->isSelectionActive() || mouseEvent.type() != Platform::MouseEvent::MouseMove) ? 1 : 0;
// Set the button type.
MouseButton buttonType = NoButton;
if (mouseEvent.isLeftButton())
buttonType = LeftButton;
else if (mouseEvent.isRightButton())
buttonType = RightButton;
else if (mouseEvent.isMiddleButton())
buttonType = MiddleButton;
// Create our event.
PlatformMouseEvent platformMouseEvent(d->mapFromTransformed(mouseEvent.position()), mouseEvent.screenPosition(),
toWebCoreMouseEventType(mouseEvent.type()), clickCount, buttonType, PointingDevice);
d->m_lastMouseEvent = platformMouseEvent;
bool success = d->handleMouseEvent(platformMouseEvent);
if (mouseEvent.wheelTicks()) {
PlatformWheelEvent wheelEvent(d->mapFromTransformed(mouseEvent.position()), mouseEvent.screenPosition(),
0, -mouseEvent.wheelDelta(),
0, -mouseEvent.wheelTicks(),
ScrollByPixelWheelEvent,
false /* shiftKey */, false /* ctrlKey */,
false /* altKey */, false /* metaKey */);
if (wheelDeltaAccepted)
*wheelDeltaAccepted = d->handleWheelEvent(wheelEvent);
} else if (wheelDeltaAccepted)
*wheelDeltaAccepted = false;
return success;
}
bool WebPagePrivate::dispatchMouseEventToFullScreenPlugin(PluginView* plugin, const Platform::MouseEvent& event)
{
NPEvent npEvent;
NPMouseEvent mouseEvent;
mouseEvent.x = event.screenPosition().x();
mouseEvent.y = event.screenPosition().y();
switch (event.type()) {
case Platform::MouseEvent::MouseButtonDown:
mouseEvent.type = MOUSE_BUTTON_DOWN;
m_pluginMouseButtonPressed = true;
break;
case Platform::MouseEvent::MouseButtonUp:
mouseEvent.type = MOUSE_BUTTON_UP;
m_pluginMouseButtonPressed = false;
break;
case Platform::MouseEvent::MouseMove:
mouseEvent.type = MOUSE_MOTION;
break;
default:
return false;
}
mouseEvent.flags = 0;
mouseEvent.button = m_pluginMouseButtonPressed;
npEvent.type = NP_MouseEvent;
npEvent.data = &mouseEvent;
return plugin->dispatchFullScreenNPEvent(npEvent);
}
bool WebPagePrivate::handleMouseEvent(PlatformMouseEvent& mouseEvent)
{
EventHandler* eventHandler = m_mainFrame->eventHandler();
if (mouseEvent.type() == WebCore::PlatformEvent::MouseMoved)
return eventHandler->mouseMoved(mouseEvent);
if (mouseEvent.type() == WebCore::PlatformEvent::MouseScroll)
return true;
Node* node = 0;
if (mouseEvent.inputMethod() == TouchScreen) {
const FatFingersResult lastFatFingersResult = m_touchEventHandler->lastFatFingersResult();
// Fat fingers can deal with shadow content.
node = lastFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
}
if (!node) {
HitTestResult result = eventHandler->hitTestResultAtPoint(mapFromViewportToContents(mouseEvent.position()), false /*allowShadowContent*/);
node = result.innerNode();
}
if (mouseEvent.type() == WebCore::PlatformEvent::MousePressed) {
m_inputHandler->setInputModeEnabled();
if (m_inputHandler->willOpenPopupForNode(node)) {
// Do not allow any human generated mouse or keyboard events to select <option>s in the list box
// because we use a pop up dialog to handle the actual selections. This prevents options from
// being selected prior to displaying the pop up dialog. The contents of the listbox are for
// display only.
//
// FIXME: We explicitly do not forward this event to WebCore so as to preserve symmetry with
// the MouseEventReleased handling (below). This has the side-effect that mousedown events
// are not fired for human generated mouse press events. See RIM Bug #1579.
// We do focus <select>/<option> on mouse down so that a Focus event is fired and have the
// element painted in its focus state on repaint.
ASSERT(node->isElementNode());
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node);
element->focus();
}
} else
eventHandler->handleMousePressEvent(mouseEvent);
} else if (mouseEvent.type() == WebCore::PlatformEvent::MouseReleased) {
// FIXME: For <select> and <options> elements, we explicitly do not forward this event to WebCore so
// as to preserve symmetry with the MouseEventPressed handling (above). This has the side-effect that
// mouseup events are not fired on such elements for human generated mouse release events. See RIM Bug #1579.
if (!m_inputHandler->didNodeOpenPopup(node))
eventHandler->handleMouseReleaseEvent(mouseEvent);
}
return true;
}
bool WebPagePrivate::handleWheelEvent(PlatformWheelEvent& wheelEvent)
{
return m_mainFrame->eventHandler()->handleWheelEvent(wheelEvent);
}
bool WebPage::touchEvent(const Platform::TouchEvent& event)
{
#if DEBUG_TOUCH_EVENTS
BBLOG(LogLevelCritical, "%s", event.toString().c_str());
#endif
#if ENABLE(TOUCH_EVENTS)
if (!d->m_mainFrame)
return false;
if (d->m_page->defersLoading())
return false;
PluginView* pluginView = d->m_fullScreenPluginView.get();
if (pluginView)
return d->dispatchTouchEventToFullScreenPlugin(pluginView, event);
Platform::TouchEvent tEvent = event;
for (unsigned i = 0; i < event.m_points.size(); i++) {
tEvent.m_points[i].m_pos = d->mapFromTransformed(tEvent.m_points[i].m_pos);
tEvent.m_points[i].m_screenPos = tEvent.m_points[i].m_screenPos;
}
if (event.isSingleTap())
d->m_pluginMayOpenNewTab = true;
else if (tEvent.m_type == Platform::TouchEvent::TouchStart || tEvent.m_type == Platform::TouchEvent::TouchCancel)
d->m_pluginMayOpenNewTab = false;
if (tEvent.m_type == Platform::TouchEvent::TouchStart)
d->clearCachedHitTestResult();
bool handled = false;
if (d->m_needTouchEvents && !event.m_type != Platform::TouchEvent::TouchInjected)
handled = d->m_mainFrame->eventHandler()->handleTouchEvent(PlatformTouchEvent(&tEvent));
// Unpress mouse if touch end is consumed by a JavaScript touch handler, otherwise the mouse state will remain pressed
// which could either mess up the internal mouse state or start text selection on the next mouse move/down.
if (tEvent.m_type == Platform::TouchEvent::TouchEnd && handled && d->m_mainFrame->eventHandler()->mousePressed())
d->m_touchEventHandler->touchEventCancel();
if (d->m_preventDefaultOnTouchStart) {
if (tEvent.m_type == Platform::TouchEvent::TouchEnd || tEvent.m_type == Platform::TouchEvent::TouchCancel)
d->m_preventDefaultOnTouchStart = false;
return true;
}
if (handled) {
if (tEvent.m_type == Platform::TouchEvent::TouchStart)
d->m_preventDefaultOnTouchStart = true;
return true;
}
if (event.isTouchHold())
d->m_touchEventHandler->touchHoldEvent();
#endif
return false;
}
void WebPagePrivate::setScrollOriginPoint(const Platform::IntPoint& documentScrollOrigin)
{
m_inRegionScroller->d->reset();
if (!m_hasInRegionScrollableAreas)
return;
postponeDocumentStyleRecalc();
m_inRegionScroller->d->calculateInRegionScrollableAreasForPoint(documentScrollOrigin);
if (!m_inRegionScroller->d->activeInRegionScrollableAreas().empty())
m_client->notifyInRegionScrollableAreasChanged(m_inRegionScroller->d->activeInRegionScrollableAreas());
resumeDocumentStyleRecalc();
}
void WebPage::setDocumentScrollOriginPoint(const Platform::IntPoint& documentScrollOrigin)
{
d->setScrollOriginPoint(documentScrollOrigin);
}
bool WebPagePrivate::dispatchTouchEventToFullScreenPlugin(PluginView* plugin, const Platform::TouchEvent& event)
{
NPTouchEvent npTouchEvent;
if (event.isDoubleTap())
npTouchEvent.type = TOUCH_EVENT_DOUBLETAP;
else if (event.isTouchHold())
npTouchEvent.type = TOUCH_EVENT_TOUCHHOLD;
else {
switch (event.m_type) {
case Platform::TouchEvent::TouchStart:
npTouchEvent.type = TOUCH_EVENT_START;
break;
case Platform::TouchEvent::TouchEnd:
npTouchEvent.type = TOUCH_EVENT_END;
break;
case Platform::TouchEvent::TouchMove:
npTouchEvent.type = TOUCH_EVENT_MOVE;
break;
case Platform::TouchEvent::TouchCancel:
npTouchEvent.type = TOUCH_EVENT_CANCEL;
break;
default:
return false;
}
}
npTouchEvent.points = 0;
npTouchEvent.size = event.m_points.size();
if (npTouchEvent.size) {
npTouchEvent.points = new NPTouchPoint[npTouchEvent.size];
for (int i = 0; i < npTouchEvent.size; i++) {
npTouchEvent.points[i].touchId = event.m_points[i].m_id;
npTouchEvent.points[i].clientX = event.m_points[i].m_screenPos.x();
npTouchEvent.points[i].clientY = event.m_points[i].m_screenPos.y();
npTouchEvent.points[i].screenX = event.m_points[i].m_screenPos.x();
npTouchEvent.points[i].screenY = event.m_points[i].m_screenPos.y();
npTouchEvent.points[i].pageX = event.m_points[i].m_pos.x();
npTouchEvent.points[i].pageY = event.m_points[i].m_pos.y();
}
}
NPEvent npEvent;
npEvent.type = NP_TouchEvent;
npEvent.data = &npTouchEvent;
bool handled = plugin->dispatchFullScreenNPEvent(npEvent);
if (npTouchEvent.type == TOUCH_EVENT_DOUBLETAP && !handled) {
// Send Touch Up if double tap not consumed.
npTouchEvent.type = TOUCH_EVENT_END;
npEvent.data = &npTouchEvent;
handled = plugin->dispatchFullScreenNPEvent(npEvent);
}
delete[] npTouchEvent.points;
return handled;
}
bool WebPage::touchPointAsMouseEvent(const Platform::TouchPoint& point, bool useFatFingers)
{
if (d->m_page->defersLoading())
return false;
PluginView* pluginView = d->m_fullScreenPluginView.get();
if (pluginView)
return d->dispatchTouchPointAsMouseEventToFullScreenPlugin(pluginView, point);
d->m_lastUserEventTimestamp = currentTime();
Platform::TouchPoint tPoint = point;
tPoint.m_pos = d->mapFromTransformed(tPoint.m_pos);
tPoint.m_screenPos = tPoint.m_screenPos;
return d->m_touchEventHandler->handleTouchPoint(tPoint, useFatFingers);
}
void WebPage::playSoundIfAnchorIsTarget() const
{
d->m_touchEventHandler->playSoundIfAnchorIsTarget();
}
bool WebPagePrivate::dispatchTouchPointAsMouseEventToFullScreenPlugin(PluginView* pluginView, const Platform::TouchPoint& point)
{
NPEvent npEvent;
NPMouseEvent mouse;
switch (point.m_state) {
case Platform::TouchPoint::TouchPressed:
mouse.type = MOUSE_BUTTON_DOWN;
break;
case Platform::TouchPoint::TouchReleased:
mouse.type = MOUSE_BUTTON_UP;
break;
case Platform::TouchPoint::TouchMoved:
mouse.type = MOUSE_MOTION;
break;
case Platform::TouchPoint::TouchStationary:
return false;
}
mouse.x = point.m_screenPos.x();
mouse.y = point.m_screenPos.y();
mouse.button = mouse.type != MOUSE_BUTTON_UP;
mouse.flags = 0;
npEvent.type = NP_MouseEvent;
npEvent.data = &mouse;
return pluginView->dispatchFullScreenNPEvent(npEvent);
}
void WebPage::touchEventCancel()
{
d->m_pluginMayOpenNewTab = false;
if (d->m_page->defersLoading())
return;
d->m_touchEventHandler->touchEventCancel();
}
Frame* WebPagePrivate::focusedOrMainFrame() const
{
return m_page->focusController()->focusedOrMainFrame();
}
void WebPagePrivate::clearFocusNode()
{
Frame* frame = focusedOrMainFrame();
if (!frame)
return;
ASSERT(frame->document());
if (frame->document()->focusedNode())
frame->page()->focusController()->setFocusedNode(0, frame);
}
BlackBerry::Platform::String WebPage::textEncoding()
{
Frame* frame = d->focusedOrMainFrame();
if (!frame)
return "";
Document* document = frame->document();
if (!document)
return "";
return document->loader()->writer()->encoding();
}
BlackBerry::Platform::String WebPage::forcedTextEncoding()
{
Frame* frame = d->focusedOrMainFrame();
if (!frame)
return BlackBerry::Platform::String::emptyString();
Document* document = frame->document();
if (!document)
return BlackBerry::Platform::String::emptyString();
return document->loader()->overrideEncoding();
}
void WebPage::setForcedTextEncoding(const BlackBerry::Platform::String& encoding)
{
if (!encoding.empty() && d->focusedOrMainFrame() && d->focusedOrMainFrame()->loader() && d->focusedOrMainFrame()->loader())
return d->focusedOrMainFrame()->loader()->reloadWithOverrideEncoding(encoding);
}
static void handleScrolling(unsigned short character, WebPagePrivate* scroller)
{
const int scrollFactor = 20;
int dx = 0, dy = 0;
switch (character) {
case KEYCODE_LEFT:
dx = -scrollFactor;
break;
case KEYCODE_RIGHT:
dx = scrollFactor;
break;
case KEYCODE_UP:
dy = -scrollFactor;
break;
case KEYCODE_DOWN:
dy = scrollFactor;
break;
case KEYCODE_PG_UP:
ASSERT(scroller);
dy = scrollFactor - scroller->actualVisibleSize().height();
break;
case KEYCODE_PG_DOWN:
ASSERT(scroller);
dy = scroller->actualVisibleSize().height() - scrollFactor;
break;
}
if (dx || dy) {
// Don't use the scrollBy function because it triggers the scroll as originating from BlackBerry
// but then it expects a separate invalidate which isn't sent in this case.
ASSERT(scroller && scroller->m_mainFrame && scroller->m_mainFrame->view());
IntPoint pos(scroller->scrollPosition() + IntSize(dx, dy));
// Prevent over scrolling for arrows and Page up/down.
if (pos.x() < 0)
pos.setX(0);
if (pos.y() < 0)
pos.setY(0);
if (pos.x() + scroller->actualVisibleSize().width() > scroller->contentsSize().width())
pos.setX(scroller->contentsSize().width() - scroller->actualVisibleSize().width());
if (pos.y() + scroller->actualVisibleSize().height() > scroller->contentsSize().height())
pos.setY(scroller->contentsSize().height() - scroller->actualVisibleSize().height());
scroller->m_mainFrame->view()->setScrollPosition(pos);
scroller->m_client->scrollChanged();
}
}
bool WebPage::keyEvent(const Platform::KeyboardEvent& keyboardEvent)
{
if (!d->m_mainFrame->view())
return false;
if (d->m_page->defersLoading())
return false;
ASSERT(d->m_page->focusController());
bool handled = d->m_inputHandler->handleKeyboardInput(keyboardEvent);
if (!handled && keyboardEvent.type() == Platform::KeyboardEvent::KeyDown && !d->m_inputHandler->isInputMode()) {
IntPoint previousPos = d->scrollPosition();
handleScrolling(keyboardEvent.character(), d);
handled = previousPos != d->scrollPosition();
}
return handled;
}
bool WebPage::deleteTextRelativeToCursor(unsigned int leftOffset, unsigned int rightOffset)
{
if (d->m_page->defersLoading())
return false;
return d->m_inputHandler->deleteTextRelativeToCursor(leftOffset, rightOffset);
}
spannable_string_t* WebPage::selectedText(int32_t flags)
{
return d->m_inputHandler->selectedText(flags);
}
spannable_string_t* WebPage::textBeforeCursor(int32_t length, int32_t flags)
{
return d->m_inputHandler->textBeforeCursor(length, flags);
}
spannable_string_t* WebPage::textAfterCursor(int32_t length, int32_t flags)
{
return d->m_inputHandler->textAfterCursor(length, flags);
}
extracted_text_t* WebPage::extractedTextRequest(extracted_text_request_t* request, int32_t flags)
{
return d->m_inputHandler->extractedTextRequest(request, flags);
}
int32_t WebPage::setComposingRegion(int32_t start, int32_t end)
{
return d->m_inputHandler->setComposingRegion(start, end);
}
int32_t WebPage::finishComposition()
{
return d->m_inputHandler->finishComposition();
}
int32_t WebPage::setComposingText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (d->m_page->defersLoading())
return -1;
return d->m_inputHandler->setComposingText(spannableString, relativeCursorPosition);
}
int32_t WebPage::commitText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (d->m_page->defersLoading())
return -1;
return d->m_inputHandler->commitText(spannableString, relativeCursorPosition);
}
void WebPage::setSpellCheckingEnabled(bool enabled)
{
static_cast<EditorClientBlackBerry*>(d->m_page->editorClient())->enableSpellChecking(enabled);
}
void WebPage::spellCheckingRequestCancelled(int32_t transactionId)
{
d->m_inputHandler->spellCheckingRequestCancelled(transactionId);
}
void WebPage::spellCheckingRequestProcessed(int32_t transactionId, spannable_string_t* spannableString)
{
d->m_inputHandler->spellCheckingRequestProcessed(transactionId, spannableString);
}
class DeferredTaskSelectionCancelled: public DeferredTask<&WebPagePrivate::m_wouldCancelSelection> {
public:
explicit DeferredTaskSelectionCancelled(WebPagePrivate* webPagePrivate)
: DeferredTaskType(webPagePrivate)
{
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->selectionCancelled();
}
};
void WebPage::selectionCancelled()
{
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskSelectionCancelled(d)));
return;
}
DeferredTaskSelectionCancelled::finishOrCancel(d);
d->m_selectionHandler->cancelSelection();
}
bool WebPage::selectionContainsDocumentPoint(const Platform::IntPoint& point)
{
return d->m_selectionHandler->selectionContains(point);
}
BlackBerry::Platform::String WebPage::title() const
{
if (d->m_mainFrame->document())
return d->m_mainFrame->loader()->documentLoader()->title().string();
return BlackBerry::Platform::String::emptyString();
}
BlackBerry::Platform::String WebPage::selectedText() const
{
return d->m_selectionHandler->selectedText();
}
BlackBerry::Platform::String WebPage::cutSelectedText()
{
BlackBerry::Platform::String selectedText = d->m_selectionHandler->selectedText();
if (!d->m_page->defersLoading() && !selectedText.empty())
d->m_inputHandler->deleteSelection();
return selectedText;
}
void WebPage::insertText(const BlackBerry::Platform::String& string)
{
if (d->m_page->defersLoading())
return;
d->m_inputHandler->insertText(string);
}
void WebPage::clearCurrentInputField()
{
if (d->m_page->defersLoading())
return;
d->m_inputHandler->clearField();
}
void WebPage::cut()
{
if (d->m_page->defersLoading())
return;
d->m_inputHandler->cut();
}
void WebPage::copy()
{
d->m_inputHandler->copy();
}
void WebPage::paste()
{
if (d->m_page->defersLoading())
return;
d->m_inputHandler->paste();
}
void WebPage::selectAll()
{
if (d->m_page->defersLoading())
return;
d->m_inputHandler->selectAll();
}
void WebPage::setDocumentSelection(const Platform::IntPoint& documentStartPoint, const Platform::IntPoint& documentEndPoint)
{
if (d->m_page->defersLoading())
return;
d->m_selectionHandler->setSelection(documentStartPoint, documentEndPoint);
}
void WebPage::setDocumentCaretPosition(const Platform::IntPoint& documentCaretPosition)
{
if (d->m_page->defersLoading())
return;
// Handled by selection handler as it's point based.
d->m_selectionHandler->setCaretPosition(documentCaretPosition);
}
void WebPage::selectAtDocumentPoint(const Platform::IntPoint& documentPoint)
{
if (d->m_page->defersLoading())
return;
d->m_selectionHandler->selectAtPoint(documentPoint);
}
BackingStore* WebPage::backingStore() const
{
return d->m_backingStore;
}
InRegionScroller* WebPage::inRegionScroller() const
{
return d->m_inRegionScroller.get();
}
void WebPagePrivate::setTextReflowAnchorPoint(const Platform::IntPoint& documentFocalPoint)
{
// Should only be invoked when text reflow is enabled.
ASSERT(m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled);
m_currentPinchZoomNode = bestNodeForZoomUnderPoint(documentFocalPoint);
if (!m_currentPinchZoomNode)
return;
IntRect nodeRect = rectForNode(m_currentPinchZoomNode.get());
m_anchorInNodeRectRatio.set(
static_cast<float>(documentFocalPoint.x() - nodeRect.x()) / nodeRect.width(),
static_cast<float>(documentFocalPoint.y() - nodeRect.y()) / nodeRect.height());
}
bool WebPage::pinchZoomAboutPoint(double scale, const Platform::FloatPoint& documentFocalPoint)
{
d->m_userPerformedManualZoom = true;
d->m_userPerformedManualScroll = true;
if (d->m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabled) {
d->setTextReflowAnchorPoint(webkitThreadViewportAccessor()->roundedDocumentContents(documentFocalPoint));
// Theoretically, d->nodeForZoomUnderPoint(documentFocalPoint) can return null.
if (!d->m_currentPinchZoomNode)
return false;
}
return d->zoomAboutPoint(scale, documentFocalPoint);
}
#if ENABLE(VIEWPORT_REFLOW)
void WebPagePrivate::toggleTextReflowIfEnabledForBlockZoomOnly(bool shouldEnableTextReflow)
{
if (m_webPage->settings()->textReflowMode() == WebSettings::TextReflowEnabledOnlyForBlockZoom)
m_page->settings()->setTextReflowEnabled(shouldEnableTextReflow);
}
#endif
bool WebPage::blockZoom(const Platform::IntPoint& documentTargetPoint)
{
if (!d->m_mainFrame->view() || !d->isUserScalable())
return false;
Node* node = d->bestNodeForZoomUnderPoint(documentTargetPoint);
if (!node)
return false;
IntRect nodeRect = d->rectForNode(node);
IntRect blockRect;
bool endOfBlockZoomMode = d->compareNodesForBlockZoom(d->m_currentBlockZoomAdjustedNode.get(), node);
const double oldScale = d->m_transformationMatrix->m11();
double newScale = 0;
const double margin = endOfBlockZoomMode ? 0 : blockZoomMargin * 2 * oldScale;
bool isFirstZoom = false;
if (endOfBlockZoomMode) {
// End of block zoom mode
IntRect rect = d->blockZoomRectForNode(node);
blockRect = IntRect(0, rect.y(), d->transformedContentsSize().width(), d->transformedContentsSize().height() - rect.y());
d->m_shouldReflowBlock = false;
} else {
// Start/continue block zoom mode
Node* tempBlockZoomAdjustedNode = d->m_currentBlockZoomAdjustedNode.get();
blockRect = d->blockZoomRectForNode(node);
// Don't use a block if it is too close to the size of the actual contents.
// We allow this for images only so that they can be zoomed tight to the screen.
if (!node->hasTagName(HTMLNames::imgTag)) {
IntRect tRect = d->mapFromTransformed(blockRect);
int blockArea = tRect.width() * tRect.height();
int pageArea = d->contentsSize().width() * d->contentsSize().height();
double blockToPageRatio = static_cast<double>(pageArea - blockArea) / pageArea;
if (blockToPageRatio < minimumExpandingRatio) {
// Restore old adjust node because zoom was canceled.
d->m_currentBlockZoomAdjustedNode = tempBlockZoomAdjustedNode;
return false;
}
}
if (blockRect.isEmpty() || !blockRect.width() || !blockRect.height())
return false;
if (!d->m_currentBlockZoomNode.get())
isFirstZoom = true;
d->m_currentBlockZoomNode = node;
d->m_shouldReflowBlock = true;
}
newScale = std::min(d->newScaleForBlockZoomRect(blockRect, oldScale, margin), d->maxBlockZoomScale());
newScale = std::max(newScale, minimumScale());
#if ENABLE(VIEWPORT_REFLOW)
// If reflowing, adjust the reflow-width of text node to make sure the font is a reasonable size.
if (d->m_currentBlockZoomNode && d->m_shouldReflowBlock && settings()->textReflowMode() != WebSettings::TextReflowDisabled) {
RenderObject* renderer = d->m_currentBlockZoomNode->renderer();
if (renderer && renderer->isText()) {
double newFontSize = renderer->style()->fontSize() * newScale;
if (newFontSize < d->m_webSettings->defaultFontSize()) {
newScale = std::min(static_cast<double>(d->m_webSettings->defaultFontSize()) / renderer->style()->fontSize(), d->maxBlockZoomScale());
newScale = std::max(newScale, minimumScale());
}
blockRect.setWidth(oldScale * static_cast<double>(d->transformedActualVisibleSize().width()) / newScale);
// Re-calculate the scale here to take in to account the margin.
newScale = std::min(d->newScaleForBlockZoomRect(blockRect, oldScale, margin), d->maxBlockZoomScale());
newScale = std::max(newScale, minimumScale()); // Still, it's not allowed to be smaller than minimum scale.
}
}
#endif
// Align the zoomed block in the screen.
double newBlockHeight = d->mapFromTransformed(blockRect).height();
double newBlockWidth = d->mapFromTransformed(blockRect).width();
double scaledViewportWidth = static_cast<double>(d->actualVisibleSize().width()) * oldScale / newScale;
double scaledViewportHeight = static_cast<double>(d->actualVisibleSize().height()) * oldScale / newScale;
double dx = std::max(0.0, (scaledViewportWidth - newBlockWidth) / 2.0);
double dy = std::max(0.0, (scaledViewportHeight - newBlockHeight) / 2.0);
RenderObject* renderer = d->m_currentBlockZoomAdjustedNode->renderer();
FloatPoint anchor;
FloatPoint topLeftPoint(d->mapFromTransformed(blockRect).location());
if (renderer && renderer->isText()) {
ETextAlign textAlign = renderer->style()->textAlign();
switch (textAlign) {
case CENTER:
case WEBKIT_CENTER:
anchor = FloatPoint(nodeRect.x() + (nodeRect.width() - scaledViewportWidth) / 2, topLeftPoint.y());
break;
case LEFT:
case WEBKIT_LEFT:
anchor = topLeftPoint;
break;
case RIGHT:
case WEBKIT_RIGHT:
anchor = FloatPoint(nodeRect.x() + nodeRect.width() - scaledViewportWidth, topLeftPoint.y());
break;
case TAAUTO:
case JUSTIFY:
default:
if (renderer->style()->isLeftToRightDirection())
anchor = topLeftPoint;
else
anchor = FloatPoint(nodeRect.x() + nodeRect.width() - scaledViewportWidth, topLeftPoint.y());
break;
}
} else
anchor = renderer->style()->isLeftToRightDirection() ? topLeftPoint : FloatPoint(nodeRect.x() + nodeRect.width() - scaledViewportWidth, topLeftPoint.y());
if (newBlockHeight <= scaledViewportHeight) {
// The block fits in the viewport so center it.
d->m_finalBlockPoint = FloatPoint(anchor.x() - dx, anchor.y() - dy);
} else {
// The block is longer than the viewport so top align it and add 3 pixel margin.
d->m_finalBlockPoint = FloatPoint(anchor.x() - dx, anchor.y() - 3);
}
#if ENABLE(VIEWPORT_REFLOW)
// We don't know how long the reflowed block will be so we position it at the top of the screen with a small margin.
if (settings()->textReflowMode() != WebSettings::TextReflowDisabled) {
d->m_finalBlockPoint = FloatPoint(anchor.x() - dx, anchor.y() - 3);
d->m_finalBlockPointReflowOffset = FloatPoint(-dx, -3);
}
#endif
// Make sure that the original node rect is visible in the screen after the zoom. This is necessary because the identified block rect might
// not be the same as the original node rect, and it could force the original node rect off the screen.
FloatRect br(anchor, FloatSize(scaledViewportWidth, scaledViewportHeight));
if (!br.contains(IntPoint(documentTargetPoint))) {
d->m_finalBlockPointReflowOffset.move(0, (documentTargetPoint.y() - scaledViewportHeight / 2) - d->m_finalBlockPoint.y());
d->m_finalBlockPoint = FloatPoint(d->m_finalBlockPoint.x(), documentTargetPoint.y() - scaledViewportHeight / 2);
}
// Clamp the finalBlockPoint to not cause any overflow scrolling.
if (d->m_finalBlockPoint.x() < 0) {
d->m_finalBlockPoint.setX(0);
d->m_finalBlockPointReflowOffset.setX(0);
} else if (d->m_finalBlockPoint.x() + scaledViewportWidth > d->contentsSize().width()) {
d->m_finalBlockPoint.setX(d->contentsSize().width() - scaledViewportWidth);
d->m_finalBlockPointReflowOffset.setX(0);
}
if (d->m_finalBlockPoint.y() < 0) {
d->m_finalBlockPoint.setY(0);
d->m_finalBlockPointReflowOffset.setY(0);
} else if (d->m_finalBlockPoint.y() + scaledViewportHeight > d->contentsSize().height()) {
d->m_finalBlockPoint.setY(d->contentsSize().height() - scaledViewportHeight);
d->m_finalBlockPointReflowOffset.setY(0);
}
// Don't block zoom if the user is zooming and the new scale is only marginally different from the
// oldScale with only a marginal change in scroll position. Ignore scroll difference in the special case
// that the zoom level is the minimumScale.
if (!endOfBlockZoomMode && abs(newScale - oldScale) / oldScale < minimumExpandingRatio) {
const double minimumDisplacement = minimumExpandingRatio * webkitThreadViewportAccessor()->documentViewportSize().width();
if (oldScale == d->minimumScale() || (distanceBetweenPoints(d->scrollPosition(), roundUntransformedPoint(d->m_finalBlockPoint)) < minimumDisplacement && abs(newScale - oldScale) / oldScale < 0.10)) {
if (isFirstZoom) {
d->resetBlockZoom();
return false;
}
// Zoom out of block zoom.
blockZoom(documentTargetPoint);
return true;
}
}
d->m_blockZoomFinalScale = newScale;
// We set this here to make sure we don't try to re-render the page at a different zoom level during loading.
d->m_userPerformedManualZoom = true;
d->m_userPerformedManualScroll = true;
d->m_client->animateBlockZoom(d->m_blockZoomFinalScale, d->m_finalBlockPoint);
return true;
}
bool WebPage::isMaxZoomed() const
{
return (d->currentScale() == d->maximumScale()) || !d->isUserScalable();
}
bool WebPage::isMinZoomed() const
{
return (d->currentScale() == d->minimumScale()) || !d->isUserScalable();
}
bool WebPage::isAtInitialZoom() const
{
return (d->currentScale() == d->initialScale()) || !d->isUserScalable();
}
class DeferredTaskSetFocused: public DeferredTask<&WebPagePrivate::m_wouldSetFocused> {
public:
explicit DeferredTaskSetFocused(WebPagePrivate* webPagePrivate, bool focused)
: DeferredTaskType(webPagePrivate)
{
webPagePrivate->m_cachedFocused = focused;
}
private:
virtual void performInternal(WebPagePrivate* webPagePrivate)
{
webPagePrivate->m_webPage->setFocused(webPagePrivate->m_cachedFocused);
}
};
void WebPage::setFocused(bool focused)
{
if (d->m_page->defersLoading()) {
d->m_deferredTasks.append(adoptPtr(new DeferredTaskSetFocused(d, focused)));
return;
}
DeferredTaskSetFocused::finishOrCancel(d);
FocusController* focusController = d->m_page->focusController();
focusController->setActive(focused);
if (focused) {
Frame* frame = focusController->focusedFrame();
if (!frame)
focusController->setFocusedFrame(d->m_mainFrame);
}
focusController->setFocused(focused);
}
bool WebPage::findNextString(const char* text, bool forward, bool caseSensitive, bool wrap, bool highlightAllMatches)
{
WebCore::FindOptions findOptions = WebCore::StartInSelection;
if (!forward)
findOptions |= WebCore::Backwards;
if (!caseSensitive)
findOptions |= WebCore::CaseInsensitive;
// The WebCore::FindOptions::WrapAround boolean actually wraps the search
// within the current frame as opposed to the entire Document, so we have to
// provide our own wrapping code to wrap at the whole Document level.
return d->m_inPageSearchManager->findNextString(String::fromUTF8(text), findOptions, wrap, highlightAllMatches);
}
void WebPage::runLayoutTests()
{
#if !defined(PUBLIC_BUILD) || !PUBLIC_BUILD
// FIXME: do we need API to toggle this?
d->m_page->settings()->setDeveloperExtrasEnabled(true);
if (!d->m_dumpRenderTree)
d->m_dumpRenderTree = new DumpRenderTree(this);
d->m_dumpRenderTree->runTests();
#endif
}
unsigned WebPage::timeoutForJavaScriptExecution() const
{
return Settings::timeoutForJavaScriptExecution(d->m_page->groupName());
}
void WebPage::setTimeoutForJavaScriptExecution(unsigned ms)
{
Settings::setTimeoutForJavaScriptExecution(d->m_page->groupName(), ms);
Document* doc = d->m_page->mainFrame()->document();
if (!doc)
return;
doc->globalData()->timeoutChecker.setTimeoutInterval(ms);
}
JSContextRef WebPage::scriptContext() const
{
if (!d->m_mainFrame)
return 0;
JSC::Bindings::RootObject *root = d->m_mainFrame->script()->bindingRootObject();
if (!root)
return 0;
JSC::ExecState *exec = root->globalObject()->globalExec();
return toRef(exec);
}
JSValueRef WebPage::windowObject() const
{
return toRef(d->m_mainFrame->script()->globalObject(mainThreadNormalWorld()));
}
// Serialize only the members of HistoryItem which are needed by the client,
// and copy them into a SharedArray. Also include the HistoryItem pointer which
// will be used by the client as an opaque reference to identify the item.
void WebPage::getBackForwardList(SharedArray<BackForwardEntry>& result) const
{
HistoryItemVector entries = static_cast<BackForwardListImpl*>(d->m_page->backForward()->client())->entries();
result.reset(new BackForwardEntry[entries.size()], entries.size());
for (unsigned i = 0; i < entries.size(); ++i) {
RefPtr<HistoryItem> entry = entries[i];
BackForwardEntry& resultEntry = result[i];
resultEntry.url = entry->urlString();
resultEntry.originalUrl = entry->originalURLString();
resultEntry.title = entry->title();
resultEntry.networkToken = entry->viewState().networkToken;
resultEntry.lastVisitWasHTTPNonGet = entry->lastVisitWasHTTPNonGet();
resultEntry.id = backForwardIdFromHistoryItem(entry.get());
// FIXME: seems we can remove this now?
// Make sure the HistoryItem is not disposed while the result list is still being used, to make sure the pointer is not reused
// will be balanced by deref in releaseBackForwardEntry.
entry->ref();
}
}
void WebPage::releaseBackForwardEntry(BackForwardId id) const
{
HistoryItem* item = historyItemFromBackForwardId(id);
ASSERT(item);
item->deref();
}
void WebPage::clearBrowsingData()
{
clearMemoryCaches();
clearAppCache(d->m_page->groupName());
clearLocalStorage();
clearCookieCache();
clearHistory();
clearPluginSiteData();
}
void WebPage::clearHistory()
{
// Don't clear the back-forward list as we might like to keep it.
PageGroup::removeAllVisitedLinks();
}
void WebPage::clearCookies()
{
clearCookieCache();
}
void WebPage::clearLocalStorage()
{
if (PageGroup* group = d->m_page->groupPtr()) {
if (StorageNamespace* storage = group->localStorage())
storage->clearAllOriginsForDeletion();
}
}
void WebPage::clearCredentials()
{
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
if (d->m_webSettings->isCredentialAutofillEnabled())
credentialManager().clearCredentials();
#endif
}
void WebPage::clearAutofillData()
{
if (d->m_webSettings->isFormAutofillEnabled())
AutofillManager::clear();
}
void WebPage::clearNeverRememberSites()
{
#if ENABLE(BLACKBERRY_CREDENTIAL_PERSIST)
if (d->m_webSettings->isCredentialAutofillEnabled())
credentialManager().clearNeverRememberSites();
#endif
}
void WebPage::clearCache()
{
clearMemoryCaches();
clearAppCache(d->m_page->groupName());
}
void WebPage::clearBackForwardList(bool keepCurrentPage) const
{
BackForwardListImpl* backForwardList = static_cast<BackForwardListImpl*>(d->m_page->backForward()->client());
RefPtr<HistoryItem> currentItem = backForwardList->currentItem();
while (!backForwardList->entries().isEmpty())
backForwardList->removeItem(backForwardList->entries().last().get());
if (keepCurrentPage)
backForwardList->addItem(currentItem);
}
bool WebPage::isEnableLocalAccessToAllCookies() const
{
return cookieManager().canLocalAccessAllCookies();
}
void WebPage::setEnableLocalAccessToAllCookies(bool enabled)
{
cookieManager().setCanLocalAccessAllCookies(enabled);
}
void WebPage::addVisitedLink(const unsigned short* url, unsigned int length)
{
ASSERT(d->m_page);
d->m_page->group().addVisitedLink(url, length);
}
#if ENABLE(WEBDOM)
WebDOMDocument WebPage::document() const
{
if (!d->m_mainFrame)
return WebDOMDocument();
return WebDOMDocument(d->m_mainFrame->document());
}
WebDOMNode WebPage::nodeAtDocumentPoint(const Platform::IntPoint& documentPoint)
{
HitTestResult result = d->m_mainFrame->eventHandler()->hitTestResultAtPoint(WebCore::IntPoint(documentPoint), false);
Node* node = result.innerNonSharedNode();
return WebDOMNode(node);
}
bool WebPage::getNodeRect(const WebDOMNode& node, Platform::IntRect& result)
{
Node* nodeImpl = node.impl();
if (nodeImpl && nodeImpl->renderer()) {
result = nodeImpl->getRect();
return true;
}
return false;
}
bool WebPage::setNodeFocus(const WebDOMNode& node, bool on)
{
Node* nodeImpl = node.impl();
if (nodeImpl && nodeImpl->isFocusable()) {
Document* doc = nodeImpl->document();
if (Page* page = doc->page()) {
// Modify if focusing on node or turning off focused node.
if (on) {
page->focusController()->setFocusedNode(nodeImpl, doc->frame());
if (nodeImpl->isElementNode())
static_cast<Element*>(nodeImpl)->updateFocusAppearance(true);
d->m_inputHandler->didNodeOpenPopup(nodeImpl);
} else if (doc->focusedNode() == nodeImpl) // && !on
page->focusController()->setFocusedNode(0, doc->frame());
return true;
}
}
return false;
}
bool WebPage::setNodeHovered(const WebDOMNode& node, bool on)
{
if (Node* nodeImpl = node.impl()) {
nodeImpl->setHovered(on);
return true;
}
return false;
}
bool WebPage::nodeHasHover(const WebDOMNode& node)
{
if (Node* nodeImpl = node.impl()) {
if (RenderStyle* style = nodeImpl->renderStyle())
return style->affectedByHoverRules();
}
return false;
}
#endif
void WebPage::initPopupWebView(BlackBerry::WebKit::WebPage* webPage)
{
d->m_selectPopup->init(webPage);
}
String WebPagePrivate::findPatternStringForUrl(const KURL& url) const
{
if ((m_webSettings->shouldHandlePatternUrls() && protocolIs(url, "pattern"))
|| protocolIs(url, "tel")
|| protocolIs(url, "wtai")
|| protocolIs(url, "cti")
|| protocolIs(url, "mailto")
|| protocolIs(url, "sms")
|| protocolIs(url, "pin")) {
return url;
}
return String();
}
bool WebPage::defersLoading() const
{
return d->m_page->defersLoading();
}
void WebPage::notifyPagePause()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handlePauseEvent();
}
void WebPage::notifyPageResume()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleResumeEvent();
}
void WebPage::notifyPageBackground()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleBackgroundEvent();
}
void WebPage::notifyPageForeground()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleForegroundEvent();
}
void WebPage::notifyPageFullScreenAllowed()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleFullScreenAllowedEvent();
}
void WebPage::notifyPageFullScreenExit()
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleFullScreenExitEvent();
}
void WebPage::notifyDeviceIdleStateChange(bool enterIdle)
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleIdleEvent(enterIdle);
}
void WebPagePrivate::notifyAppActivationStateChange(ActivationStateType activationState)
{
m_activationState = activationState;
#if ENABLE(PAGE_VISIBILITY_API)
setPageVisibilityState();
#endif
}
void WebPage::notifyAppActivationStateChange(ActivationStateType activationState)
{
#if ENABLE(VIDEO)
MediaPlayerPrivate::notifyAppActivatedEvent(activationState == ActivationActive);
#endif
FOR_EACH_PLUGINVIEW(d->m_pluginViews) {
switch (activationState) {
case ActivationActive:
(*it)->handleAppActivatedEvent();
break;
case ActivationInactive:
(*it)->handleAppDeactivatedEvent();
break;
case ActivationStandby:
(*it)->handleAppStandbyEvent();
break;
}
}
d->notifyAppActivationStateChange(activationState);
}
void WebPage::notifySwipeEvent()
{
if (d->m_fullScreenPluginView.get())
d->m_fullScreenPluginView->handleSwipeEvent();
else
notifyFullScreenVideoExited(true);
}
void WebPage::notifyScreenPowerStateChanged(bool powered)
{
FOR_EACH_PLUGINVIEW(d->m_pluginViews)
(*it)->handleScreenPowerEvent(powered);
}
void WebPage::notifyFullScreenVideoExited(bool done)
{
UNUSED_PARAM(done);
#if ENABLE(VIDEO)
if (d->m_webSettings->fullScreenVideoCapable()) {
if (HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(d->m_fullscreenVideoNode.get()))
mediaElement->exitFullscreen();
} else {
#if ENABLE(FULLSCREEN_API)
if (Element* element = static_cast<Element*>(d->m_fullscreenVideoNode.get()))
element->document()->webkitCancelFullScreen();
#endif
}
#endif
}
void WebPage::clearPluginSiteData()
{
PluginDatabase* database = PluginDatabase::installedPlugins(true);
if (!database)
return;
Vector<PluginPackage*> plugins = database->plugins();
Vector<PluginPackage*>::const_iterator end = plugins.end();
for (Vector<PluginPackage*>::const_iterator it = plugins.begin(); it != end; ++it)
(*it)->clearSiteData(String());
}
void WebPage::onNetworkAvailabilityChanged(bool available)
{
updateOnlineStatus(available);
}
void WebPage::onCertificateStoreLocationSet(const BlackBerry::Platform::String& caPath)
{
#if ENABLE(VIDEO)
MediaPlayerPrivate::setCertificatePath(caPath);
#endif
}
void WebPage::enableDNSPrefetch()
{
d->m_page->settings()->setDNSPrefetchingEnabled(true);
}
void WebPage::disableDNSPrefetch()
{
d->m_page->settings()->setDNSPrefetchingEnabled(false);
}
bool WebPage::isDNSPrefetchEnabled() const
{
return d->m_page->settings()->dnsPrefetchingEnabled();
}
void WebPage::enableWebInspector()
{
if (!d->m_inspectorClient)
return;
d->m_page->inspectorController()->connectFrontend(d->m_inspectorClient);
d->m_page->settings()->setDeveloperExtrasEnabled(true);
}
void WebPage::disableWebInspector()
{
d->m_page->inspectorController()->disconnectFrontend();
d->m_page->settings()->setDeveloperExtrasEnabled(false);
}
bool WebPage::isWebInspectorEnabled()
{
return d->m_page->settings()->developerExtrasEnabled();
}
void WebPage::enablePasswordEcho()
{
d->m_page->settings()->setPasswordEchoEnabled(true);
}
void WebPage::disablePasswordEcho()
{
d->m_page->settings()->setPasswordEchoEnabled(false);
}
void WebPage::dispatchInspectorMessage(const BlackBerry::Platform::String& message)
{
d->m_page->inspectorController()->dispatchMessageFromFrontend(message);
}
void WebPage::inspectCurrentContextElement()
{
if (isWebInspectorEnabled() && d->m_currentContextNode.get())
d->m_page->inspectorController()->inspect(d->m_currentContextNode.get());
}
bool WebPagePrivate::compositorDrawsRootLayer() const
{
#if USE(ACCELERATED_COMPOSITING)
if (Platform::userInterfaceThreadMessageClient()->isCurrentThread())
return m_compositor && m_compositor->drawsRootLayer();
// WebKit thread implementation:
RenderView* renderView = m_mainFrame->contentRenderer();
if (!renderView || !renderView->layer() || !renderView->layer()->backing())
return false;
return !renderView->layer()->backing()->paintingGoesToWindow();
#else
return false;
#endif
}
void WebPagePrivate::setCompositorDrawsRootLayer(bool compositorDrawsRootLayer)
{
#if USE(ACCELERATED_COMPOSITING)
if (m_page->settings()->forceCompositingMode() == compositorDrawsRootLayer)
return;
// When the BlackBerry port forces compositing mode, the root layer stops
// painting to window and starts painting to layer instead.
m_page->settings()->setForceCompositingMode(compositorDrawsRootLayer);
if (!m_mainFrame)
return;
if (FrameView* view = m_mainFrame->view())
view->updateCompositingLayers();
#endif
}
#if USE(ACCELERATED_COMPOSITING)
void WebPagePrivate::scheduleRootLayerCommit()
{
if (!(m_frameLayers && m_frameLayers->hasLayer()) && !m_overlayLayer)
return;
m_needsCommit = true;
if (!m_rootLayerCommitTimer->isActive()) {
#if DEBUG_AC_COMMIT
BBLOG(Platform::LogLevelCritical, "%s: m_rootLayerCommitTimer->isActive() = %d", WTF_PRETTY_FUNCTION, m_rootLayerCommitTimer->isActive());
#endif
m_rootLayerCommitTimer->startOneShot(0);
}
}
static bool needsLayoutRecursive(FrameView* view)
{
if (view->needsLayout())
return true;
bool subframesNeedsLayout = false;
const HashSet<RefPtr<Widget> >* viewChildren = view->children();
HashSet<RefPtr<Widget> >::const_iterator end = viewChildren->end();
for (HashSet<RefPtr<Widget> >::const_iterator current = viewChildren->begin(); current != end && !subframesNeedsLayout; ++current) {
Widget* widget = (*current).get();
if (widget->isFrameView())
subframesNeedsLayout |= needsLayoutRecursive(static_cast<FrameView*>(widget));
}
return subframesNeedsLayout;
}
LayerRenderingResults WebPagePrivate::lastCompositingResults() const
{
if (m_compositor)
return m_compositor->lastCompositingResults();
return LayerRenderingResults();
}
GraphicsLayer* WebPagePrivate::overlayLayer()
{
if (!m_overlayLayer)
m_overlayLayer = GraphicsLayer::create(0, this);
return m_overlayLayer.get();
}
void WebPagePrivate::setCompositor(PassRefPtr<WebPageCompositorPrivate> compositor)
{
using namespace BlackBerry::Platform;
// We depend on the current thread being the WebKit thread when it's not the Compositing thread.
// That seems extremely likely to be the case, but let's assert just to make sure.
ASSERT(webKitThreadMessageClient()->isCurrentThread());
if (m_compositor || m_client->window())
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
// The m_compositor member has to be modified during a sync call for thread
// safe access to m_compositor and its refcount.
userInterfaceThreadMessageClient()->dispatchSyncMessage(createMethodCallMessage(&WebPagePrivate::setCompositorHelper, this, compositor));
if (m_compositor || m_client->window()) // the new compositor, if one was set
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
}
void WebPagePrivate::setCompositorHelper(PassRefPtr<WebPageCompositorPrivate> compositor)
{
using namespace BlackBerry::Platform;
// The m_compositor member has to be modified during a sync call for thread
// safe access to m_compositor and its refcount.
ASSERT(userInterfaceThreadMessageClient()->isCurrentThread());
m_compositor = compositor;
if (m_compositor) {
m_compositor->setPage(this);
m_compositor->setBackgroundColor(m_webSettings->backgroundColor());
}
// The previous compositor, if any, has now released it's OpenGL resources,
// so we can safely free the owned context, if any.
m_ownedContext.clear();
}
void WebPagePrivate::setCompositorBackgroundColor(const Color& backgroundColor)
{
if (m_compositor)
m_compositor->setBackgroundColor(backgroundColor);
}
void WebPagePrivate::commitRootLayer(const IntRect& layoutRectForCompositing,
const IntSize& contentsSizeForCompositing,
bool drawsRootLayer)
{
#if DEBUG_AC_COMMIT
BBLOG(Platform::LogLevelCritical, "%s: m_compositor = 0x%x",
WTF_PRETTY_FUNCTION, m_compositor.get());
#endif
if (!m_compositor)
return;
// Frame layers
LayerWebKitThread* rootLayer = 0;
if (m_frameLayers)
rootLayer = m_frameLayers->rootLayer();
if (rootLayer && rootLayer->layerCompositingThread() != m_compositor->rootLayer())
m_compositor->setRootLayer(rootLayer->layerCompositingThread());
// Overlay layers
LayerWebKitThread* overlayLayer = 0;
if (m_overlayLayer)
overlayLayer = m_overlayLayer->platformLayer();
if (overlayLayer && overlayLayer->layerCompositingThread() != m_compositor->overlayLayer())
m_compositor->setOverlayLayer(overlayLayer->layerCompositingThread());
m_compositor->setLayoutRectForCompositing(layoutRectForCompositing);
m_compositor->setContentsSizeForCompositing(contentsSizeForCompositing);
m_compositor->setDrawsRootLayer(drawsRootLayer);
if (rootLayer)
rootLayer->commitOnCompositingThread();
if (overlayLayer)
overlayLayer->commitOnCompositingThread();
scheduleCompositingRun();
}
bool WebPagePrivate::commitRootLayerIfNeeded()
{
#if DEBUG_AC_COMMIT
BBLOG(Platform::LogLevelCritical, "%s: m_suspendRootLayerCommit = %d, m_needsCommit = %d, m_frameLayers = 0x%x, m_frameLayers->hasLayer() = %d, needsLayoutRecursive() = %d",
WTF_PRETTY_FUNCTION,
m_suspendRootLayerCommit,
m_needsCommit,
m_frameLayers.get(),
m_frameLayers && m_frameLayers->hasLayer(),
m_mainFrame && m_mainFrame->view() && needsLayoutRecursive(m_mainFrame->view()));
#endif
if (m_suspendRootLayerCommit)
return false;
if (!m_needsCommit)
return false;
// Don't bail if the layers were removed and we now need a one shot drawing sync as a consequence.
if (!(m_frameLayers && m_frameLayers->hasLayer()) && !m_overlayLayer
&& !m_needsOneShotDrawingSynchronization)
return false;
FrameView* view = m_mainFrame->view();
if (!view)
return false;
// This can do pretty much anything depending on the overlay,
// so in case it causes relayout or schedule a commit, call it early.
updateDelegatedOverlays();
// If we sync compositing layers when a layout is pending, we may cause painting of compositing
// layer content to occur before layout has happened, which will cause paintContents() to bail.
if (needsLayoutRecursive(view)) {
// In case of one shot drawing synchronization, you
// should first layoutIfNeeded, render, then commit and draw the layers.
ASSERT(!needsOneShotDrawingSynchronization());
return false;
}
willComposite();
m_needsCommit = false;
// We get here either due to the commit timer, which would have called
// render if a one shot sync was needed. Or we get called from render
// before the timer times out, which means we are doing a one shot anyway.
m_needsOneShotDrawingSynchronization = false;
if (m_rootLayerCommitTimer->isActive())
m_rootLayerCommitTimer->stop();
double scale = currentScale();
if (m_frameLayers && m_frameLayers->hasLayer())
m_frameLayers->commitOnWebKitThread(scale);
if (m_overlayLayer)
m_overlayLayer->platformLayer()->commitOnWebKitThread(scale);
// Stash the visible content rect according to webkit thread
// This is the rectangle used to layout fixed positioned elements,
// and that's what the layer renderer wants.
IntRect layoutRectForCompositing(scrollPosition(), actualVisibleSize());
IntSize contentsSizeForCompositing = contentsSize();
bool drawsRootLayer = compositorDrawsRootLayer();
// Commit changes made to the layers synchronously with the compositing thread.
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
Platform::createMethodCallMessage(
&WebPagePrivate::commitRootLayer,
this,
layoutRectForCompositing,
contentsSizeForCompositing,
drawsRootLayer));
didComposite();
return true;
}
void WebPagePrivate::rootLayerCommitTimerFired(Timer<WebPagePrivate>*)
{
if (m_suspendRootLayerCommit)
return;
#if DEBUG_AC_COMMIT
BBLOG(Platform::LogLevelCritical, "%s", WTF_PRETTY_FUNCTION);
#endif
m_backingStore->d->instrumentBeginFrame();
// The commit timer may have fired just before the layout timer, or for some
// other reason we need layout. It's not allowed to commit when a layout is
// pending, becaues a commit can cause parts of the web page to be rendered
// to texture.
// The layout can also turn of compositing altogether, so we need to be prepared
// to handle a one shot drawing synchronization after the layout.
requestLayoutIfNeeded();
// If we transitioned to drawing the root layer using compositor instead of
// backing store, doing a one shot drawing synchronization with the
// backing store is never necessary, because the backing store draws
// nothing.
if (!compositorDrawsRootLayer()) {
// If we are doing direct rendering and have a single rendering target,
// committing is equivalent to a one shot drawing synchronization.
// We need to re-render the web page, re-render the layers, and
// then blit them on top of the re-rendered web page.
if (m_backingStore->d->isOpenGLCompositing() && m_backingStore->d->shouldDirectRenderingToWindow())
setNeedsOneShotDrawingSynchronization();
if (needsOneShotDrawingSynchronization()) {
#if DEBUG_AC_COMMIT
BBLOG(Platform::LogLevelCritical, "%s: OneShotDrawingSynchronization code path!", WTF_PRETTY_FUNCTION);
#endif
const IntRect windowRect = IntRect(IntPoint::zero(), viewportSize());
m_backingStore->d->repaint(windowRect, true /*contentChanged*/, true /*immediate*/);
return;
}
}
commitRootLayerIfNeeded();
}
void WebPagePrivate::setRootLayerWebKitThread(Frame* frame, LayerWebKitThread* layer)
{
// This method updates the FrameLayers based on input from WebCore.
// FrameLayers keeps track of the layer proxies attached to frames.
// We will have to compute a new root layer and update the compositor.
if (!layer && !m_frameLayers)
return;
if (!layer) {
ASSERT(m_frameLayers);
m_frameLayers->removeLayerByFrame(frame);
if (!m_frameLayers->hasLayer())
m_frameLayers.clear();
} else {
if (!m_frameLayers)
m_frameLayers = adoptPtr(new FrameLayers(this));
if (!m_frameLayers->containsLayerForFrame(frame))
m_frameLayers->addLayer(frame, layer);
ASSERT(m_frameLayers);
}
LayerCompositingThread* rootLayerCompositingThread = 0;
if (m_frameLayers && m_frameLayers->rootLayer())
rootLayerCompositingThread = m_frameLayers->rootLayer()->layerCompositingThread();
setRootLayerCompositingThread(rootLayerCompositingThread);
}
void WebPagePrivate::setRootLayerCompositingThread(LayerCompositingThread* layer)
{
if (!Platform::userInterfaceThreadMessageClient()->isCurrentThread()) {
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
Platform::createMethodCallMessage(&WebPagePrivate::setRootLayerCompositingThread, this, layer));
return;
}
if (layer && !m_compositor)
createCompositor();
// Don't ASSERT(m_compositor) here because setIsAcceleratedCompositingActive(true)
// may not turn accelerated compositing on since m_backingStore is 0.
if (m_compositor)
m_compositor->setRootLayer(layer);
}
bool WebPagePrivate::createCompositor()
{
// If there's no window, the compositor will be supplied by the API client
if (!m_client->window())
return false;
m_ownedContext = GLES2Context::create(this);
m_compositor = WebPageCompositorPrivate::create(this, 0);
m_compositor->setContext(m_ownedContext.get());
// The compositor is created in a sync message, so there's no risk of race condition on the
// WebSettings.
m_compositor->setBackgroundColor(m_webSettings->backgroundColor());
return true;
}
void WebPagePrivate::destroyCompositor()
{
// m_compositor is a RefPtr, so it may live on beyond this point.
// Disconnect the compositor from us
m_compositor->detach();
m_compositor.clear();
m_ownedContext.clear();
}
void WebPagePrivate::syncDestroyCompositorOnCompositingThread()
{
if (!m_compositor)
return;
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
Platform::createMethodCallMessage(
&WebPagePrivate::destroyCompositor, this));
}
void WebPagePrivate::releaseLayerResources()
{
if (!isAcceleratedCompositingActive())
return;
if (m_frameLayers)
m_frameLayers->releaseLayerResources();
Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage(
Platform::createMethodCallMessage(&WebPagePrivate::releaseLayerResourcesCompositingThread, this));
}
void WebPagePrivate::releaseLayerResourcesCompositingThread()
{
m_compositor->releaseLayerResources();
}
void WebPagePrivate::suspendRootLayerCommit()
{
if (m_suspendRootLayerCommit)
return;
m_suspendRootLayerCommit = true;
if (!m_compositor)
return;
releaseLayerResources();
}
void WebPagePrivate::resumeRootLayerCommit()
{
if (!m_suspendRootLayerCommit)
return;
m_suspendRootLayerCommit = false;
m_needsCommit = true;
}
bool WebPagePrivate::needsOneShotDrawingSynchronization()
{
return m_needsOneShotDrawingSynchronization;
}
void WebPagePrivate::setNeedsOneShotDrawingSynchronization()
{
if (compositorDrawsRootLayer()) {
scheduleRootLayerCommit();
return;
}
// This means we have to commit layers on next render, or render on the next commit,
// whichever happens first.
m_needsCommit = true;
m_needsOneShotDrawingSynchronization = true;
}
void WebPagePrivate::notifyFlushRequired(const GraphicsLayer*)
{
scheduleRootLayerCommit();
}
#endif // USE(ACCELERATED_COMPOSITING)
void WebPagePrivate::enterFullscreenForNode(Node* node)
{
#if ENABLE(VIDEO)
if (!node || !node->hasTagName(HTMLNames::videoTag))
return;
MediaPlayer* player = static_cast<HTMLMediaElement*>(node)->player();
if (!player)
return;
MediaPlayerPrivate* mmrPlayer = static_cast<MediaPlayerPrivate*>(player->implementation());
if (!mmrPlayer)
return;
Platform::Graphics::Window* window = mmrPlayer->getWindow();
if (!window)
return;
const char* contextName = mmrPlayer->mmrContextName();
if (!contextName)
return;
mmrPlayer->setFullscreenWebPageClient(m_client);
m_fullscreenVideoNode = node;
m_client->fullscreenStart(contextName, window, mmrPlayer->getWindowScreenRect());
#endif
}
void WebPagePrivate::exitFullscreenForNode(Node* node)
{
#if ENABLE(VIDEO)
if (m_fullscreenVideoNode.get()) {
m_client->fullscreenStop();
m_fullscreenVideoNode = 0;
}
if (!node || !node->hasTagName(HTMLNames::videoTag))
return;
MediaPlayer* player = static_cast<HTMLMediaElement*>(node)->player();
if (!player)
return;
MediaPlayerPrivate* mmrPlayer = static_cast<MediaPlayerPrivate*>(player->implementation());
if (!mmrPlayer)
return;
// Fullscreen mode is being turned off, so MediaPlayerPrivate no longer needs the pointer.
mmrPlayer->setFullscreenWebPageClient(0);
#endif
}
#if ENABLE(FULLSCREEN_API)
// TODO: We should remove this helper class when we decide to support all elements.
static bool containsVideoTags(Element* element)
{
for (Node* node = element->firstChild(); node; node = node->traverseNextNode(element)) {
if (node->hasTagName(HTMLNames::videoTag))
return true;
}
return false;
}
void WebPagePrivate::enterFullScreenForElement(Element* element)
{
#if ENABLE(VIDEO)
// TODO: We should not check video tag when we decide to support all elements.
if (!element || (!element->hasTagName(HTMLNames::videoTag) && !containsVideoTags(element)))
return;
if (m_webSettings->fullScreenVideoCapable()) {
// The Browser chrome has its own fullscreen video widget it wants to
// use, and this is a video element. The only reason that
// webkitWillEnterFullScreenForElement() and
// webkitDidEnterFullScreenForElement() are still called in this case
// is so that exitFullScreenForElement() gets called later.
enterFullscreenForNode(element);
} else {
// When an element goes fullscreen, the viewport size changes and the scroll
// position might change. So we keep track of it here, in order to restore it
// once element leaves fullscreen.
WebCore::IntPoint scrollPosition = m_mainFrame->view()->scrollPosition();
m_xScrollOffsetBeforeFullScreen = scrollPosition.x();
// The current scale can be clamped to a greater minimum scale when we relayout contents during
// the change of the viewport size. Cache the current scale so that we can restore it when
// leaving fullscreen. Otherwise, it is possible that we will use the wrong scale.
m_scaleBeforeFullScreen = currentScale();
// No fullscreen video widget has been made available by the Browser
// chrome, or this is not a video element. The webkitRequestFullScreen
// Javascript call is often made on a div element.
// This is where we would hide the browser's chrome if we wanted to.
client()->fullscreenStart();
m_fullscreenVideoNode = element;
}
#endif
}
void WebPagePrivate::exitFullScreenForElement(Element* element)
{
#if ENABLE(VIDEO)
// TODO: We should not check video tag when we decide to support all elements.
if (!element || (!element->hasTagName(HTMLNames::videoTag) && !containsVideoTags(element)))
return;
if (m_webSettings->fullScreenVideoCapable()) {
// The Browser chrome has its own fullscreen video widget.
exitFullscreenForNode(element);
} else {
m_backingStore->d->suspendScreenAndBackingStoreUpdates();
// When leaving fullscreen mode, we need to restore the 'x' scroll position
// before fullscreen.
// FIXME: We may need to respect 'y' position as well, because the web page always scrolls to
// the top when leaving fullscreen mode.
WebCore::IntPoint scrollPosition = m_mainFrame->view()->scrollPosition();
m_mainFrame->view()->setScrollPosition(
WebCore::IntPoint(m_xScrollOffsetBeforeFullScreen, scrollPosition.y()));
m_xScrollOffsetBeforeFullScreen = -1;
if (m_scaleBeforeFullScreen > 0) {
// Restore the scale when leaving fullscreen. We can't use TransformationMatrix::scale(double) here, as it
// will multiply the scale rather than set the scale.
// FIXME: We can refactor this into setCurrentScale(double) if it is useful in the future.
m_transformationMatrix->setM11(m_scaleBeforeFullScreen);
m_transformationMatrix->setM22(m_scaleBeforeFullScreen);
m_scaleBeforeFullScreen = -1.0;
}
notifyTransformChanged();
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
// This is where we would restore the browser's chrome
// if hidden above.
client()->fullscreenStop();
m_fullscreenVideoNode = 0;
}
#endif
}
#endif
void WebPagePrivate::didChangeSettings(WebSettings* webSettings)
{
Settings* coreSettings = m_page->settings();
m_page->setGroupName(webSettings->pageGroupName());
coreSettings->setXSSAuditorEnabled(webSettings->xssAuditorEnabled());
coreSettings->setLoadsImagesAutomatically(webSettings->loadsImagesAutomatically());
coreSettings->setShouldDrawBorderWhileLoadingImages(webSettings->shouldDrawBorderWhileLoadingImages());
coreSettings->setScriptEnabled(webSettings->isJavaScriptEnabled());
coreSettings->setPrivateBrowsingEnabled(webSettings->isPrivateBrowsingEnabled());
coreSettings->setDeviceSupportsMouse(webSettings->deviceSupportsMouse());
coreSettings->setDefaultFixedFontSize(webSettings->defaultFixedFontSize());
coreSettings->setDefaultFontSize(webSettings->defaultFontSize());
coreSettings->setMinimumLogicalFontSize(webSettings->minimumFontSize());
if (!webSettings->serifFontFamily().empty())
coreSettings->setSerifFontFamily(String(webSettings->serifFontFamily()));
if (!webSettings->fixedFontFamily().empty())
coreSettings->setFixedFontFamily(String(webSettings->fixedFontFamily()));
if (!webSettings->sansSerifFontFamily().empty())
coreSettings->setSansSerifFontFamily(String(webSettings->sansSerifFontFamily()));
if (!webSettings->standardFontFamily().empty())
coreSettings->setStandardFontFamily(String(webSettings->standardFontFamily()));
coreSettings->setJavaScriptCanOpenWindowsAutomatically(webSettings->canJavaScriptOpenWindowsAutomatically());
coreSettings->setAllowScriptsToCloseWindows(webSettings->canJavaScriptOpenWindowsAutomatically()); // Why are we using the same value as setJavaScriptCanOpenWindowsAutomatically()?
coreSettings->setPluginsEnabled(webSettings->arePluginsEnabled());
coreSettings->setDefaultTextEncodingName(webSettings->defaultTextEncodingName());
coreSettings->setDownloadableBinaryFontsEnabled(webSettings->downloadableBinaryFontsEnabled());
coreSettings->setSpatialNavigationEnabled(m_webSettings->isSpatialNavigationEnabled());
coreSettings->setAsynchronousSpellCheckingEnabled(m_webSettings->isAsynchronousSpellCheckingEnabled());
BlackBerry::Platform::String stylesheetURL = webSettings->userStyleSheetLocation();
if (!stylesheetURL.empty())
coreSettings->setUserStyleSheetLocation(KURL(KURL(), stylesheetURL));
coreSettings->setFirstScheduledLayoutDelay(webSettings->firstScheduledLayoutDelay());
coreSettings->setUseCache(webSettings->useWebKitCache());
#if ENABLE(SQL_DATABASE)
// DatabaseTracker can only be initialized for once, so it doesn't
// make sense to change database path after DatabaseTracker has
// already been initialized.
static bool dbinit = false;
if (!dbinit && !webSettings->databasePath().empty()) {
dbinit = true;
DatabaseTracker::initializeTracker(webSettings->databasePath());
}
// The directory of cacheStorage for one page group can only be initialized once.
static bool acinit = false;
if (!acinit && !webSettings->appCachePath().empty()) {
acinit = true;
cacheStorage().setCacheDirectory(webSettings->appCachePath());
}
coreSettings->setLocalStorageDatabasePath(webSettings->localStoragePath());
Database::setIsAvailable(webSettings->isDatabasesEnabled());
DatabaseSync::setIsAvailable(webSettings->isDatabasesEnabled());
coreSettings->setLocalStorageEnabled(webSettings->isLocalStorageEnabled());
coreSettings->setOfflineWebApplicationCacheEnabled(webSettings->isAppCacheEnabled());
m_page->group().groupSettings()->setLocalStorageQuotaBytes(webSettings->localStorageQuota());
coreSettings->setSessionStorageQuota(webSettings->sessionStorageQuota());
coreSettings->setUsesPageCache(webSettings->maximumPagesInCache());
coreSettings->setFrameFlatteningEnabled(webSettings->isFrameFlatteningEnabled());
#endif
#if ENABLE(INDEXED_DATABASE)
m_page->group().groupSettings()->setIndexedDBDatabasePath(webSettings->indexedDataBasePath());
#endif
#if ENABLE(WEB_SOCKETS)
WebSocket::setIsAvailable(webSettings->areWebSocketsEnabled());
#endif
#if ENABLE(FULLSCREEN_API)
// This allows Javascript to call webkitRequestFullScreen() on an element.
coreSettings->setFullScreenEnabled(true);
#endif
#if ENABLE(VIEWPORT_REFLOW)
coreSettings->setTextReflowEnabled(webSettings->textReflowMode() == WebSettings::TextReflowEnabled);
#endif
// FIXME: We don't want HTMLTokenizer to yield for anything other than email case because
// call to currentTime() is too expensive on our platform. See RIM Bug #746.
coreSettings->setShouldUseFirstScheduledLayoutDelay(webSettings->isEmailMode());
coreSettings->setProcessHTTPEquiv(!webSettings->isEmailMode());
coreSettings->setShouldUseCrossOriginProtocolCheck(!webSettings->allowCrossSiteRequests());
coreSettings->setWebSecurityEnabled(!webSettings->allowCrossSiteRequests());
cookieManager().setPrivateMode(webSettings->isPrivateBrowsingEnabled());
CredentialStorage::setPrivateMode(webSettings->isPrivateBrowsingEnabled());
if (m_mainFrame && m_mainFrame->view()) {
Color backgroundColor(webSettings->backgroundColor());
m_mainFrame->view()->updateBackgroundRecursively(backgroundColor, backgroundColor.hasAlpha());
Platform::userInterfaceThreadMessageClient()->dispatchMessage(
createMethodCallMessage(&WebPagePrivate::setCompositorBackgroundColor, this, backgroundColor));
}
if (m_backingStore) {
m_backingStore->d->setWebPageBackgroundColor(m_mainFrame && m_mainFrame->view()
? m_mainFrame->view()->documentBackgroundColor()
: webSettings->backgroundColor());
}
m_page->setDeviceScaleFactor(webSettings->devicePixelRatio());
}
BlackBerry::Platform::String WebPage::textHasAttribute(const BlackBerry::Platform::String& query) const
{
if (Document* doc = d->m_page->focusController()->focusedOrMainFrame()->document())
return doc->queryCommandValue(query);
return "";
}
void WebPage::setAllowNotification(const BlackBerry::Platform::String& domain, bool allow)
{
#if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
static_cast<NotificationPresenterImpl*>(NotificationPresenterImpl::instance())->onPermission(domain.c_str(), allow);
#else
UNUSED_PARAM(domain);
UNUSED_PARAM(allow);
#endif
}
void WebPage::setJavaScriptCanAccessClipboard(bool enabled)
{
d->m_page->settings()->setJavaScriptCanAccessClipboard(enabled);
}
#if USE(ACCELERATED_COMPOSITING)
void WebPagePrivate::blitVisibleContents()
{
if (m_backingStore->d->shouldDirectRenderingToWindow())
return;
m_backingStore->d->blitVisibleContents();
}
void WebPagePrivate::scheduleCompositingRun()
{
if (WebPageCompositorClient* compositorClient = compositor()->client()) {
double animationTime = compositorClient->requestAnimationFrame();
compositorClient->invalidate(animationTime);
return;
}
blitVisibleContents();
}
#endif
void WebPage::setWebGLEnabled(bool enabled)
{
d->m_page->settings()->setWebGLEnabled(enabled);
}
bool WebPage::isWebGLEnabled() const
{
return d->m_page->settings()->webGLEnabled();
}
void WebPagePrivate::setNeedTouchEvents(bool value)
{
m_needTouchEvents = value;
}
void WebPagePrivate::frameUnloaded(const Frame* frame)
{
m_inputHandler->frameUnloaded(frame);
m_inPageSearchManager->frameUnloaded(frame);
}
const BlackBerry::Platform::String& WebPagePrivate::defaultUserAgent()
{
static BlackBerry::Platform::String* defaultUserAgent = 0;
if (!defaultUserAgent) {
BlackBerry::Platform::DeviceInfo* info = BlackBerry::Platform::DeviceInfo::instance();
char uaBuffer[256];
int uaSize = snprintf(uaBuffer, 256, "Mozilla/5.0 (%s) AppleWebKit/%d.%d+ (KHTML, like Gecko) Version/%s %sSafari/%d.%d+",
info->family(), WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, info->osVersion(),
info->isMobile() ? "Mobile " : "", WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION);
if (uaSize <= 0 || uaSize >= 256)
BLACKBERRY_CRASH();
defaultUserAgent = new BlackBerry::Platform::String(uaBuffer, uaSize);
}
return *defaultUserAgent;
}
WebTapHighlight* WebPage::tapHighlight() const
{
return d->m_tapHighlight.get();
}
void WebPage::addOverlay(WebOverlay* overlay)
{
#if USE(ACCELERATED_COMPOSITING)
if (overlay->d->graphicsLayer()) {
overlay->d->setPage(d);
d->overlayLayer()->addChild(overlay->d->graphicsLayer());
}
#endif
}
void WebPage::removeOverlay(WebOverlay* overlay)
{
#if USE(ACCELERATED_COMPOSITING)
if (overlay->d->graphicsLayer()->parent() != d->overlayLayer())
return;
overlay->removeFromParent();
overlay->d->clear();
overlay->d->setPage(0);
#endif
}
void WebPage::addCompositingThreadOverlay(WebOverlay* overlay)
{
#if USE(ACCELERATED_COMPOSITING)
ASSERT(Platform::userInterfaceThreadMessageClient()->isCurrentThread());
if (!d->compositor())
return;
overlay->d->setPage(d);
d->compositor()->addOverlay(overlay->d->layerCompositingThread());
#endif
}
void WebPage::removeCompositingThreadOverlay(WebOverlay* overlay)
{
#if USE(ACCELERATED_COMPOSITING)
ASSERT(Platform::userInterfaceThreadMessageClient()->isCurrentThread());
if (d->compositor())
d->compositor()->removeOverlay(overlay->d->layerCompositingThread());
overlay->d->clear();
overlay->d->setPage(0);
#endif
}
void WebPage::popupOpened(PagePopupBlackBerry* webPopup)
{
ASSERT(!d->m_selectPopup);
d->m_selectPopup = webPopup;
}
void WebPage::popupClosed()
{
ASSERT(d->m_selectPopup);
d->m_selectPopup = 0;
}
bool WebPage::hasOpenedPopup() const
{
return d->m_selectPopup;
}
PagePopupBlackBerry* WebPage::popup()
{
return d->m_selectPopup;
}
void WebPagePrivate::setInspectorOverlayClient(InspectorOverlay::InspectorOverlayClient* inspectorOverlayClient)
{
if (inspectorOverlayClient) {
if (!m_inspectorOverlay)
m_inspectorOverlay = InspectorOverlay::create(this, inspectorOverlayClient);
else
m_inspectorOverlay->setClient(inspectorOverlayClient);
m_inspectorOverlay->update();
scheduleRootLayerCommit();
} else {
if (m_inspectorOverlay) {
m_inspectorOverlay->clear();
m_inspectorOverlay = nullptr;
scheduleRootLayerCommit();
}
}
}
void WebPagePrivate::applySizeOverride(int overrideWidth, int overrideHeight)
{
m_client->requestUpdateViewport(overrideWidth, overrideHeight);
}
void WebPagePrivate::setTextZoomFactor(float textZoomFactor)
{
if (!m_mainFrame)
return;
m_mainFrame->setTextZoomFactor(textZoomFactor);
}
void WebPagePrivate::restoreHistoryViewState(Platform::IntSize contentsSize, Platform::IntPoint scrollPosition, double scale, bool shouldReflowBlock)
{
if (!m_mainFrame) {
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
return;
}
m_mainFrame->view()->setContentsSizeFromHistory(contentsSize);
// Here we need to set scroll position what we asked for.
// So we use ScrollView::constrainsScrollingToContentEdge(false).
bool oldConstrainsScrollingToContentEdge = m_mainFrame->view()->constrainsScrollingToContentEdge();
m_mainFrame->view()->setConstrainsScrollingToContentEdge(false);
setScrollPosition(scrollPosition);
m_mainFrame->view()->setConstrainsScrollingToContentEdge(oldConstrainsScrollingToContentEdge);
m_shouldReflowBlock = shouldReflowBlock;
bool didZoom = zoomAboutPoint(scale, m_mainFrame->view()->scrollPosition(), true /* enforceScaleClamping */, true /*forceRendering*/, true /*isRestoringZoomLevel*/);
// If we're already at that scale, then we should still force rendering
// since our scroll position changed.
m_backingStore->d->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
if (!didZoom) {
// We need to notify the client of the scroll position and content size change(s) above even if we didn't scale.
notifyTransformedContentsSizeChanged();
notifyTransformedScrollChanged();
}
}
IntSize WebPagePrivate::screenSize() const
{
return Platform::Graphics::Screen::primaryScreen()->size();
}
void WebPagePrivate::postponeDocumentStyleRecalc()
{
if (Document* document = m_mainFrame->document()) {
m_documentChildNeedsStyleRecalc = document->childNeedsStyleRecalc();
document->clearChildNeedsStyleRecalc();
m_documentStyleRecalcPostponed = document->isPendingStyleRecalc();
document->unscheduleStyleRecalc();
}
}
void WebPagePrivate::resumeDocumentStyleRecalc()
{
if (Document* document = m_mainFrame->document()) {
if (m_documentChildNeedsStyleRecalc)
document->setChildNeedsStyleRecalc();
if (m_documentStyleRecalcPostponed)
document->scheduleStyleRecalc();
}
m_documentChildNeedsStyleRecalc = false;
m_documentStyleRecalcPostponed = false;
}
const HitTestResult& WebPagePrivate::hitTestResult(const IntPoint& contentPos)
{
if (m_cachedHitTestContentPos != contentPos) {
m_cachedHitTestContentPos = contentPos;
m_cachedHitTestResult = m_mainFrame->eventHandler()->hitTestResultAtPoint(m_cachedHitTestContentPos, true /*allowShadowContent*/);
}
return m_cachedHitTestResult;
}
void WebPagePrivate::clearCachedHitTestResult()
{
m_cachedHitTestContentPos = WebCore::IntPoint(-1, -1);
}
void WebPagePrivate::willComposite()
{
if (!m_page->settings()->developerExtrasEnabled())
return;
InspectorInstrumentation::willComposite(m_page);
}
void WebPagePrivate::didComposite()
{
if (!m_page->settings()->developerExtrasEnabled())
return;
InspectorInstrumentation::didComposite(m_page);
}
}
}