/* * 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 #include #include using namespace WebCore; using namespace WebKit; using namespace skia; 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("", 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) { // 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 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(); 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) { 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); } 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 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::const_iterator end = m_clearHeaders.end(); for (HashSet::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(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(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 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() : ""); } 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 PLATFORM(CG) webWidget()->paint(canvas()->getTopPlatformDevice().GetBitmapContext(), rect); #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()); } PlatformCanvas* WebViewHost::canvas() { if (m_canvas) return m_canvas.get(); WebSize widgetSize = webWidget()->size(); resetScrollRect(); m_canvas.set(new PlatformCanvas(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); }