blob: 6aa10f07b183d286d373d72e69c76f8474b95513 [file] [log] [blame]
/*
* Copyright (C) 2007, 2014-2015 Apple 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:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of Apple Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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 "EditingDelegate.h"
#include <comutil.h>
#include "DumpRenderTree.h"
#include "TestRunner.h"
#include <WebCore/COMPtr.h>
#include <wtf/Assertions.h>
#include <wtf/Platform.h>
#include <wtf/StringExtras.h>
#include <string>
#include <tchar.h>
using std::wstring;
EditingDelegate::EditingDelegate()
{
}
// IUnknown
HRESULT EditingDelegate::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject)
{
if (!ppvObject)
return E_POINTER;
*ppvObject = nullptr;
if (IsEqualGUID(riid, IID_IUnknown))
*ppvObject = static_cast<IWebEditingDelegate2*>(this);
else if (IsEqualGUID(riid, IID_IWebEditingDelegate))
*ppvObject = static_cast<IWebEditingDelegate*>(this);
else if (IsEqualGUID(riid, IID_IWebEditingDelegate2))
*ppvObject = static_cast<IWebEditingDelegate2*>(this);
else if (IsEqualGUID(riid, IID_IWebNotificationObserver))
*ppvObject = static_cast<IWebNotificationObserver*>(this);
else
return E_NOINTERFACE;
AddRef();
return S_OK;
}
ULONG EditingDelegate::AddRef()
{
return ++m_refCount;
}
ULONG EditingDelegate::Release()
{
ULONG newRef = --m_refCount;
if (!newRef)
delete this;
return newRef;
}
static std::string dumpPath(IDOMNode* node)
{
ASSERT(node);
std::string result;
_bstr_t name;
if (FAILED(node->nodeName(&name.GetBSTR())))
return result;
result.assign(static_cast<const char*>(name), name.length());
COMPtr<IDOMNode> parent;
if (SUCCEEDED(node->parentNode(&parent)))
result += " > " + dumpPath(parent.get());
return result;
}
static std::string dump(IDOMRange* range)
{
if (!range)
return std::string();
int startOffset;
if (FAILED(range->startOffset(&startOffset)))
return std::string();
int endOffset;
if (FAILED(range->endOffset(&endOffset)))
return std::string();
COMPtr<IDOMNode> startContainer;
if (FAILED(range->startContainer(&startContainer)))
return std::string();
COMPtr<IDOMNode> endContainer;
if (FAILED(range->endContainer(&endContainer)))
return std::string();
char buffer[1024];
snprintf(buffer, ARRAYSIZE(buffer), "range from %ld of %s to %ld of %s", startOffset, dumpPath(startContainer.get()).c_str(), endOffset, dumpPath(endContainer.get()).c_str());
return buffer;
}
HRESULT EditingDelegate::shouldBeginEditingInDOMRange(_In_opt_ IWebView*, _In_opt_ IDOMRange* range, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", dump(range).c_str());
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldEndEditingInDOMRange(_In_opt_ IWebView*, _In_opt_ IDOMRange* range, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", dump(range).c_str());
*result = m_acceptsEditing;
return S_OK;
}
// IWebEditingDelegate
HRESULT EditingDelegate::shouldInsertNode(_In_opt_ IWebView* webView, _In_opt_ IDOMNode* node, _In_opt_ IDOMRange* range, WebViewInsertAction action)
{
BOOL ignore;
return shouldInsertNode(webView, node, range, action, &ignore);
}
// IWebEditingDelegate2
HRESULT EditingDelegate::shouldInsertNode(_In_opt_ IWebView* /*webView*/, _In_opt_ IDOMNode* node, _In_opt_ IDOMRange* range, WebViewInsertAction action, _Out_ BOOL* result)
{
static const char* insertActionString[] = {
"WebViewInsertActionTyped",
"WebViewInsertActionPasted",
"WebViewInsertActionDropped",
};
if (!result)
return E_POINTER;
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", dumpPath(node).c_str(), dump(range).c_str(), insertActionString[action]);
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldInsertText(_In_opt_ IWebView* /*webView*/, _In_ BSTR text, _In_opt_ IDOMRange* range, WebViewInsertAction action, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
static const char* insertactionstring[] = {
"WebViewInsertActionTyped",
"WebViewInsertActionPasted",
"WebViewInsertActionDropped",
};
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t textBstr(text);
fprintf(testResult, "EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n", static_cast<const char*>(textBstr), dump(range).c_str(), insertactionstring[action]);
}
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldDeleteDOMRange(_In_opt_ IWebView* /*webView*/, _In_opt_ IDOMRange* range, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldDeleteDOMRange:%s\n", dump(range).c_str());
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldChangeSelectedDOMRange(_In_opt_ IWebView* /*webView*/, _In_opt_ IDOMRange* currentRange, _In_opt_ IDOMRange* proposedRange,
WebSelectionAffinity selectionAffinity, BOOL stillSelecting, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
static const char* affinityString[] = {
"NSSelectionAffinityUpstream",
"NSSelectionAffinityDownstream"
};
static const char* boolstring[] = {
"FALSE",
"TRUE"
};
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n", dump(currentRange).c_str(), dump(proposedRange).c_str(), affinityString[selectionAffinity], boolstring[stillSelecting]);
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldApplyStyle(_In_opt_ IWebView* /*webView*/, _In_opt_ IDOMCSSStyleDeclaration* style, _In_opt_ IDOMRange* range, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n", "'style description'"/*[[style description] UTF8String]*/, dump(range).c_str());
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::shouldChangeTypingStyle(_In_opt_ IWebView* /*webView*/, _In_opt_ IDOMCSSStyleDeclaration* currentStyle,
_In_opt_ IDOMCSSStyleDeclaration* proposedStyle, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done)
fprintf(testResult, "EDITING DELEGATE: shouldChangeTypingStyle:%s toStyle:%s\n", "'currentStyle description'", "'proposedStyle description'");
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::doPlatformCommand(_In_opt_ IWebView* /*webView*/, _In_ BSTR command, _Out_ BOOL* result)
{
if (!result) {
ASSERT_NOT_REACHED();
return E_POINTER;
}
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t commandBSTR(command);
fprintf(testResult, "EDITING DELEGATE: doPlatformCommand:%s\n", static_cast<const char*>(commandBSTR));
}
*result = m_acceptsEditing;
return S_OK;
}
HRESULT EditingDelegate::webViewDidBeginEditing(_In_opt_ IWebNotification* notification)
{
if (!notification)
return S_OK;
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t name;
notification->name(&name.GetBSTR());
fprintf(testResult, "EDITING DELEGATE: webViewDidBeginEditing:%s\n", static_cast<const char*>(name));
}
return S_OK;
}
HRESULT EditingDelegate::webViewDidChange(_In_opt_ IWebNotification* notification)
{
if (!notification)
return S_OK;
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t name;
notification->name(&name.GetBSTR());
fprintf(testResult, "EDITING DELEGATE: webViewDidChange:%s\n", static_cast<const char*>(name));
}
return S_OK;
}
HRESULT EditingDelegate::webViewDidEndEditing(_In_opt_ IWebNotification* notification)
{
if (!notification)
return S_OK;
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t name;
notification->name(&name.GetBSTR());
fprintf(testResult, "EDITING DELEGATE: webViewDidEndEditing:%s\n", static_cast<const char*>(name));
}
return S_OK;
}
HRESULT EditingDelegate::webViewDidChangeTypingStyle(_In_opt_ IWebNotification* notification)
{
if (!notification)
return S_OK;
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t name;
notification->name(&name.GetBSTR());
fprintf(testResult, "EDITING DELEGATE: webViewDidChangeTypingStyle:%s\n", static_cast<const char*>(name));
}
return S_OK;
}
HRESULT EditingDelegate::webViewDidChangeSelection(_In_opt_ IWebNotification* notification)
{
if (!notification)
return S_OK;
if (::gTestRunner->dumpEditingCallbacks() && !done) {
_bstr_t name;
notification->name(&name.GetBSTR());
fprintf(testResult, "EDITING DELEGATE: webViewDidChangeSelection:%s\n", static_cast<const char*>(name));
}
return S_OK;
}
HRESULT EditingDelegate::undoManagerForWebView(_In_opt_ IWebView*, _COM_Outptr_opt_ IWebUndoManager** undoManager)
{
if (!undoManager)
return E_POINTER;
*undoManager = nullptr;
return E_NOTIMPL;
}
static int indexOfFirstWordCharacter(const TCHAR* text)
{
const TCHAR* cursor = text;
while (*cursor && !iswalpha(*cursor))
++cursor;
return *cursor ? (cursor - text) : -1;
};
static int wordLength(const TCHAR* text)
{
const TCHAR* cursor = text;
while (*cursor && iswalpha(*cursor))
++cursor;
return cursor - text;
};
HRESULT EditingDelegate::checkSpellingOfString(_In_opt_ IWebView* view, _In_ LPCTSTR text,
int length, _Out_ int* misspellingLocation, _Out_ int* misspellingLength)
{
static const TCHAR* misspelledWords[] = {
// These words are known misspelled words in webkit tests.
// If there are other misspelled words in webkit tests, please add them in
// this array.
TEXT("foo"),
TEXT("Foo"),
TEXT("baz"),
TEXT("fo"),
TEXT("LibertyF"),
TEXT("chello"),
TEXT("xxxtestxxx"),
TEXT("XXxxx"),
TEXT("Textx"),
TEXT("blockquoted"),
TEXT("asd"),
TEXT("Lorem"),
TEXT("Nunc"),
TEXT("Curabitur"),
TEXT("eu"),
TEXT("adlj"),
TEXT("adaasj"),
TEXT("sdklj"),
TEXT("jlkds"),
TEXT("jsaada"),
TEXT("jlda"),
TEXT("zz"),
TEXT("contentEditable"),
0,
};
if (!misspellingLocation || !misspellingLength)
return E_POINTER;
*misspellingLocation = 0;
*misspellingLength = 0;
wstring textString(text, length);
int wordStart = indexOfFirstWordCharacter(textString.c_str());
if (-1 == wordStart)
return S_OK;
wstring word = textString.substr(wordStart, wordLength(textString.c_str() + wordStart));
for (size_t i = 0; misspelledWords[i]; ++i) {
if (word == misspelledWords[i]) {
*misspellingLocation = wordStart;
*misspellingLength = word.size();
break;
}
}
return S_OK;
}
HRESULT EditingDelegate::checkGrammarOfString(_In_opt_ IWebView*, _In_ LPCTSTR text, int length, _COM_Outptr_opt_ IEnumWebGrammarDetails** details,
_Out_ int* badGrammarLocation, _Out_ int* badGrammarLength)
{
if (!details)
return E_POINTER;
*details = nullptr;
return E_NOTIMPL;
}
HRESULT EditingDelegate::guessesForWord(_In_ BSTR word, _COM_Outptr_opt_ IEnumSpellingGuesses** guesses)
{
if (!guesses)
return E_POINTER;
*guesses = nullptr;
return E_NOTIMPL;
}
HRESULT EditingDelegate::onNotify(_In_opt_ IWebNotification* notification)
{
_bstr_t notificationName;
HRESULT hr = notification->name(&notificationName.GetBSTR());
if (FAILED(hr))
return hr;
static _bstr_t webViewDidBeginEditingNotificationName(WebViewDidBeginEditingNotification);
static _bstr_t webViewDidChangeSelectionNotificationName(WebViewDidChangeSelectionNotification);
static _bstr_t webViewDidEndEditingNotificationName(WebViewDidEndEditingNotification);
static _bstr_t webViewDidChangeTypingStyleNotificationName(WebViewDidChangeTypingStyleNotification);
static _bstr_t webViewDidChangeNotificationName(WebViewDidChangeNotification);
if (!wcscmp(notificationName, webViewDidBeginEditingNotificationName))
return webViewDidBeginEditing(notification);
if (!wcscmp(notificationName, webViewDidChangeSelectionNotificationName))
return webViewDidChangeSelection(notification);
if (!wcscmp(notificationName, webViewDidEndEditingNotificationName))
return webViewDidEndEditing(notification);
if (!wcscmp(notificationName, webViewDidChangeTypingStyleNotificationName))
return webViewDidChangeTypingStyle(notification);
if (!wcscmp(notificationName, webViewDidChangeNotificationName))
return webViewDidChange(notification);
return S_OK;
}