1/*
2 * Copyright (C) 2007 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "EditingDelegate.h"
31
32#include "DumpRenderTree.h"
33#include "LayoutTestController.h"
34#include <WebCore/COMPtr.h>
35#include <wtf/Platform.h>
36#include <JavaScriptCore/Assertions.h>
37#include <string>
38#include <tchar.h>
39
40using std::wstring;
41
42EditingDelegate::EditingDelegate()
43    : m_refCount(1)
44    , m_acceptsEditing(true)
45{
46}
47
48// IUnknown
49HRESULT STDMETHODCALLTYPE EditingDelegate::QueryInterface(REFIID riid, void** ppvObject)
50{
51    *ppvObject = 0;
52    if (IsEqualGUID(riid, IID_IUnknown))
53        *ppvObject = static_cast<IWebEditingDelegate*>(this);
54    else if (IsEqualGUID(riid, IID_IWebEditingDelegate))
55        *ppvObject = static_cast<IWebEditingDelegate*>(this);
56    else
57        return E_NOINTERFACE;
58
59    AddRef();
60    return S_OK;
61}
62
63ULONG STDMETHODCALLTYPE EditingDelegate::AddRef(void)
64{
65    return ++m_refCount;
66}
67
68ULONG STDMETHODCALLTYPE EditingDelegate::Release(void)
69{
70    ULONG newRef = --m_refCount;
71    if (!newRef)
72        delete this;
73
74    return newRef;
75}
76
77static wstring dumpPath(IDOMNode* node)
78{
79    ASSERT(node);
80
81    wstring result;
82
83    BSTR name;
84    if (FAILED(node->nodeName(&name)))
85        return result;
86    result.assign(name, SysStringLen(name));
87    SysFreeString(name);
88
89    COMPtr<IDOMNode> parent;
90    if (SUCCEEDED(node->parentNode(&parent)))
91        result += TEXT(" > ") + dumpPath(parent.get());
92
93    return result;
94}
95
96static wstring dump(IDOMRange* range)
97{
98    ASSERT(range);
99
100    int startOffset;
101    if (FAILED(range->startOffset(&startOffset)))
102        return 0;
103
104    int endOffset;
105    if (FAILED(range->endOffset(&endOffset)))
106        return 0;
107
108    COMPtr<IDOMNode> startContainer;
109    if (FAILED(range->startContainer(&startContainer)))
110        return 0;
111
112    COMPtr<IDOMNode> endContainer;
113    if (FAILED(range->endContainer(&endContainer)))
114        return 0;
115
116    wchar_t buffer[1024];
117    _snwprintf(buffer, ARRAYSIZE(buffer), L"range from %ld of %s to %ld of %s", startOffset, dumpPath(startContainer.get()), endOffset, dumpPath(endContainer.get()));
118    return buffer;
119}
120
121HRESULT STDMETHODCALLTYPE EditingDelegate::shouldBeginEditingInDOMRange(
122    /* [in] */ IWebView* webView,
123    /* [in] */ IDOMRange* range,
124    /* [retval][out] */ BOOL* result)
125{
126    if (!result) {
127        ASSERT_NOT_REACHED();
128        return E_POINTER;
129    }
130
131    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
132        _tprintf(TEXT("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n"), dump(range));
133
134    *result = m_acceptsEditing;
135    return S_OK;
136}
137
138HRESULT STDMETHODCALLTYPE EditingDelegate::shouldEndEditingInDOMRange(
139    /* [in] */ IWebView* webView,
140    /* [in] */ IDOMRange* range,
141    /* [retval][out] */ BOOL* result)
142{
143    if (!result) {
144        ASSERT_NOT_REACHED();
145        return E_POINTER;
146    }
147
148    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
149        _tprintf(TEXT("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n"), dump(range));
150
151    *result = m_acceptsEditing;
152    return S_OK;
153}
154
155HRESULT STDMETHODCALLTYPE EditingDelegate::shouldInsertNode(
156    /* [in] */ IWebView* webView,
157    /* [in] */ IDOMNode* node,
158    /* [in] */ IDOMRange* range,
159    /* [in] */ WebViewInsertAction action)
160{
161    static LPCTSTR insertactionstring[] = {
162        TEXT("WebViewInsertActionTyped"),
163        TEXT("WebViewInsertActionPasted"),
164        TEXT("WebViewInsertActionDropped"),
165    };
166
167    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
168        _tprintf(TEXT("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n"), dumpPath(node), dump(range), insertactionstring[action]);
169
170    return S_OK;
171}
172
173HRESULT STDMETHODCALLTYPE EditingDelegate::shouldInsertText(
174    /* [in] */ IWebView* webView,
175    /* [in] */ BSTR text,
176    /* [in] */ IDOMRange* range,
177    /* [in] */ WebViewInsertAction action,
178    /* [retval][out] */ BOOL* result)
179{
180    if (!result) {
181        ASSERT_NOT_REACHED();
182        return E_POINTER;
183    }
184
185    static LPCTSTR insertactionstring[] = {
186        TEXT("WebViewInsertActionTyped"),
187        TEXT("WebViewInsertActionPasted"),
188        TEXT("WebViewInsertActionDropped"),
189    };
190
191    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
192        _tprintf(TEXT("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n"), text ? text : TEXT(""), dump(range), insertactionstring[action]);
193
194    *result = m_acceptsEditing;
195    return S_OK;
196}
197
198HRESULT STDMETHODCALLTYPE EditingDelegate::shouldDeleteDOMRange(
199    /* [in] */ IWebView* webView,
200    /* [in] */ IDOMRange* range,
201    /* [retval][out] */ BOOL* result)
202{
203    if (!result) {
204        ASSERT_NOT_REACHED();
205        return E_POINTER;
206    }
207
208    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
209        _tprintf(TEXT("EDITING DELEGATE: shouldDeleteDOMRange:%s\n"), dump(range));
210
211    *result = m_acceptsEditing;
212    return S_OK;
213}
214
215HRESULT STDMETHODCALLTYPE EditingDelegate::shouldChangeSelectedDOMRange(
216    /* [in] */ IWebView* webView,
217    /* [in] */ IDOMRange* currentRange,
218    /* [in] */ IDOMRange* proposedRange,
219    /* [in] */ WebSelectionAffinity selectionAffinity,
220    /* [in] */ BOOL stillSelecting,
221    /* [retval][out] */ BOOL* result)
222{
223    if (!result) {
224        ASSERT_NOT_REACHED();
225        return E_POINTER;
226    }
227
228    static LPCTSTR affinitystring[] = {
229        TEXT("NSSelectionAffinityUpstream"),
230        TEXT("NSSelectionAffinityDownstream")
231    };
232    static LPCTSTR boolstring[] = {
233        TEXT("FALSE"),
234        TEXT("TRUE")
235    };
236
237    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
238        _tprintf(TEXT("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n"), dump(currentRange), dump(proposedRange), affinitystring[selectionAffinity], boolstring[stillSelecting]);
239
240    *result = m_acceptsEditing;
241    return S_OK;
242}
243
244HRESULT STDMETHODCALLTYPE EditingDelegate::shouldApplyStyle(
245    /* [in] */ IWebView* webView,
246    /* [in] */ IDOMCSSStyleDeclaration* style,
247    /* [in] */ IDOMRange* range,
248    /* [retval][out] */ BOOL* result)
249{
250    if (!result) {
251        ASSERT_NOT_REACHED();
252        return E_POINTER;
253    }
254
255    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
256        _tprintf(TEXT("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n"), TEXT("'style description'")/*[[style description] UTF8String]*/, dump(range));
257
258    *result = m_acceptsEditing;
259    return S_OK;
260}
261
262HRESULT STDMETHODCALLTYPE EditingDelegate::shouldChangeTypingStyle(
263    /* [in] */ IWebView* webView,
264    /* [in] */ IDOMCSSStyleDeclaration* currentStyle,
265    /* [in] */ IDOMCSSStyleDeclaration* proposedStyle,
266    /* [retval][out] */ BOOL* result)
267{
268    if (!result) {
269        ASSERT_NOT_REACHED();
270        return E_POINTER;
271    }
272
273    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
274        _tprintf(TEXT("EDITING DELEGATE: shouldChangeTypingStyle:%s toStyle:%s\n"), TEXT("'currentStyle description'"), TEXT("'proposedStyle description'"));
275
276    *result = m_acceptsEditing;
277    return S_OK;
278}
279
280HRESULT STDMETHODCALLTYPE EditingDelegate::doPlatformCommand(
281    /* [in] */ IWebView *webView,
282    /* [in] */ BSTR command,
283    /* [retval][out] */ BOOL *result)
284{
285    if (!result) {
286        ASSERT_NOT_REACHED();
287        return E_POINTER;
288    }
289
290    if (::gLayoutTestController->dumpEditingCallbacks() && !done)
291        _tprintf(TEXT("EDITING DELEGATE: doPlatformCommand:%s\n"), command ? command : TEXT(""));
292
293    *result = m_acceptsEditing;
294    return S_OK;
295}
296
297HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidBeginEditing(
298    /* [in] */ IWebNotification* notification)
299{
300    if (::gLayoutTestController->dumpEditingCallbacks() && !done) {
301        BSTR name;
302        notification->name(&name);
303        _tprintf(TEXT("EDITING DELEGATE: webViewDidBeginEditing:%s\n"), name ? name : TEXT(""));
304        SysFreeString(name);
305    }
306    return S_OK;
307}
308
309HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChange(
310    /* [in] */ IWebNotification *notification)
311{
312    if (::gLayoutTestController->dumpEditingCallbacks() && !done) {
313        BSTR name;
314        notification->name(&name);
315        _tprintf(TEXT("EDITING DELEGATE: webViewDidBeginEditing:%s\n"), name ? name : TEXT(""));
316        SysFreeString(name);
317    }
318    return S_OK;
319}
320
321HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidEndEditing(
322    /* [in] */ IWebNotification *notification)
323{
324    if (::gLayoutTestController->dumpEditingCallbacks() && !done) {
325        BSTR name;
326        notification->name(&name);
327        _tprintf(TEXT("EDITING DELEGATE: webViewDidEndEditing:%s\n"), name ? name : TEXT(""));
328        SysFreeString(name);
329    }
330    return S_OK;
331}
332
333HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChangeTypingStyle(
334    /* [in] */ IWebNotification *notification)
335{
336    if (::gLayoutTestController->dumpEditingCallbacks() && !done) {
337        BSTR name;
338        notification->name(&name);
339        _tprintf(TEXT("EDITING DELEGATE: webViewDidChangeTypingStyle:%s\n"), name ? name : TEXT(""));
340        SysFreeString(name);
341    }
342    return S_OK;
343}
344
345HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChangeSelection(
346    /* [in] */ IWebNotification *notification)
347{
348    if (::gLayoutTestController->dumpEditingCallbacks() && !done) {
349        BSTR name;
350        notification->name(&name);
351        _tprintf(TEXT("EDITING DELEGATE: webViewDidChangeSelection:%s\n"), name ? name : TEXT(""));
352        SysFreeString(name);
353    }
354    return S_OK;
355}
356
357static int indexOfFirstWordCharacter(const TCHAR* text)
358{
359    const TCHAR* cursor = text;
360    while (*cursor && !iswalpha(*cursor))
361        ++cursor;
362    return *cursor ? (cursor - text) : -1;
363};
364
365static int wordLength(const TCHAR* text)
366{
367    const TCHAR* cursor = text;
368    while (*cursor && iswalpha(*cursor))
369        ++cursor;
370    return cursor - text;
371};
372
373HRESULT STDMETHODCALLTYPE EditingDelegate::checkSpellingOfString(
374            /* [in] */ IWebView* view,
375            /* [in] */ LPCTSTR text,
376            /* [in] */ int length,
377            /* [out] */ int* misspellingLocation,
378            /* [out] */ int* misspellingLength)
379{
380    static const TCHAR* misspelledWords[] = {
381        // These words are known misspelled words in webkit tests.
382        // If there are other misspelled words in webkit tests, please add them in
383        // this array.
384        TEXT("foo"),
385        TEXT("Foo"),
386        TEXT("baz"),
387        TEXT("fo"),
388        TEXT("LibertyF"),
389        TEXT("chello"),
390        TEXT("xxxtestxxx"),
391        TEXT("XXxxx"),
392        TEXT("Textx"),
393        TEXT("blockquoted"),
394        TEXT("asd"),
395        TEXT("Lorem"),
396        TEXT("Nunc"),
397        TEXT("Curabitur"),
398        TEXT("eu"),
399        TEXT("adlj"),
400        TEXT("adaasj"),
401        TEXT("sdklj"),
402        TEXT("jlkds"),
403        TEXT("jsaada"),
404        TEXT("jlda"),
405        TEXT("zz"),
406        TEXT("contentEditable"),
407        0,
408    };
409
410    wstring textString(text, length);
411    int wordStart = indexOfFirstWordCharacter(textString.c_str());
412    if (-1 == wordStart)
413        return S_OK;
414    wstring word = textString.substr(wordStart, wordLength(textString.c_str() + wordStart));
415    for (size_t i = 0; misspelledWords[i]; ++i) {
416        if (word == misspelledWords[i]) {
417            *misspellingLocation = wordStart;
418            *misspellingLength = word.size();
419            break;
420        }
421    }
422
423    return S_OK;
424}
425