blob: ae4a1b1cc9e8fb978966d16ffed42f59fbac6cfb [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebViewHost.h"
#include "LayoutTestController.h"
#include "TestNavigationController.h"
#include "TestShell.h"
#include "TestWebWorker.h"
#include "WebCString.h"
#include "WebConsoleMessage.h"
#include "WebContextMenuData.h"
#include "WebDataSource.h"
#include "WebDeviceOrientationClientMock.h"
#include "WebDragData.h"
#include "WebElement.h"
#include "WebFrame.h"
#include "WebGeolocationClientMock.h"
#include "WebHistoryItem.h"
#include "WebNode.h"
#include "WebRange.h"
#include "WebRect.h"
#include "WebScreenInfo.h"
#include "WebSize.h"
#include "WebSpeechInputControllerMock.h"
#include "WebStorageNamespace.h"
#include "WebTextCheckingCompletion.h"
#include "WebTextCheckingResult.h"
#include "WebURLRequest.h"
#include "WebURLResponse.h"
#include "WebView.h"
#include "WebWindowFeatures.h"
#include "skia/ext/platform_canvas.h"
#include "webkit/support/webkit_support.h"
#include <wtf/Assertions.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/Vector.h>
using namespace WebCore;
using namespace WebKit;
using namespace std;
static const int screenWidth = 1920;
static const int screenHeight = 1080;
static const int screenUnavailableBorder = 8;
// WebNavigationType debugging strings taken from PolicyDelegate.mm.
static const char* linkClickedString = "link clicked";
static const char* formSubmittedString = "form submitted";
static const char* backForwardString = "back/forward";
static const char* reloadString = "reload";
static const char* formResubmittedString = "form resubmitted";
static const char* otherString = "other";
static const char* illegalString = "illegal value";
static int nextPageID = 1;
// Used to write a platform neutral file:/// URL by only taking the filename
// (e.g., converts "file:///tmp/foo.txt" to just "foo.txt").
static string urlSuitableForTestResult(const string& url)
{
if (url.empty() || string::npos == url.find("file://"))
return url;
size_t pos = url.rfind('/');
if (pos == string::npos) {
#if OS(WINDOWS)
pos = url.rfind('\\');
if (pos == string::npos)
pos = 0;
#else
pos = 0;
#endif
}
string filename = url.substr(pos + 1);
if (filename.empty())
return "file:"; // A WebKit test has this in its expected output.
return filename;
}
// Used to write a platform neutral file:/// URL by taking the
// filename and its directory. (e.g., converts
// "file:///tmp/foo/bar.txt" to just "bar.txt").
static string descriptionSuitableForTestResult(const string& url)
{
if (url.empty() || string::npos == url.find("file://"))
return url;
size_t pos = url.rfind('/');
if (pos == string::npos || !pos)
return "ERROR:" + url;
pos = url.rfind('/', pos - 1);
if (pos == string::npos)
return "ERROR:" + url;
return url.substr(pos + 1);
}
// Adds a file called "DRTFakeFile" to |data_object| (CF_HDROP). Use to fake
// dragging a file.
static void addDRTFakeFileToDataObject(WebDragData* dragData)
{
dragData->appendToFilenames(WebString::fromUTF8("DRTFakeFile"));
}
// Get a debugging string from a WebNavigationType.
static const char* webNavigationTypeToString(WebNavigationType type)
{
switch (type) {
case WebKit::WebNavigationTypeLinkClicked:
return linkClickedString;
case WebKit::WebNavigationTypeFormSubmitted:
return formSubmittedString;
case WebKit::WebNavigationTypeBackForward:
return backForwardString;
case WebKit::WebNavigationTypeReload:
return reloadString;
case WebKit::WebNavigationTypeFormResubmitted:
return formResubmittedString;
case WebKit::WebNavigationTypeOther:
return otherString;
}
return illegalString;
}
static string URLDescription(const GURL& url)
{
if (url.SchemeIs("file"))
return url.ExtractFileName();
return url.possibly_invalid_spec();
}
static void printResponseDescription(const WebURLResponse& response)
{
if (response.isNull()) {
fputs("(null)", stdout);
return;
}
string url = response.url().spec();
printf("<NSURLResponse %s, http status code %d>",
descriptionSuitableForTestResult(url).c_str(),
response.httpStatusCode());
}
static void printNodeDescription(const WebNode& node, int exception)
{
if (exception) {
fputs("ERROR", stdout);
return;
}
if (node.isNull()) {
fputs("(null)", stdout);
return;
}
fputs(node.nodeName().utf8().data(), stdout);
const WebNode& parent = node.parentNode();
if (!parent.isNull()) {
fputs(" > ", stdout);
printNodeDescription(parent, 0);
}
}
static void printRangeDescription(const WebRange& range)
{
if (range.isNull()) {
fputs("(null)", stdout);
return;
}
printf("range from %d of ", range.startOffset());
int exception = 0;
WebNode startNode = range.startContainer(exception);
printNodeDescription(startNode, exception);
printf(" to %d of ", range.endOffset());
WebNode endNode = range.endContainer(exception);
printNodeDescription(endNode, exception);
}
static string editingActionDescription(WebEditingAction action)
{
switch (action) {
case WebKit::WebEditingActionTyped:
return "WebViewInsertActionTyped";
case WebKit::WebEditingActionPasted:
return "WebViewInsertActionPasted";
case WebKit::WebEditingActionDropped:
return "WebViewInsertActionDropped";
}
return "(UNKNOWN ACTION)";
}
static string textAffinityDescription(WebTextAffinity affinity)
{
switch (affinity) {
case WebKit::WebTextAffinityUpstream:
return "NSSelectionAffinityUpstream";
case WebKit::WebTextAffinityDownstream:
return "NSSelectionAffinityDownstream";
}
return "(UNKNOWN AFFINITY)";
}
// WebViewClient -------------------------------------------------------------
WebView* WebViewHost::createView(WebFrame*, const WebURLRequest&, const WebWindowFeatures&, const WebString&)
{
if (!layoutTestController()->canOpenWindows())
return 0;
return m_shell->createNewWindow(WebURL())->webView();
}
WebWidget* WebViewHost::createPopupMenu(WebPopupType)
{
return 0;
}
WebWidget* WebViewHost::createPopupMenu(const WebPopupMenuInfo&)
{
return 0;
}
WebStorageNamespace* WebViewHost::createSessionStorageNamespace(unsigned quota)
{
return WebKit::WebStorageNamespace::createSessionStorageNamespace(quota);
}
void WebViewHost::didAddMessageToConsole(const WebConsoleMessage& message, const WebString& sourceName, unsigned sourceLine)
{
// This matches win DumpRenderTree's UIDelegate.cpp.
string newMessage;
if (!message.text.isEmpty()) {
newMessage = message.text.utf8();
size_t fileProtocol = newMessage.find("file://");
if (fileProtocol != string::npos) {
newMessage = newMessage.substr(0, fileProtocol)
+ urlSuitableForTestResult(newMessage.substr(fileProtocol));
}
}
printf("CONSOLE MESSAGE: line %d: %s\n", sourceLine, newMessage.data());
}
void WebViewHost::didStartLoading()
{
m_shell->setIsLoading(true);
}
void WebViewHost::didStopLoading()
{
m_shell->setIsLoading(false);
}
// The output from these methods in layout test mode should match that
// expected by the layout tests. See EditingDelegate.m in DumpRenderTree.
bool WebViewHost::shouldBeginEditing(const WebRange& range)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
fputs("EDITING DELEGATE: shouldBeginEditingInDOMRange:", stdout);
printRangeDescription(range);
fputs("\n", stdout);
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldEndEditing(const WebRange& range)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
fputs("EDITING DELEGATE: shouldEndEditingInDOMRange:", stdout);
printRangeDescription(range);
fputs("\n", stdout);
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldInsertNode(const WebNode& node, const WebRange& range, WebEditingAction action)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
fputs("EDITING DELEGATE: shouldInsertNode:", stdout);
printNodeDescription(node, 0);
fputs(" replacingDOMRange:", stdout);
printRangeDescription(range);
printf(" givenAction:%s\n", editingActionDescription(action).c_str());
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldInsertText(const WebString& text, const WebRange& range, WebEditingAction action)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:", text.utf8().data());
printRangeDescription(range);
printf(" givenAction:%s\n", editingActionDescription(action).c_str());
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldChangeSelectedRange(
const WebRange& fromRange, const WebRange& toRange, WebTextAffinity affinity, bool stillSelecting)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
fputs("EDITING DELEGATE: shouldChangeSelectedDOMRange:", stdout);
printRangeDescription(fromRange);
fputs(" toDOMRange:", stdout);
printRangeDescription(toRange);
printf(" affinity:%s stillSelecting:%s\n",
textAffinityDescription(affinity).c_str(),
(stillSelecting ? "TRUE" : "FALSE"));
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldDeleteRange(const WebRange& range)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
fputs("EDITING DELEGATE: shouldDeleteDOMRange:", stdout);
printRangeDescription(range);
fputs("\n", stdout);
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::shouldApplyStyle(const WebString& style, const WebRange& range)
{
if (layoutTestController()->shouldDumpEditingCallbacks()) {
printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:", style.utf8().data());
printRangeDescription(range);
fputs("\n", stdout);
}
return layoutTestController()->acceptsEditing();
}
bool WebViewHost::isSmartInsertDeleteEnabled()
{
return m_smartInsertDeleteEnabled;
}
bool WebViewHost::isSelectTrailingWhitespaceEnabled()
{
return m_selectTrailingWhitespaceEnabled;
}
void WebViewHost::didBeginEditing()
{
if (!layoutTestController()->shouldDumpEditingCallbacks())
return;
fputs("EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification\n", stdout);
}
void WebViewHost::didChangeSelection(bool isEmptySelection)
{
if (layoutTestController()->shouldDumpEditingCallbacks())
fputs("EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification\n", stdout);
// No need to update clipboard with the selected text in DRT.
}
void WebViewHost::didChangeContents()
{
if (!layoutTestController()->shouldDumpEditingCallbacks())
return;
fputs("EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification\n", stdout);
}
void WebViewHost::didEndEditing()
{
if (!layoutTestController()->shouldDumpEditingCallbacks())
return;
fputs("EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification\n", stdout);
}
bool WebViewHost::handleCurrentKeyboardEvent()
{
if (m_editCommandName.empty())
return false;
WebFrame* frame = webView()->focusedFrame();
if (!frame)
return false;
return frame->executeCommand(WebString::fromUTF8(m_editCommandName), WebString::fromUTF8(m_editCommandValue));
}
void WebViewHost::spellCheck(const WebString& text, int& misspelledOffset, int& misspelledLength, WebVector<WebString>* optionalSuggestions)
{
// Check the spelling of the given text.
m_spellcheck.spellCheckWord(text, &misspelledOffset, &misspelledLength);
}
void WebViewHost::requestCheckingOfText(const WebString& text, WebTextCheckingCompletion* completion)
{
m_lastRequestedTextCheckingCompletion = completion;
m_lastRequestedTextCheckString = text;
postDelayedTask(new HostMethodTask(this, &WebViewHost::finishLastTextCheck), 0);
}
void WebViewHost::finishLastTextCheck()
{
Vector<WebTextCheckingResult> results;
// FIXME: Do the grammar check.
int offset = 0;
String text(m_lastRequestedTextCheckString.data(), m_lastRequestedTextCheckString.length());
while (text.length()) {
int misspelledPosition = 0;
int misspelledLength = 0;
m_spellcheck.spellCheckWord(WebString(text.characters(), text.length()), &misspelledPosition, &misspelledLength);
if (!misspelledLength)
break;
results.append(WebTextCheckingResult(WebTextCheckingResult::ErrorSpelling, offset + misspelledPosition, misspelledLength));
text = text.substring(misspelledPosition + misspelledLength);
offset += misspelledPosition;
}
m_lastRequestedTextCheckingCompletion->didFinishCheckingText(results);
m_lastRequestedTextCheckingCompletion = 0;
}
WebString WebViewHost::autoCorrectWord(const WebString&)
{
// Returns an empty string as Mac WebKit ('WebKitSupport/WebEditorClient.mm')
// does. (If this function returns a non-empty string, WebKit replaces the
// given misspelled string with the result one. This process executes some
// editor commands and causes layout-test failures.)
return WebString();
}
void WebViewHost::runModalAlertDialog(WebFrame*, const WebString& message)
{
printf("ALERT: %s\n", message.utf8().data());
}
bool WebViewHost::runModalConfirmDialog(WebFrame*, const WebString& message)
{
printf("CONFIRM: %s\n", message.utf8().data());
return true;
}
bool WebViewHost::runModalPromptDialog(WebFrame* frame, const WebString& message,
const WebString& defaultValue, WebString*)
{
printf("PROMPT: %s, default text: %s\n", message.utf8().data(), defaultValue.utf8().data());
return true;
}
bool WebViewHost::runModalBeforeUnloadDialog(WebFrame*, const WebString&)
{
return true; // Allow window closure.
}
void WebViewHost::showContextMenu(WebFrame*, const WebContextMenuData& contextMenuData)
{
m_lastContextMenuData = adoptPtr(new WebContextMenuData(contextMenuData));
}
void WebViewHost::clearContextMenuData()
{
m_lastContextMenuData.clear();
}
WebContextMenuData* WebViewHost::lastContextMenuData() const
{
return m_lastContextMenuData.get();
}
void WebViewHost::setStatusText(const WebString& text)
{
if (!layoutTestController()->shouldDumpStatusCallbacks())
return;
// When running tests, write to stdout.
printf("UI DELEGATE STATUS CALLBACK: setStatusText:%s\n", text.utf8().data());
}
void WebViewHost::startDragging(const WebDragData& data, WebDragOperationsMask mask, const WebImage&, const WebPoint&)
{
WebDragData mutableDragData = data;
if (layoutTestController()->shouldAddFileToPasteboard()) {
// Add a file called DRTFakeFile to the drag&drop clipboard.
addDRTFakeFileToDataObject(&mutableDragData);
}
// When running a test, we need to fake a drag drop operation otherwise
// Windows waits for real mouse events to know when the drag is over.
m_shell->eventSender()->doDragDrop(mutableDragData, mask);
}
void WebViewHost::navigateBackForwardSoon(int offset)
{
navigationController()->goToOffset(offset);
}
int WebViewHost::historyBackListCount()
{
return navigationController()->lastCommittedEntryIndex();
}
int WebViewHost::historyForwardListCount()
{
int currentIndex =navigationController()->lastCommittedEntryIndex();
return navigationController()->entryCount() - currentIndex - 1;
}
void WebViewHost::postAccessibilityNotification(const WebAccessibilityObject& obj, WebAccessibilityNotification notification)
{
if (notification == WebAccessibilityNotificationFocusedUIElementChanged)
m_shell->accessibilityController()->setFocusedElement(obj);
if (m_shell->accessibilityController()->shouldDumpAccessibilityNotifications()) {
printf("AccessibilityNotification - ");
switch (notification) {
case WebAccessibilityNotificationActiveDescendantChanged:
printf("ActiveDescendantChanged");
break;
case WebAccessibilityNotificationCheckedStateChanged:
printf("CheckedStateChanged");
break;
case WebAccessibilityNotificationChildrenChanged:
printf("ChildrenChanged");
break;
case WebAccessibilityNotificationFocusedUIElementChanged:
printf("FocusedUIElementChanged");
break;
case WebAccessibilityNotificationLayoutComplete:
printf("LayoutComplete");
break;
case WebAccessibilityNotificationLoadComplete:
printf("LoadComplete");
break;
case WebAccessibilityNotificationSelectedChildrenChanged:
printf("SelectedChildrenChanged");
break;
case WebAccessibilityNotificationSelectedTextChanged:
printf("SelectedTextChanged");
break;
case WebAccessibilityNotificationValueChanged:
printf("ValueChanged");
break;
case WebAccessibilityNotificationScrolledToAnchor:
printf("ScrolledToAnchor");
break;
case WebAccessibilityNotificationLiveRegionChanged:
printf("LiveRegionChanged");
break;
case WebAccessibilityNotificationMenuListValueChanged:
printf("MenuListValueChanged");
break;
case WebAccessibilityNotificationRowCountChanged:
printf("RowCountChanged");
break;
case WebAccessibilityNotificationRowCollapsed:
printf("RowCollapsed");
break;
case WebAccessibilityNotificationRowExpanded:
printf("RowExpanded");
break;
default:
break;
}
WebKit::WebNode node = obj.node();
if (!node.isNull() && node.isElementNode()) {
WebKit::WebElement element = node.to<WebKit::WebElement>();
if (element.hasAttribute("id"))
printf(" - id:%s", element.getAttribute("id").utf8().data());
}
printf("\n");
}
}
WebNotificationPresenter* WebViewHost::notificationPresenter()
{
return m_shell->notificationPresenter();
}
WebKit::WebGeolocationClient* WebViewHost::geolocationClient()
{
return geolocationClientMock();
}
WebKit::WebGeolocationClientMock* WebViewHost::geolocationClientMock()
{
if (!m_geolocationClientMock)
m_geolocationClientMock.set(WebGeolocationClientMock::create());
return m_geolocationClientMock.get();
}
WebSpeechInputController* WebViewHost::speechInputController(WebKit::WebSpeechInputListener* listener)
{
if (!m_speechInputControllerMock)
m_speechInputControllerMock.set(WebSpeechInputControllerMock::create(listener));
return m_speechInputControllerMock.get();
}
WebDeviceOrientationClientMock* WebViewHost::deviceOrientationClientMock()
{
if (!m_deviceOrientationClientMock.get())
m_deviceOrientationClientMock.set(WebDeviceOrientationClientMock::create());
return m_deviceOrientationClientMock.get();
}
MockSpellCheck* WebViewHost::mockSpellCheck()
{
return &m_spellcheck;
}
WebDeviceOrientationClient* WebViewHost::deviceOrientationClient()
{
return deviceOrientationClientMock();
}
// WebWidgetClient -----------------------------------------------------------
void WebViewHost::didInvalidateRect(const WebRect& rect)
{
updatePaintRect(rect);
}
void WebViewHost::didScrollRect(int, int, const WebRect& clipRect)
{
// This is used for optimizing painting when the renderer is scrolled. We're
// currently not doing any optimizations so just invalidate the region.
didInvalidateRect(clipRect);
}
void WebViewHost::scheduleComposite()
{
WebSize widgetSize = webWidget()->size();
WebRect clientRect(0, 0, widgetSize.width, widgetSize.height);
didInvalidateRect(clientRect);
}
#if ENABLE(REQUEST_ANIMATION_FRAME)
void WebViewHost::scheduleAnimation()
{
postDelayedTask(new HostMethodTask(this, &WebViewHost::scheduleComposite), 0);
}
#endif
void WebViewHost::didFocus()
{
m_shell->setFocus(webWidget(), true);
}
void WebViewHost::didBlur()
{
m_shell->setFocus(webWidget(), false);
}
WebScreenInfo WebViewHost::screenInfo()
{
// We don't need to set actual values.
WebScreenInfo info;
info.depth = 24;
info.depthPerComponent = 8;
info.isMonochrome = false;
info.rect = WebRect(0, 0, screenWidth, screenHeight);
// Use values different from info.rect for testing.
info.availableRect = WebRect(screenUnavailableBorder, screenUnavailableBorder,
screenWidth - screenUnavailableBorder * 2,
screenHeight - screenUnavailableBorder * 2);
return info;
}
void WebViewHost::show(WebNavigationPolicy)
{
m_hasWindow = true;
WebSize size = webWidget()->size();
updatePaintRect(WebRect(0, 0, size.width, size.height));
}
void WebViewHost::closeWidget()
{
m_hasWindow = false;
m_shell->closeWindow(this);
// No more code here, we should be deleted at this point.
}
void WebViewHost::closeWidgetSoon()
{
postDelayedTask(new HostMethodTask(this, &WebViewHost::closeWidget), 0);
}
void WebViewHost::didChangeCursor(const WebCursorInfo& cursorInfo)
{
if (!hasWindow())
return;
m_currentCursor = cursorInfo;
}
WebRect WebViewHost::windowRect()
{
return m_windowRect;
}
void WebViewHost::setWindowRect(const WebRect& rect)
{
m_windowRect = rect;
const int border2 = TestShell::virtualWindowBorder * 2;
if (m_windowRect.width <= border2)
m_windowRect.width = 1 + border2;
if (m_windowRect.height <= border2)
m_windowRect.height = 1 + border2;
int width = m_windowRect.width - border2;
int height = m_windowRect.height - border2;
discardBackingStore();
webWidget()->resize(WebSize(width, height));
updatePaintRect(WebRect(0, 0, width, height));
}
WebRect WebViewHost::rootWindowRect()
{
return windowRect();
}
WebRect WebViewHost::windowResizerRect()
{
// Not necessary.
return WebRect();
}
void WebViewHost::runModal()
{
bool oldState = webkit_support::MessageLoopNestableTasksAllowed();
webkit_support::MessageLoopSetNestableTasksAllowed(true);
m_inModalLoop = true;
webkit_support::RunMessageLoop();
webkit_support::MessageLoopSetNestableTasksAllowed(oldState);
}
// WebFrameClient ------------------------------------------------------------
WebPlugin* WebViewHost::createPlugin(WebFrame* frame, const WebPluginParams& params)
{
return webkit_support::CreateWebPlugin(frame, params);
}
WebWorker* WebViewHost::createWorker(WebFrame*, WebWorkerClient*)
{
return new TestWebWorker();
}
WebMediaPlayer* WebViewHost::createMediaPlayer(WebFrame* frame, WebMediaPlayerClient* client)
{
return webkit_support::CreateMediaPlayer(frame, client);
}
WebApplicationCacheHost* WebViewHost::createApplicationCacheHost(WebFrame* frame, WebApplicationCacheHostClient* client)
{
return webkit_support::CreateApplicationCacheHost(frame, client);
}
bool WebViewHost::allowPlugins(WebFrame* frame, bool enabledPerSettings)
{
return enabledPerSettings;
}
bool WebViewHost::allowImages(WebFrame* frame, bool enabledPerSettings)
{
return enabledPerSettings;
}
void WebViewHost::loadURLExternally(WebFrame*, const WebURLRequest& request, WebNavigationPolicy policy)
{
ASSERT(policy != WebKit::WebNavigationPolicyCurrentTab);
WebViewHost* another = m_shell->createNewWindow(request.url());
if (another)
another->show(policy);
}
WebNavigationPolicy WebViewHost::decidePolicyForNavigation(
WebFrame*, const WebURLRequest& request,
WebNavigationType type, const WebNode& originatingNode,
WebNavigationPolicy defaultPolicy, bool isRedirect)
{
WebNavigationPolicy result;
if (!m_policyDelegateEnabled)
return defaultPolicy;
printf("Policy delegate: attempt to load %s with navigation type '%s'",
URLDescription(request.url()).c_str(), webNavigationTypeToString(type));
if (!originatingNode.isNull()) {
fputs(" originating from ", stdout);
printNodeDescription(originatingNode, 0);
}
fputs("\n", stdout);
if (m_policyDelegateIsPermissive)
result = WebKit::WebNavigationPolicyCurrentTab;
else
result = WebKit::WebNavigationPolicyIgnore;
if (m_policyDelegateShouldNotifyDone)
layoutTestController()->policyDelegateDone();
return result;
}
bool WebViewHost::canHandleRequest(WebFrame*, const WebURLRequest& request)
{
GURL url = request.url();
// Just reject the scheme used in
// LayoutTests/http/tests/misc/redirect-to-external-url.html
return !url.SchemeIs("spaceballs");
}
WebURLError WebViewHost::cannotHandleRequestError(WebFrame*, const WebURLRequest& request)
{
WebURLError error;
// A WebKit layout test expects the following values.
// unableToImplementPolicyWithError() below prints them.
error.domain = WebString::fromUTF8("WebKitErrorDomain");
error.reason = 101;
error.unreachableURL = request.url();
return error;
}
WebURLError WebViewHost::cancelledError(WebFrame*, const WebURLRequest& request)
{
return webkit_support::CreateCancelledError(request);
}
void WebViewHost::unableToImplementPolicyWithError(WebFrame* frame, const WebURLError& error)
{
printf("Policy delegate: unable to implement policy with error domain '%s', "
"error code %d, in frame '%s'\n",
error.domain.utf8().data(), error.reason, frame->name().utf8().data());
}
void WebViewHost::willPerformClientRedirect(WebFrame* frame, const WebURL& from, const WebURL& to,
double interval, double fire_time)
{
if (!m_shell->shouldDumpFrameLoadCallbacks())
return;
printFrameDescription(frame);
printf(" - willPerformClientRedirectToURL: %s \n", to.spec().data());
}
void WebViewHost::didCancelClientRedirect(WebFrame* frame)
{
if (!m_shell->shouldDumpFrameLoadCallbacks())
return;
printFrameDescription(frame);
fputs(" - didCancelClientRedirectForFrame\n", stdout);
}
void WebViewHost::didCreateDataSource(WebFrame*, WebDataSource* ds)
{
ds->setExtraData(m_pendingExtraData.leakPtr());
if (!layoutTestController()->deferMainResourceDataLoad())
ds->setDeferMainResourceDataLoad(false);
}
void WebViewHost::didStartProvisionalLoad(WebFrame* frame)
{
if (m_shell->shouldDumpUserGestureInFrameLoadCallbacks())
printFrameUserGestureStatus(frame, " - in didStartProvisionalLoadForFrame\n");
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didStartProvisionalLoadForFrame\n", stdout);
}
if (!m_topLoadingFrame)
m_topLoadingFrame = frame;
if (layoutTestController()->stopProvisionalFrameLoads()) {
printFrameDescription(frame);
fputs(" - stopping load in didStartProvisionalLoadForFrame callback\n", stdout);
frame->stopLoading();
}
updateAddressBar(frame->view());
}
void WebViewHost::didReceiveServerRedirectForProvisionalLoad(WebFrame* frame)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didReceiveServerRedirectForProvisionalLoadForFrame\n", stdout);
}
updateAddressBar(frame->view());
}
void WebViewHost::didFailProvisionalLoad(WebFrame* frame, const WebURLError& error)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didFailProvisionalLoadWithError\n", stdout);
}
locationChangeDone(frame);
// Don't display an error page if we're running layout tests, because
// DumpRenderTree doesn't.
}
void WebViewHost::didCommitProvisionalLoad(WebFrame* frame, bool isNewNavigation)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didCommitLoadForFrame\n", stdout);
}
updateForCommittedLoad(frame, isNewNavigation);
}
void WebViewHost::didClearWindowObject(WebFrame* frame)
{
m_shell->bindJSObjectsToWindow(frame);
}
void WebViewHost::didReceiveTitle(WebFrame* frame, const WebString& title, WebTextDirection direction)
{
WebCString title8 = title.utf8();
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
printf(" - didReceiveTitle: %s\n", title8.data());
}
if (layoutTestController()->shouldDumpTitleChanges())
printf("TITLE CHANGED: %s\n", title8.data());
setPageTitle(title);
layoutTestController()->setTitleTextDirection(direction);
}
void WebViewHost::didFinishDocumentLoad(WebFrame* frame)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didFinishDocumentLoadForFrame\n", stdout);
} else {
unsigned pendingUnloadEvents = frame->unloadListenerCount();
if (pendingUnloadEvents) {
printFrameDescription(frame);
printf(" - has %u onunload handler(s)\n", pendingUnloadEvents);
}
}
}
void WebViewHost::didHandleOnloadEvents(WebFrame* frame)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didHandleOnloadEventsForFrame\n", stdout);
}
}
void WebViewHost::didFailLoad(WebFrame* frame, const WebURLError& error)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didFailLoadWithError\n", stdout);
}
locationChangeDone(frame);
}
void WebViewHost::didFinishLoad(WebFrame* frame)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didFinishLoadForFrame\n", stdout);
}
updateAddressBar(frame->view());
locationChangeDone(frame);
}
void WebViewHost::didNavigateWithinPage(WebFrame* frame, bool isNewNavigation)
{
frame->dataSource()->setExtraData(m_pendingExtraData.leakPtr());
updateForCommittedLoad(frame, isNewNavigation);
}
void WebViewHost::didChangeLocationWithinPage(WebFrame* frame)
{
if (m_shell->shouldDumpFrameLoadCallbacks()) {
printFrameDescription(frame);
fputs(" - didChangeLocationWithinPageForFrame\n", stdout);
}
}
void WebViewHost::assignIdentifierToRequest(WebFrame*, unsigned identifier, const WebURLRequest& request)
{
if (!m_shell->shouldDumpResourceLoadCallbacks())
return;
ASSERT(!m_resourceIdentifierMap.contains(identifier));
m_resourceIdentifierMap.set(identifier, descriptionSuitableForTestResult(request.url().spec()));
}
void WebViewHost::removeIdentifierForRequest(unsigned identifier)
{
m_resourceIdentifierMap.remove(identifier);
}
void WebViewHost::willSendRequest(WebFrame*, unsigned identifier, WebURLRequest& request, const WebURLResponse& redirectResponse)
{
// Need to use GURL for host() and SchemeIs()
GURL url = request.url();
string requestURL = url.possibly_invalid_spec();
if (layoutTestController()->shouldDumpResourceLoadCallbacks()) {
GURL mainDocumentURL = request.firstPartyForCookies();
printResourceDescription(identifier);
printf(" - willSendRequest <NSURLRequest URL %s, main document URL %s,"
" http method %s> redirectResponse ",
descriptionSuitableForTestResult(requestURL).c_str(),
URLDescription(mainDocumentURL).c_str(),
request.httpMethod().utf8().data());
printResponseDescription(redirectResponse);
fputs("\n", stdout);
}
if (!redirectResponse.isNull() && m_blocksRedirects) {
fputs("Returning null for this redirect\n", stdout);
// To block the request, we set its URL to an empty one.
request.setURL(WebURL());
return;
}
if (m_requestReturnNull) {
// To block the request, we set its URL to an empty one.
request.setURL(WebURL());
return;
}
string host = url.host();
// 255.255.255.255 is used in some tests that expect to get back an error.
if (!host.empty() && (url.SchemeIs("http") || url.SchemeIs("https"))
&& host != "127.0.0.1"
&& host != "255.255.255.255"
&& host != "localhost"
&& !m_shell->allowExternalPages()) {
printf("Blocked access to external URL %s\n", requestURL.c_str());
// To block the request, we set its URL to an empty one.
request.setURL(WebURL());
return;
}
HashSet<String>::const_iterator end = m_clearHeaders.end();
for (HashSet<String>::const_iterator header = m_clearHeaders.begin(); header != end; ++header)
request.clearHTTPHeaderField(WebString(header->characters(), header->length()));
// Set the new substituted URL.
request.setURL(webkit_support::RewriteLayoutTestsURL(request.url().spec()));
}
void WebViewHost::didReceiveResponse(WebFrame*, unsigned identifier, const WebURLResponse& response)
{
if (m_shell->shouldDumpResourceLoadCallbacks()) {
printResourceDescription(identifier);
fputs(" - didReceiveResponse ", stdout);
printResponseDescription(response);
fputs("\n", stdout);
}
if (m_shell->shouldDumpResourceResponseMIMETypes()) {
GURL url = response.url();
WebString mimeType = response.mimeType();
printf("%s has MIME type %s\n",
url.ExtractFileName().c_str(),
// Simulate NSURLResponse's mapping of empty/unknown MIME types to application/octet-stream
mimeType.isEmpty() ? "application/octet-stream" : mimeType.utf8().data());
}
}
void WebViewHost::didFinishResourceLoad(WebFrame*, unsigned identifier)
{
if (m_shell->shouldDumpResourceLoadCallbacks()) {
printResourceDescription(identifier);
fputs(" - didFinishLoading\n", stdout);
}
removeIdentifierForRequest(identifier);
}
void WebViewHost::didFailResourceLoad(WebFrame*, unsigned identifier, const WebURLError& error)
{
if (m_shell->shouldDumpResourceLoadCallbacks()) {
printResourceDescription(identifier);
fputs(" - didFailLoadingWithError: ", stdout);
fputs(webkit_support::MakeURLErrorDescription(error).c_str(), stdout);
fputs("\n", stdout);
}
removeIdentifierForRequest(identifier);
}
void WebViewHost::didDisplayInsecureContent(WebFrame*)
{
if (m_shell->shouldDumpFrameLoadCallbacks())
fputs("didDisplayInsecureContent\n", stdout);
}
void WebViewHost::didRunInsecureContent(WebFrame*, const WebSecurityOrigin& origin, const WebURL& insecureURL)
{
if (m_shell->shouldDumpFrameLoadCallbacks())
fputs("didRunInsecureContent\n", stdout);
}
bool WebViewHost::allowScript(WebFrame*, bool enabledPerSettings)
{
return enabledPerSettings;
}
void WebViewHost::openFileSystem(WebFrame* frame, WebFileSystem::Type type, long long size, bool create, WebFileSystemCallbacks* callbacks)
{
webkit_support::OpenFileSystem(frame, type, size, create, callbacks);
}
// Public functions -----------------------------------------------------------
WebViewHost::WebViewHost(TestShell* shell)
: m_shell(shell)
, m_webWidget(0)
, m_lastRequestedTextCheckingCompletion(0)
{
reset();
}
WebViewHost::~WebViewHost()
{
// DevTools frontend page is supposed to be navigated only once and
// loading another URL in that Page is an error.
if (m_shell->devToolsWebView() != this) {
// Navigate to an empty page to fire all the destruction logic for the
// current page.
loadURLForFrame(GURL("about:blank"), WebString());
}
webWidget()->close();
if (m_inModalLoop)
webkit_support::QuitMessageLoop();
}
void WebViewHost::setWebWidget(WebKit::WebWidget* widget)
{
m_webWidget = widget;
webView()->setSpellCheckClient(this);
}
WebView* WebViewHost::webView() const
{
ASSERT(m_webWidget);
// DRT does not support popup widgets. So m_webWidget is always a WebView.
return static_cast<WebView*>(m_webWidget);
}
WebWidget* WebViewHost::webWidget() const
{
ASSERT(m_webWidget);
return m_webWidget;
}
void WebViewHost::reset()
{
m_policyDelegateEnabled = false;
m_policyDelegateIsPermissive = false;
m_policyDelegateShouldNotifyDone = false;
m_topLoadingFrame = 0;
m_pageId = -1;
m_lastPageIdUpdated = -1;
m_hasWindow = false;
m_inModalLoop = false;
m_smartInsertDeleteEnabled = true;
#if OS(WINDOWS)
m_selectTrailingWhitespaceEnabled = true;
#else
m_selectTrailingWhitespaceEnabled = false;
#endif
m_blocksRedirects = false;
m_requestReturnNull = false;
m_isPainting = false;
m_canvas.clear();
m_navigationController.set(new TestNavigationController(this));
m_pendingExtraData.clear();
m_resourceIdentifierMap.clear();
m_clearHeaders.clear();
m_editCommandName.clear();
m_editCommandValue.clear();
if (m_geolocationClientMock.get())
m_geolocationClientMock->resetMock();
if (m_speechInputControllerMock.get())
m_speechInputControllerMock->clearResults();
m_currentCursor = WebCursorInfo();
m_windowRect = WebRect();
m_paintRect = WebRect();
if (m_webWidget) {
webView()->mainFrame()->setName(WebString());
webView()->settings()->setMinimumTimerInterval(webkit_support::GetForegroundTabTimerInterval());
}
}
void WebViewHost::setSelectTrailingWhitespaceEnabled(bool enabled)
{
m_selectTrailingWhitespaceEnabled = enabled;
// In upstream WebKit, smart insert/delete is mutually exclusive with select
// trailing whitespace, however, we allow both because Chromium on Windows
// allows both.
}
void WebViewHost::setSmartInsertDeleteEnabled(bool enabled)
{
m_smartInsertDeleteEnabled = enabled;
// In upstream WebKit, smart insert/delete is mutually exclusive with select
// trailing whitespace, however, we allow both because Chromium on Windows
// allows both.
}
void WebViewHost::setCustomPolicyDelegate(bool isCustom, bool isPermissive)
{
m_policyDelegateEnabled = isCustom;
m_policyDelegateIsPermissive = isPermissive;
}
void WebViewHost::waitForPolicyDelegate()
{
m_policyDelegateEnabled = true;
m_policyDelegateShouldNotifyDone = true;
}
void WebViewHost::setEditCommand(const string& name, const string& value)
{
m_editCommandName = name;
m_editCommandValue = value;
}
void WebViewHost::clearEditCommand()
{
m_editCommandName.clear();
m_editCommandValue.clear();
}
void WebViewHost::loadURLForFrame(const WebURL& url, const WebString& frameName)
{
if (!url.isValid())
return;
TestShell::resizeWindowForTest(this, url);
navigationController()->loadEntry(TestNavigationEntry::create(-1, url, WebString(), frameName).get());
}
bool WebViewHost::navigate(const TestNavigationEntry& entry, bool reload)
{
// Get the right target frame for the entry.
WebFrame* frame = webView()->mainFrame();
if (!entry.targetFrame().isEmpty())
frame = webView()->findFrameByName(entry.targetFrame());
// TODO(mpcomplete): should we clear the target frame, or should
// back/forward navigations maintain the target frame?
// A navigation resulting from loading a javascript URL should not be
// treated as a browser initiated event. Instead, we want it to look as if
// the page initiated any load resulting from JS execution.
if (!GURL(entry.URL()).SchemeIs("javascript"))
setPendingExtraData(new TestShellExtraData(entry.pageID()));
// If we are reloading, then WebKit will use the state of the current page.
// Otherwise, we give it the state to navigate to.
if (reload) {
frame->reload(false);
} else if (!entry.contentState().isNull()) {
ASSERT(entry.pageID() != -1);
frame->loadHistoryItem(entry.contentState());
} else {
ASSERT(entry.pageID() == -1);
frame->loadRequest(WebURLRequest(entry.URL()));
}
// In case LoadRequest failed before DidCreateDataSource was called.
setPendingExtraData(0);
// Restore focus to the main frame prior to loading new request.
// This makes sure that we don't have a focused iframe. Otherwise, that
// iframe would keep focus when the SetFocus called immediately after
// LoadRequest, thus making some tests fail (see http://b/issue?id=845337
// for more details).
webView()->setFocusedFrame(frame);
m_shell->setFocus(webView(), true);
return true;
}
// Private functions ----------------------------------------------------------
LayoutTestController* WebViewHost::layoutTestController() const
{
return m_shell->layoutTestController();
}
void WebViewHost::updateAddressBar(WebView* webView)
{
WebFrame* mainFrame = webView->mainFrame();
WebDataSource* dataSource = mainFrame->dataSource();
if (!dataSource)
dataSource = mainFrame->provisionalDataSource();
if (!dataSource)
return;
setAddressBarURL(dataSource->request().url());
}
void WebViewHost::locationChangeDone(WebFrame* frame)
{
if (frame != m_topLoadingFrame)
return;
m_topLoadingFrame = 0;
layoutTestController()->locationChangeDone();
}
void WebViewHost::updateForCommittedLoad(WebFrame* frame, bool isNewNavigation)
{
// Code duplicated from RenderView::DidCommitLoadForFrame.
TestShellExtraData* extraData = static_cast<TestShellExtraData*>(frame->dataSource()->extraData());
if (isNewNavigation) {
// New navigation.
updateSessionHistory(frame);
m_pageId = nextPageID++;
} else if (extraData && extraData->pendingPageID != -1 && !extraData->requestCommitted) {
// This is a successful session history navigation!
updateSessionHistory(frame);
m_pageId = extraData->pendingPageID;
}
// Don't update session history multiple times.
if (extraData)
extraData->requestCommitted = true;
updateURL(frame);
}
void WebViewHost::updateURL(WebFrame* frame)
{
WebDataSource* ds = frame->dataSource();
ASSERT(ds);
const WebURLRequest& request = ds->request();
RefPtr<TestNavigationEntry> entry(TestNavigationEntry::create());
// The referrer will be empty on https->http transitions. It
// would be nice if we could get the real referrer from somewhere.
entry->setPageID(m_pageId);
if (ds->hasUnreachableURL())
entry->setURL(ds->unreachableURL());
else
entry->setURL(request.url());
const WebHistoryItem& historyItem = frame->currentHistoryItem();
if (!historyItem.isNull())
entry->setContentState(historyItem);
navigationController()->didNavigateToEntry(entry.get());
updateAddressBar(frame->view());
m_lastPageIdUpdated = max(m_lastPageIdUpdated, m_pageId);
}
void WebViewHost::updateSessionHistory(WebFrame* frame)
{
// If we have a valid page ID at this point, then it corresponds to the page
// we are navigating away from. Otherwise, this is the first navigation, so
// there is no past session history to record.
if (m_pageId == -1)
return;
TestNavigationEntry* entry = navigationController()->entryWithPageID(m_pageId);
if (!entry)
return;
const WebHistoryItem& historyItem = webView()->mainFrame()->previousHistoryItem();
if (historyItem.isNull())
return;
entry->setContentState(historyItem);
}
void WebViewHost::printFrameDescription(WebFrame* webframe)
{
string name8 = webframe->name().utf8();
if (webframe == webView()->mainFrame()) {
if (!name8.length()) {
fputs("main frame", stdout);
return;
}
printf("main frame \"%s\"", name8.c_str());
return;
}
if (!name8.length()) {
fputs("frame (anonymous)", stdout);
return;
}
printf("frame \"%s\"", name8.c_str());
}
void WebViewHost::printFrameUserGestureStatus(WebFrame* webframe, const char* msg)
{
bool isUserGesture = webframe->isProcessingUserGesture();
printf("Frame with user gesture \"%s\"%s", isUserGesture ? "true" : "false", msg);
}
void WebViewHost::printResourceDescription(unsigned identifier)
{
ResourceMap::iterator it = m_resourceIdentifierMap.find(identifier);
printf("%s", it != m_resourceIdentifierMap.end() ? it->second.c_str() : "<unknown>");
}
void WebViewHost::setPendingExtraData(TestShellExtraData* extraData)
{
m_pendingExtraData.set(extraData);
}
void WebViewHost::setPageTitle(const WebString&)
{
// Nothing to do in layout test.
}
void WebViewHost::setAddressBarURL(const WebURL&)
{
// Nothing to do in layout test.
}
// Painting functions ---------------------------------------------------------
void WebViewHost::updatePaintRect(const WebRect& rect)
{
// m_paintRect = m_paintRect U rect
if (rect.isEmpty())
return;
if (m_paintRect.isEmpty()) {
m_paintRect = rect;
return;
}
int left = min(m_paintRect.x, rect.x);
int top = min(m_paintRect.y, rect.y);
int right = max(m_paintRect.x + m_paintRect.width, rect.x + rect.width);
int bottom = max(m_paintRect.y + m_paintRect.height, rect.y + rect.height);
m_paintRect = WebRect(left, top, right - left, bottom - top);
}
void WebViewHost::paintRect(const WebRect& rect)
{
ASSERT(!m_isPainting);
ASSERT(canvas());
m_isPainting = true;
#if USE(CG)
webWidget()->paint(skia::BeginPlatformPaint(canvas()), rect);
skia::EndPlatformPaint(canvas());
#else
webWidget()->paint(canvas(), rect);
#endif
m_isPainting = false;
}
void WebViewHost::paintInvalidatedRegion()
{
#if ENABLE(REQUEST_ANIMATION_FRAME)
webWidget()->animate();
#endif
webWidget()->layout();
WebSize widgetSize = webWidget()->size();
WebRect clientRect(0, 0, widgetSize.width, widgetSize.height);
// Paint the canvas if necessary. Allow painting to generate extra rects
// for the first two calls. This is necessary because some WebCore rendering
// objects update their layout only when painted.
// Store the total area painted in total_paint. Then tell the gdk window
// to update that area after we're done painting it.
for (int i = 0; i < 3; ++i) {
// m_paintRect = intersect(m_paintRect , clientRect)
int left = max(m_paintRect.x, clientRect.x);
int top = max(m_paintRect.y, clientRect.y);
int right = min(m_paintRect.x + m_paintRect.width, clientRect.x + clientRect.width);
int bottom = min(m_paintRect.y + m_paintRect.height, clientRect.y + clientRect.height);
if (left >= right || top >= bottom)
m_paintRect = WebRect();
else
m_paintRect = WebRect(left, top, right - left, bottom - top);
if (m_paintRect.isEmpty())
continue;
WebRect rect(m_paintRect);
m_paintRect = WebRect();
paintRect(rect);
}
ASSERT(m_paintRect.isEmpty());
}
SkCanvas* WebViewHost::canvas()
{
if (m_canvas)
return m_canvas.get();
WebSize widgetSize = webWidget()->size();
resetScrollRect();
m_canvas.set(skia::CreateBitmapCanvas(
widgetSize.width, widgetSize.height, true));
return m_canvas.get();
}
void WebViewHost::resetScrollRect()
{
}
void WebViewHost::discardBackingStore()
{
m_canvas.clear();
}
// Paints the entire canvas a semi-transparent black (grayish). This is used
// by the layout tests in fast/repaint. The alpha value matches upstream.
void WebViewHost::displayRepaintMask()
{
canvas()->drawARGB(167, 0, 0, 0);
}