1/*
2 * Copyright (C) 2009 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include <errno.h>
21#include <glib.h>
22#include <glib/gstdio.h>
23#include <gtk/gtk.h>
24#include <unistd.h>
25#include <webkit/webkit.h>
26
27#if GTK_CHECK_VERSION(2, 14, 0)
28
29static const char* centeredContents = "<html><body><p style='text-align: center;'>Short line</p><p style='text-align: center;'>Long-size line with some foo bar baz content</p><p style='text-align: center;'>Short line</p><p style='text-align: center;'>This is a multi-line paragraph<br />where the first line<br />is the biggest one</p></body></html>";
30
31static const char* contents = "<html><body><p>This is a test. This is the second sentence. And this the third.</p></body></html>";
32
33static const char* contentsWithNewlines = "<html><body><p>This is a test. \n\nThis\n is the second sentence. And this the third.</p></body></html>";
34
35static const char* contentsWithSpecialChars = "<html><body><p>&laquo;&nbsp;This is a paragraph with &ldquo;special&rdquo; characters inside.&nbsp;&raquo;</p></body></html>";
36
37static const char* contentsInTextarea = "<html><body><textarea cols='80'>This is a test. This is the second sentence. And this the third.</textarea></body></html>";
38
39static const char* contentsInTextInput = "<html><body><input type='text' size='80' value='This is a test. This is the second sentence. And this the third.'/></body></html>";
40
41static const char* contentsInParagraphAndBodySimple = "<html><body><p>This is a test.</p>Hello world.</body></html>";
42
43static const char* contentsInParagraphAndBodyModerate = "<html><body><p>This is a test.</p>Hello world.<br /><font color='#00cc00'>This sentence is green.</font><br />This one is not.</body></html>";
44
45static const char* contentsInTable = "<html><body><table><tr><td>foo</td><td>bar</td></tr></table></body></html>";
46
47static const char* contentsInTableWithHeaders = "<html><body><table><tr><th>foo</th><th>bar</th><th colspan='2'>baz</th></tr><tr><th>qux</th><td>1</td><td>2</td><td>3</td></tr><tr><th rowspan='2'>quux</th><td>4</td><td>5</td><td>6</td></tr><tr><td>6</td><td>7</td><td>8</td></tr><tr><th>corge</th><td>9</td><td>10</td><td>11</td></tr></table><table><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></table></body></html>";
48
49static const char* contentsWithExtraneousWhiteSpaces = "<html><head><body><p>This\n                          paragraph\n                                                      is\n                                                                                                                                                                                                                                                                                                                                                                            borked!</p></body></html>";
50
51static const char* comboBoxSelector = "<html><body><select><option selected value='foo'>foo</option><option value='bar'>bar</option></select></body></html>";
52
53static const char* embeddedObjects = "<html><body><p>Choose: <input value='foo' type='checkbox'/>foo <input value='bar' type='checkbox'/>bar (pick one)</p><p>Choose: <select name='foo'><option>bar</option><option>baz</option></select> (pick one)</p><p><input name='foobarbutton' value='foobar' type='button'/></p></body></html>";
54
55static const char* formWithTextInputs = "<html><body><form><input type='text' name='entry' /></form></body></html>";
56
57static const char* hypertextAndHyperlinks = "<html><body><p>A paragraph with no links at all</p><p><a href='http://foo.bar.baz/'>A line</a> with <a href='http://bar.baz.foo/'>a link in the middle</a> as well as at the beginning and <a href='http://baz.foo.bar/'>at the end</a></p><ol><li>List item with a <span><a href='http://foo.bar.baz/'>link inside a span node</a></span></li></ol></body></html>";
58
59static const char* layoutAndDataTables = "<html><body><table><tr><th>Odd</th><th>Even</th></tr><tr><td>1</td><td>2</td></tr></table><table><tr><td>foo</td><td>bar</td></tr></table></body></html>";
60
61static const char* linksWithInlineImages = "<html><head><style>a.http:before {content: url(no-image.png);}</style><body><p><a class='http' href='foo'>foo</a> bar baz</p><p>foo <a class='http' href='bar'>bar</a> baz</p><p>foo bar <a class='http' href='baz'>baz</a></p></body></html>";
62
63static const char* listsOfItems = "<html><body><ul><li>text only</li><li><a href='foo'>link only</a></li><li>text and a <a href='bar'>link</a></li></ul><ol><li>text only</li><li><a href='foo'>link only</a></li><li>text and a <a href='bar'>link</a></li></ol></body></html>";
64
65static const char* textForCaretBrowsing = "<html><body><h1>A text header</h1><p>A paragraph <a href='http://foo.bar.baz/'>with a link</a> in the middle</p><ol><li>A list item</li></ol><select><option selected value='foo'>An option in a combo box</option></select></body></html>";
66
67static const char* textForSelections = "<html><body><p>A paragraph with plain text</p><p>A paragraph with <a href='http://webkit.org'>a link</a> in the middle</p><ol><li>A list item</li></ol><select></body></html>";
68
69static const char* textWithAttributes = "<html><head><style>.st1 {font-family: monospace; color:rgb(120,121,122);} .st2 {text-decoration:underline; background-color:rgb(80,81,82);}</style></head><body><p style=\"font-size:14; text-align:right;\">This is the <i>first</i><b> sentence of this text.</b></p><p class=\"st1\">This sentence should have an style applied <span class=\"st2\">and this part should have another one</span>.</p><p>x<sub>1</sub><sup>2</sup>=x<sub>2</sub><sup>3</sup></p><p style=\"text-align:center;\">This sentence is the <strike>last</strike> one.</p></body></html>";
70
71static void waitForAccessibleObjects()
72{
73    /* Manually spin the main context to make sure the accessible
74       objects are properly created before continuing. */
75    while (g_main_context_pending(0))
76        g_main_context_iteration(0, TRUE);
77}
78
79typedef gchar* (*AtkGetTextFunction) (AtkText*, gint, AtkTextBoundary, gint*, gint*);
80
81static void testGetTextFunction(AtkText* textObject, AtkGetTextFunction fn, AtkTextBoundary boundary, gint offset, const char* textResult, gint startOffsetResult, gint endOffsetResult)
82{
83    gint startOffset;
84    gint endOffset;
85    char* text = fn(textObject, offset, boundary, &startOffset, &endOffset);
86    g_assert_cmpstr(text, ==, textResult);
87    g_assert_cmpint(startOffset, ==, startOffsetResult);
88    g_assert_cmpint(endOffset, ==, endOffsetResult);
89    g_free(text);
90}
91
92static void runGetTextTests(AtkText* textObject)
93{
94    char* text = atk_text_get_text(textObject, 0, -1);
95    g_assert_cmpstr(text, ==, "This is a test. This is the second sentence. And this the third.");
96    g_free(text);
97
98    /* ATK_TEXT_BOUNDARY_CHAR */
99    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_CHAR,
100                        0, "T", 0, 1);
101
102    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_CHAR,
103                        0, "h", 1, 2);
104
105    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_CHAR,
106                        0, "", 0, 0);
107
108    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_CHAR,
109                        1, "T", 0, 1);
110
111    /* ATK_TEXT_BOUNDARY_WORD_START */
112    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
113                        0, "This ", 0, 5);
114
115    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
116                        4, "This ", 0, 5);
117
118    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
119                        10, "test. ", 10, 16);
120
121    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
122                        58, "third.", 58, 64);
123
124    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_START,
125                        5, "This ", 0, 5);
126
127    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_START,
128                        7, "This ", 0, 5);
129
130    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
131                        0, "is ", 5, 8);
132
133    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
134                        4, "is ", 5, 8);
135
136    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
137                        3, "is ", 5, 8);
138
139    /* ATK_TEXT_BOUNDARY_WORD_END */
140    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
141                        0, "This", 0, 4);
142
143    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
144                        4, " is", 4, 7);
145
146    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
147                        5, " is", 4, 7);
148
149    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
150                        9, " test", 9, 14);
151
152    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
153                        5, "This", 0, 4);
154
155    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
156                        4, "This", 0, 4);
157
158    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
159                        7, " is", 4, 7);
160
161    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_END,
162                        5, " a", 7, 9);
163
164    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_END,
165                        4, " a", 7, 9);
166
167    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
168                        58, " third", 57, 63);
169
170    /* ATK_TEXT_BOUNDARY_SENTENCE_START */
171    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
172                        0, "This is a test. ", 0, 16);
173
174    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
175                        15, "This is a test. ", 0, 16);
176
177    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
178                        0, "This is the second sentence. ", 16, 45);
179
180    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
181                        15, "This is the second sentence. ", 16, 45);
182
183    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
184                        16, "This is a test. ", 0, 16);
185
186    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
187                        44, "This is a test. ", 0, 16);
188
189    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
190                        15, "", 0, 0);
191
192    /* ATK_TEXT_BOUNDARY_SENTENCE_END */
193    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
194                        0, "This is a test.", 0, 15);
195
196    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
197                        15, " This is the second sentence.", 15, 44);
198
199    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
200                        16, " This is the second sentence.", 15, 44);
201
202    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
203                        17, " This is the second sentence.", 15, 44);
204
205    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
206                        0, " This is the second sentence.", 15, 44);
207
208    testGetTextFunction(textObject, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
209                        15, " And this the third.", 44, 64);
210
211    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
212                        16, "This is a test.", 0, 15);
213
214    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
215                        15, "This is a test.", 0, 15);
216
217    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
218                        14, "", 0, 0);
219
220    testGetTextFunction(textObject, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
221                        44, " This is the second sentence.", 15, 44);
222
223    /* It's trick to test these properly right now, since our a11y
224       implementation splits different lines in different a11y items. */
225    /* ATK_TEXT_BOUNDARY_LINE_START */
226    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_START,
227                        0, "This is a test. This is the second sentence. And this the third.", 0, 64);
228
229    /* ATK_TEXT_BOUNDARY_LINE_END */
230    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_END,
231                        0, "This is a test. This is the second sentence. And this the third.", 0, 64);
232}
233
234static void testWebkitAtkCaretOffsets()
235{
236    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
237    g_object_ref_sink(webView);
238    GtkAllocation allocation = { 0, 0, 800, 600 };
239    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
240    webkit_web_view_load_string(webView, textForCaretBrowsing, 0, 0, 0);
241
242    /* Wait for the accessible objects to be created. */
243    waitForAccessibleObjects();
244
245    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
246    g_assert(object);
247
248    AtkObject* header = atk_object_ref_accessible_child(object, 0);
249    g_assert(ATK_IS_TEXT(header));
250    gchar* text = atk_text_get_text(ATK_TEXT(header), 0, -1);
251    g_assert_cmpstr(text, ==, "A text header");
252    g_free (text);
253
254    /* It should be possible to place the caret inside a header. */
255    gboolean result = atk_text_set_caret_offset(ATK_TEXT(header), 5);
256    g_assert_cmpint(result, ==, TRUE);
257    gint offset = atk_text_get_caret_offset(ATK_TEXT(header));
258    g_assert_cmpint(offset, ==, 5);
259
260    AtkObject* paragraph = atk_object_ref_accessible_child(object, 1);
261    g_assert(ATK_IS_TEXT(paragraph));
262    text = atk_text_get_text(ATK_TEXT(paragraph), 0, -1);
263    g_assert_cmpstr(text, ==, "A paragraph with a link in the middle");
264    g_free (text);
265
266    /* It should be possible to place the caret inside a paragraph and a link. */
267    result = atk_text_set_caret_offset(ATK_TEXT(paragraph), 5);
268    g_assert_cmpint(result, ==, TRUE);
269    offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
270    g_assert_cmpint(offset, ==, 5);
271
272    result = atk_text_set_caret_offset(ATK_TEXT(paragraph), 20);
273    g_assert_cmpint(result, ==, TRUE);
274    offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
275    g_assert_cmpint(offset, ==, 20);
276
277    result = atk_text_set_caret_offset(ATK_TEXT(paragraph), 30);
278    g_assert_cmpint(result, ==, TRUE);
279    offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
280    g_assert_cmpint(offset, ==, 30);
281
282    AtkObject* list = atk_object_ref_accessible_child(object, 2);
283    g_assert(ATK_OBJECT(list));
284    g_assert(atk_object_get_role(list) == ATK_ROLE_LIST);
285    g_assert_cmpint(atk_object_get_n_accessible_children(list), ==, 1);
286
287    AtkObject* listItem = atk_object_ref_accessible_child(list, 0);
288    g_assert(ATK_IS_TEXT(listItem));
289    text = atk_text_get_text(ATK_TEXT(listItem), 0, -1);
290    g_assert_cmpstr(text, ==, "1. A list item");
291    g_free (text);
292
293    /* It's not possible to place the caret inside an item's marker. */
294    result = atk_text_set_caret_offset(ATK_TEXT(listItem), 1);
295    g_assert_cmpint(result, ==, FALSE);
296
297    /* It should be possible to place the caret inside an item's text. */
298    result = atk_text_set_caret_offset(ATK_TEXT(listItem), 5);
299    g_assert_cmpint(result, ==, TRUE);
300    offset = atk_text_get_caret_offset(ATK_TEXT(listItem));
301    g_assert_cmpint(offset, ==, 5);
302
303    AtkObject* panel = atk_object_ref_accessible_child(object, 3);
304    g_assert(ATK_IS_OBJECT(panel));
305    g_assert(atk_object_get_role(panel) == ATK_ROLE_PANEL);
306
307    AtkObject* comboBox = atk_object_ref_accessible_child(panel, 0);
308    g_assert(ATK_IS_OBJECT(comboBox));
309    g_assert(atk_object_get_role(comboBox) == ATK_ROLE_COMBO_BOX);
310
311    AtkObject* menuPopup = atk_object_ref_accessible_child(comboBox, 0);
312    g_assert(ATK_IS_OBJECT(menuPopup));
313    g_assert(atk_object_get_role(menuPopup) == ATK_ROLE_MENU);
314
315    AtkObject* comboBoxOption = atk_object_ref_accessible_child(menuPopup, 0);
316    g_assert(ATK_IS_OBJECT(comboBoxOption));
317    g_assert(atk_object_get_role(comboBoxOption) == ATK_ROLE_MENU_ITEM);
318    g_assert(ATK_IS_TEXT(comboBoxOption));
319    text = atk_text_get_text(ATK_TEXT(comboBoxOption), 0, -1);
320    g_assert_cmpstr(text, ==, "An option in a combo box");
321
322    /* It's not possible to place the caret inside an option for a combobox. */
323    result = atk_text_set_caret_offset(ATK_TEXT(comboBoxOption), 1);
324    g_assert_cmpint(result, ==, FALSE);
325
326    g_object_unref(header);
327    g_object_unref(paragraph);
328    g_object_unref(list);
329    g_object_unref(listItem);
330    g_object_unref(panel);
331    g_object_unref(comboBox);
332    g_object_unref(menuPopup);
333    g_object_unref(comboBoxOption);
334    g_object_unref(webView);
335}
336
337static void testWebkitAtkCaretOffsetsAndExtranousWhiteSpaces()
338{
339    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
340    g_object_ref_sink(webView);
341    GtkAllocation allocation = { 0, 0, 800, 600 };
342    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
343    webkit_web_view_load_string(webView, contentsWithExtraneousWhiteSpaces, 0, 0, 0);
344
345    /* Wait for the accessible objects to be created. */
346    waitForAccessibleObjects();
347
348    /* Enable caret browsing. */
349    WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
350    g_object_set(G_OBJECT(settings), "enable-caret-browsing", TRUE, NULL);
351    webkit_web_view_set_settings(webView, settings);
352
353    /* Get to the inner AtkText object. */
354    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
355    g_assert(object);
356    object = atk_object_ref_accessible_child(object, 0);
357    g_assert(object);
358
359    AtkText* textObject = ATK_TEXT(object);
360    g_assert(ATK_IS_TEXT(textObject));
361
362    gchar* text = atk_text_get_text(textObject, 0, -1);
363    g_assert_cmpstr(text, ==, "This paragraph is borked!");
364    g_free(text);
365
366    gint characterCount = atk_text_get_character_count(textObject);
367    g_assert_cmpint(characterCount, ==, 25);
368
369    gboolean result = atk_text_set_caret_offset(textObject, characterCount - 1);
370    g_assert_cmpint(result, ==, TRUE);
371
372    gint caretOffset = atk_text_get_caret_offset(textObject);
373    g_assert_cmpint(caretOffset, ==, characterCount - 1);
374
375    result = atk_text_set_caret_offset(textObject, characterCount);
376    g_assert_cmpint(result, ==, TRUE);
377
378    caretOffset = atk_text_get_caret_offset(textObject);
379    g_assert_cmpint(caretOffset, ==, characterCount);
380
381    g_object_unref(webView);
382}
383
384static void testWebkitAtkComboBox()
385{
386    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
387    g_object_ref_sink(webView);
388    GtkAllocation allocation = { 0, 0, 800, 600 };
389    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
390    webkit_web_view_load_string(webView, comboBoxSelector, 0, 0, 0);
391
392    /* Wait for the accessible objects to be created. */
393    waitForAccessibleObjects();
394
395    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
396    g_assert(object);
397
398    AtkObject* formObject = atk_object_ref_accessible_child(object, 0);
399    g_assert(formObject);
400
401    AtkObject* comboBox = atk_object_ref_accessible_child(formObject, 0);
402    g_assert(ATK_IS_OBJECT(comboBox));
403
404    AtkObject* menuPopup = atk_object_ref_accessible_child(comboBox, 0);
405    g_assert(ATK_IS_OBJECT(menuPopup));
406
407    AtkObject* item1 = atk_object_ref_accessible_child(menuPopup, 0);
408    g_assert(ATK_IS_OBJECT(item1));
409
410    AtkObject* item2 = atk_object_ref_accessible_child(menuPopup, 1);
411    g_assert(ATK_IS_OBJECT(item2));
412
413    /* Check roles. */
414    g_assert(atk_object_get_role(comboBox) == ATK_ROLE_COMBO_BOX);
415    g_assert(atk_object_get_role(menuPopup) == ATK_ROLE_MENU);
416    g_assert(atk_object_get_role(item1) == ATK_ROLE_MENU_ITEM);
417    g_assert(atk_object_get_role(item2) == ATK_ROLE_MENU_ITEM);
418
419    /* Check the implementation of the AtkSelection interface. */
420    g_assert(ATK_IS_SELECTION(comboBox));
421    AtkSelection* atkSelection = ATK_SELECTION(comboBox);
422    g_assert_cmpint(atk_selection_get_selection_count(atkSelection), ==, 1);
423    g_assert(atk_selection_is_child_selected(atkSelection, 0));
424    g_assert(!atk_selection_is_child_selected(atkSelection, 1));
425    AtkObject* selectedItem = atk_selection_ref_selection(atkSelection, 0);
426    g_assert(selectedItem == item1);
427    g_object_unref(selectedItem);
428
429    /* Check the implementations of the AtkAction interface. */
430    g_assert(ATK_IS_ACTION(comboBox));
431    AtkAction* atkAction = ATK_ACTION(comboBox);
432    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
433    g_assert(atk_action_do_action(atkAction, 0));
434
435    g_assert(ATK_IS_ACTION(menuPopup));
436    atkAction = ATK_ACTION(menuPopup);
437    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
438    g_assert(atk_action_do_action(atkAction, 0));
439
440    g_assert(ATK_IS_ACTION(item1));
441    atkAction = ATK_ACTION(item1);
442    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
443    g_assert(atk_action_do_action(atkAction, 0));
444
445    g_assert(ATK_IS_ACTION(item2));
446    atkAction = ATK_ACTION(item2);
447    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
448    g_assert(atk_action_do_action(atkAction, 0));
449
450    /* After selecting the second item, selection should have changed. */
451    g_assert_cmpint(atk_selection_get_selection_count(atkSelection), ==, 1);
452    g_assert(!atk_selection_is_child_selected(atkSelection, 0));
453    g_assert(atk_selection_is_child_selected(atkSelection, 1));
454    selectedItem = atk_selection_ref_selection(atkSelection, 0);
455    g_assert(selectedItem == item2);
456    g_object_unref(selectedItem);
457
458    /* Check the implementation of the AtkText interface. */
459    g_assert(ATK_IS_TEXT(item1));
460    AtkText* atkText = ATK_TEXT(item1);
461    char *text = atk_text_get_text(atkText, 0, -1);
462    g_assert_cmpstr(text, ==, "foo");
463    g_free(text);
464    text = atk_text_get_text(atkText, 0, 2);
465    g_assert_cmpstr(text, ==, "fo");
466    g_free(text);
467
468    g_assert(ATK_IS_TEXT(item2));
469    atkText = ATK_TEXT(item2);
470    text = atk_text_get_text(atkText, 0, -1);
471    g_assert_cmpstr(text, ==, "bar");
472    g_free(text);
473    text = atk_text_get_text(atkText, 1, 3);
474    g_assert_cmpstr(text, ==, "ar");
475    g_free(text);
476
477    g_object_unref(formObject);
478    g_object_unref(comboBox);
479    g_object_unref(menuPopup);
480    g_object_unref(item1);
481    g_object_unref(item2);
482    g_object_unref(webView);
483}
484
485static void testWebkitAtkEmbeddedObjects()
486{
487    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
488    g_object_ref_sink(webView);
489    GtkAllocation allocation = { 0, 0, 800, 600 };
490    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
491    webkit_web_view_load_string(webView, embeddedObjects, 0, 0, 0);
492
493    /* Wait for the accessible objects to be created. */
494    waitForAccessibleObjects();
495
496    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
497    g_assert(object);
498
499    AtkText* paragraph1 = ATK_TEXT(atk_object_ref_accessible_child(object, 0));
500    g_assert(ATK_IS_TEXT(paragraph1));
501    g_assert(ATK_IS_HYPERTEXT(paragraph1));
502
503    const gchar* expectedText = "Choose: \357\277\274foo \357\277\274bar (pick one)";
504    char* text = atk_text_get_text(paragraph1, 0, -1);
505    g_assert_cmpstr(text, ==, expectedText);
506    g_free(text);
507
508    gint nLinks = atk_hypertext_get_n_links(ATK_HYPERTEXT(paragraph1));
509    g_assert_cmpint(nLinks, ==, 2);
510
511    AtkHyperlink* hLink = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph1), 0);
512    g_assert(ATK_HYPERLINK(hLink));
513    AtkObject* hLinkObject = atk_hyperlink_get_object(hLink, 0);
514    g_assert(ATK_OBJECT(hLinkObject));
515    g_assert(atk_object_get_role(hLinkObject) == ATK_ROLE_CHECK_BOX);
516    g_assert_cmpint(atk_hyperlink_get_start_index(hLink), ==, 8);
517    g_assert_cmpint(atk_hyperlink_get_end_index(hLink), ==, 9);
518    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink), ==, 1);
519    g_assert_cmpstr(atk_hyperlink_get_uri(hLink, 0), ==, 0);
520
521    AtkText* paragraph2 = ATK_TEXT(atk_object_ref_accessible_child(object, 1));
522    g_assert(ATK_IS_TEXT(paragraph2));
523    g_assert(ATK_IS_HYPERTEXT(paragraph2));
524
525    expectedText = "Choose: \357\277\274 (pick one)";
526    text = atk_text_get_text(paragraph2, 0, -1);
527    g_assert_cmpstr(text, ==, expectedText);
528    g_free(text);
529
530    nLinks = atk_hypertext_get_n_links(ATK_HYPERTEXT(paragraph2));
531    g_assert_cmpint(nLinks, ==, 1);
532
533    hLink = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph2), 0);
534    g_assert(ATK_HYPERLINK(hLink));
535    hLinkObject = atk_hyperlink_get_object(hLink, 0);
536    g_assert(ATK_OBJECT(hLinkObject));
537    g_assert(atk_object_get_role(hLinkObject) == ATK_ROLE_COMBO_BOX);
538    g_assert_cmpint(atk_hyperlink_get_start_index(hLink), ==, 8);
539    g_assert_cmpint(atk_hyperlink_get_end_index(hLink), ==, 9);
540    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink), ==, 1);
541    g_assert_cmpstr(atk_hyperlink_get_uri(hLink, 0), ==, 0);
542
543    AtkText* paragraph3 = ATK_TEXT(atk_object_ref_accessible_child(object, 2));
544    g_assert(ATK_IS_TEXT(paragraph3));
545    g_assert(ATK_IS_HYPERTEXT(paragraph3));
546
547    expectedText = "\357\277\274";
548    text = atk_text_get_text(paragraph3, 0, -1);
549    g_assert_cmpstr(text, ==, expectedText);
550    g_free(text);
551
552    nLinks = atk_hypertext_get_n_links(ATK_HYPERTEXT(paragraph3));
553    g_assert_cmpint(nLinks, ==, 1);
554
555    hLink = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph3), 0);
556    g_assert(ATK_HYPERLINK(hLink));
557    hLinkObject = atk_hyperlink_get_object(hLink, 0);
558    g_assert(ATK_OBJECT(hLinkObject));
559    g_assert(atk_object_get_role(hLinkObject) == ATK_ROLE_PUSH_BUTTON);
560    g_assert_cmpint(atk_hyperlink_get_start_index(hLink), ==, 0);
561    g_assert_cmpint(atk_hyperlink_get_end_index(hLink), ==, 1);
562    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink), ==, 1);
563    g_assert_cmpstr(atk_hyperlink_get_uri(hLink, 0), ==, 0);
564
565    g_object_unref(paragraph1);
566    g_object_unref(paragraph2);
567    g_object_unref(paragraph3);
568    g_object_unref(webView);
569}
570
571static void testWebkitAtkGetTextAtOffsetForms()
572{
573    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
574    g_object_ref_sink(webView);
575    GtkAllocation allocation = { 0, 0, 800, 600 };
576    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
577    webkit_web_view_load_string(webView, contents, 0, 0, 0);
578
579    /* Wait for the accessible objects to be created. */
580    waitForAccessibleObjects();
581
582    /* Get to the inner AtkText object. */
583    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
584    g_assert(object);
585    object = atk_object_ref_accessible_child(object, 0);
586    g_assert(object);
587
588    AtkText* textObject = ATK_TEXT(object);
589    g_assert(ATK_IS_TEXT(textObject));
590
591    runGetTextTests(textObject);
592
593    g_object_unref(webView);
594}
595
596static void testWebkitAtkGetTextAtOffset()
597{
598    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
599    g_object_ref_sink(webView);
600    GtkAllocation allocation = { 0, 0, 800, 600 };
601    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
602    webkit_web_view_load_string(webView, contents, 0, 0, 0);
603
604    /* Wait for the accessible objects to be created. */
605    waitForAccessibleObjects();
606
607    /* Get to the inner AtkText object. */
608    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
609    g_assert(object);
610    object = atk_object_ref_accessible_child(object, 0);
611    g_assert(object);
612
613    AtkText* textObject = ATK_TEXT(object);
614    g_assert(ATK_IS_TEXT(textObject));
615
616    runGetTextTests(textObject);
617
618    g_object_unref(webView);
619}
620
621static void testWebkitAtkGetTextAtOffsetNewlines()
622{
623    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
624    g_object_ref_sink(webView);
625    GtkAllocation allocation = { 0, 0, 800, 600 };
626    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
627    webkit_web_view_load_string(webView, contentsWithNewlines, 0, 0, 0);
628
629    /* Wait for the accessible objects to be created. */
630    waitForAccessibleObjects();
631
632    /* Get to the inner AtkText object. */
633    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
634    g_assert(object);
635    object = atk_object_ref_accessible_child(object, 0);
636    g_assert(object);
637
638    AtkText* textObject = ATK_TEXT(object);
639    g_assert(ATK_IS_TEXT(textObject));
640
641    runGetTextTests(textObject);
642
643    g_object_unref(webView);
644}
645
646static void testWebkitAtkGetTextAtOffsetTextarea()
647{
648    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
649    g_object_ref_sink(webView);
650    GtkAllocation allocation = { 0, 0, 800, 600 };
651    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
652    webkit_web_view_load_string(webView, contentsInTextarea, 0, 0, 0);
653
654    /* Wait for the accessible objects to be created. */
655    waitForAccessibleObjects();
656
657    /* Get to the inner AtkText object. */
658    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
659    g_assert(object);
660    object = atk_object_ref_accessible_child(object, 0);
661    g_assert(object);
662    object = atk_object_ref_accessible_child(object, 0);
663    g_assert(object);
664
665    AtkText* textObject = ATK_TEXT(object);
666    g_assert(ATK_IS_TEXT(textObject));
667
668    runGetTextTests(textObject);
669
670    g_object_unref(webView);
671}
672
673static void testWebkitAtkGetTextAtOffsetTextInput()
674{
675    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
676    g_object_ref_sink(webView);
677    GtkAllocation allocation = { 0, 0, 800, 600 };
678    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
679    webkit_web_view_load_string(webView, contentsInTextInput, 0, 0, 0);
680
681    /* Wait for the accessible objects to be created. */
682    waitForAccessibleObjects();
683
684    /* Get to the inner AtkText object. */
685    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
686    g_assert(object);
687    object = atk_object_ref_accessible_child(object, 0);
688    g_assert(object);
689    object = atk_object_ref_accessible_child(object, 0);
690    g_assert(object);
691
692    AtkText* textObject = ATK_TEXT(object);
693    g_assert(ATK_IS_TEXT(textObject));
694
695    runGetTextTests(textObject);
696
697    g_object_unref(webView);
698}
699
700static void testWebkitAtkGetTextAtOffsetWithSpecialCharacters()
701{
702    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
703    g_object_ref_sink(webView);
704    GtkAllocation allocation = { 0, 0, 800, 600 };
705    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
706    webkit_web_view_load_string(webView, contentsWithSpecialChars, 0, 0, 0);
707
708    /* Wait for the accessible objects to be created. */
709    waitForAccessibleObjects();
710
711    /* Get to the inner AtkText object. */
712    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
713    g_assert(object);
714    object = atk_object_ref_accessible_child(object, 0);
715    g_assert(object);
716
717    AtkText* textObject = ATK_TEXT(object);
718    g_assert(ATK_IS_TEXT(textObject));
719
720    const gchar* expectedText = "\302\253\302\240This is a paragraph with \342\200\234special\342\200\235 characters inside.\302\240\302\273";
721    char* text = atk_text_get_text(textObject, 0, -1);
722    g_assert_cmpstr(text, ==, expectedText);
723    g_free(text);
724
725    /* Check that getting the text with ATK_TEXT_BOUNDARY_LINE_START
726       and ATK_TEXT_BOUNDARY_LINE_END does not crash because of not
727       properly handling characters inside the UTF-8 string. */
728    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_START, 0, expectedText, 0, 57);
729    testGetTextFunction(textObject, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_END, 0, expectedText, 0, 57);
730
731    g_object_unref(webView);
732}
733
734static void testWebkitAtkGetTextInParagraphAndBodySimple()
735{
736    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
737    g_object_ref_sink(webView);
738    GtkAllocation allocation = { 0, 0, 800, 600 };
739    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
740    webkit_web_view_load_string(webView, contentsInParagraphAndBodySimple, 0, 0, 0);
741
742    /* Wait for the accessible objects to be created. */
743    waitForAccessibleObjects();
744
745    /* Get to the inner AtkText object. */
746    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
747    g_assert(object);
748    AtkObject* object1 = atk_object_ref_accessible_child(object, 0);
749    g_assert(object1);
750    AtkObject* object2 = atk_object_ref_accessible_child(object, 1);
751    g_assert(object2);
752
753    AtkText* textObject1 = ATK_TEXT(object1);
754    g_assert(ATK_IS_TEXT(textObject1));
755    AtkText* textObject2 = ATK_TEXT(object2);
756    g_assert(ATK_IS_TEXT(textObject2));
757
758    char *text = atk_text_get_text(textObject1, 0, -1);
759    g_assert_cmpstr(text, ==, "This is a test.");
760
761    text = atk_text_get_text(textObject2, 0, 12);
762    g_assert_cmpstr(text, ==, "Hello world.");
763
764    g_object_unref(object1);
765    g_object_unref(object2);
766    g_object_unref(webView);
767}
768
769static void testWebkitAtkGetTextInParagraphAndBodyModerate()
770{
771    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
772    g_object_ref_sink(webView);
773    GtkAllocation allocation = { 0, 0, 800, 600 };
774    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
775    webkit_web_view_load_string(webView, contentsInParagraphAndBodyModerate, 0, 0, 0);
776
777    /* Wait for the accessible objects to be created. */
778    waitForAccessibleObjects();
779
780    /* Get to the inner AtkText object. */
781    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
782    g_assert(object);
783    AtkObject* object1 = atk_object_ref_accessible_child(object, 0);
784    g_assert(object1);
785    AtkObject* object2 = atk_object_ref_accessible_child(object, 1);
786    g_assert(object2);
787
788    AtkText* textObject1 = ATK_TEXT(object1);
789    g_assert(ATK_IS_TEXT(textObject1));
790    AtkText* textObject2 = ATK_TEXT(object2);
791    g_assert(ATK_IS_TEXT(textObject2));
792
793    char *text = atk_text_get_text(textObject1, 0, -1);
794    g_assert_cmpstr(text, ==, "This is a test.");
795
796    text = atk_text_get_text(textObject2, 0, 53);
797    g_assert_cmpstr(text, ==, "Hello world.\nThis sentence is green.\nThis one is not.");
798
799    g_object_unref(object1);
800    g_object_unref(object2);
801    g_object_unref(webView);
802}
803
804static void testWebkitAtkGetTextInTable()
805{
806    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
807    g_object_ref_sink(webView);
808    GtkAllocation allocation = { 0, 0, 800, 600 };
809    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
810    webkit_web_view_load_string(webView, contentsInTable, 0, 0, 0);
811
812    /* Wait for the accessible objects to be created. */
813    waitForAccessibleObjects();
814
815    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
816    g_assert(object);
817    object = atk_object_ref_accessible_child(object, 0);
818    g_assert(object);
819
820    /* Tables should not implement AtkText. */
821    g_assert(!G_TYPE_INSTANCE_GET_INTERFACE(object, ATK_TYPE_TEXT, AtkTextIface));
822
823    g_object_unref(object);
824    g_object_unref(webView);
825}
826
827static void testWebkitAtkGetHeadersInTable()
828{
829    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
830    g_object_ref_sink(webView);
831    GtkAllocation allocation = { 0, 0, 800, 600 };
832    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
833    webkit_web_view_load_string(webView, contentsInTableWithHeaders, 0, 0, 0);
834
835    /* Wait for the accessible objects to be created. */
836    waitForAccessibleObjects();
837
838    AtkObject* axWebView = gtk_widget_get_accessible(GTK_WIDGET(webView));
839    g_assert(axWebView);
840
841    /* Check table with both column and row headers. */
842    AtkObject* table = atk_object_ref_accessible_child(axWebView, 0);
843    g_assert(table);
844    g_assert(atk_object_get_role(table) == ATK_ROLE_TABLE);
845
846    AtkObject* colHeader = atk_table_get_column_header(ATK_TABLE(table), 0);
847    g_assert(colHeader);
848    g_assert(atk_object_get_role(colHeader) == ATK_ROLE_TABLE_CELL);
849    g_assert(atk_object_get_index_in_parent(colHeader) == 0);
850
851    colHeader = atk_table_get_column_header(ATK_TABLE(table), 1);
852    g_assert(colHeader);
853    g_assert(atk_object_get_role(colHeader) == ATK_ROLE_TABLE_CELL);
854    g_assert(atk_object_get_index_in_parent(colHeader) == 1);
855
856    colHeader = atk_table_get_column_header(ATK_TABLE(table), 2);
857    g_assert(colHeader);
858    g_assert(atk_object_get_role(colHeader) == ATK_ROLE_TABLE_CELL);
859    g_assert(atk_object_get_index_in_parent(colHeader) == 2);
860
861    colHeader = atk_table_get_column_header(ATK_TABLE(table), 3);
862    g_assert(colHeader);
863    g_assert(atk_object_get_role(colHeader) == ATK_ROLE_TABLE_CELL);
864    g_assert(atk_object_get_index_in_parent(colHeader) == 2);
865
866    AtkObject* rowHeader = atk_table_get_row_header(ATK_TABLE(table), 0);
867    g_assert(rowHeader);
868    g_assert(atk_object_get_role(rowHeader) == ATK_ROLE_TABLE_CELL);
869    g_assert(atk_object_get_index_in_parent(rowHeader) == 0);
870
871    rowHeader = atk_table_get_row_header(ATK_TABLE(table), 1);
872    g_assert(rowHeader);
873    g_assert(atk_object_get_role(rowHeader) == ATK_ROLE_TABLE_CELL);
874    g_assert(atk_object_get_index_in_parent(rowHeader) == 3);
875
876    rowHeader = atk_table_get_row_header(ATK_TABLE(table), 2);
877    g_assert(rowHeader);
878    g_assert(atk_object_get_role(rowHeader) == ATK_ROLE_TABLE_CELL);
879    g_assert(atk_object_get_index_in_parent(rowHeader) == 7);
880
881    rowHeader = atk_table_get_row_header(ATK_TABLE(table), 3);
882    g_assert(rowHeader);
883    g_assert(atk_object_get_role(rowHeader) == ATK_ROLE_TABLE_CELL);
884    g_assert(atk_object_get_index_in_parent(rowHeader) == 7);
885
886    g_object_unref(table);
887
888    /* Check table with no headers at all. */
889    table = atk_object_ref_accessible_child(axWebView, 1);
890    g_assert(table);
891    g_assert(atk_object_get_role(table) == ATK_ROLE_TABLE);
892
893    colHeader = atk_table_get_column_header(ATK_TABLE(table), 0);
894    g_assert(colHeader == 0);
895
896    colHeader = atk_table_get_column_header(ATK_TABLE(table), 1);
897    g_assert(colHeader == 0);
898
899    rowHeader = atk_table_get_row_header(ATK_TABLE(table), 0);
900    g_assert(rowHeader == 0);
901
902    rowHeader = atk_table_get_row_header(ATK_TABLE(table), 1);
903    g_assert(rowHeader == 0);
904
905    g_object_unref(table);
906    g_object_unref(webView);
907}
908
909static gint compAtkAttribute(AtkAttribute* a1, AtkAttribute* a2)
910{
911    gint strcmpVal = g_strcmp0(a1->name, a2->name);
912    if (strcmpVal)
913        return strcmpVal;
914    return g_strcmp0(a1->value, a2->value);
915}
916
917static gint compAtkAttributeName(AtkAttribute* a1, AtkAttribute* a2)
918{
919    return g_strcmp0(a1->name, a2->name);
920}
921
922static gboolean atkAttributeSetAttributeNameHasValue(AtkAttributeSet* set, const gchar* attributeName, const gchar* value)
923{
924    AtkAttribute at;
925    at.name = (gchar*)attributeName;
926    GSList* element = g_slist_find_custom(set, &at, (GCompareFunc)compAtkAttributeName);
927    return element && !g_strcmp0(((AtkAttribute*)(element->data))->value, value);
928}
929
930static gboolean atkAttributeSetContainsAttributeName(AtkAttributeSet* set, const gchar* attributeName)
931{
932    AtkAttribute at;
933    at.name = (gchar*)attributeName;
934    return g_slist_find_custom(set, &at, (GCompareFunc)compAtkAttributeName) ? true : false;
935}
936
937static gboolean atkAttributeSetAttributeHasValue(AtkAttributeSet* set, AtkTextAttribute attribute, const gchar* value)
938{
939    return atkAttributeSetAttributeNameHasValue(set, atk_text_attribute_get_name(attribute), value);
940}
941
942static gboolean atkAttributeSetAreEqual(AtkAttributeSet* set1, AtkAttributeSet* set2)
943{
944    if (!set1)
945        return !set2;
946
947    set1 = g_slist_sort(set1, (GCompareFunc)compAtkAttribute);
948    set2 = g_slist_sort(set2, (GCompareFunc)compAtkAttribute);
949
950    while (set1) {
951        if (!set2 || compAtkAttribute(set1->data, set2->data))
952            return FALSE;
953
954        set1 = set1->next;
955        set2 = set2->next;
956    }
957
958    return (!set2);
959}
960
961static void testWebkitAtkTextAttributes()
962{
963    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
964    g_object_ref_sink(webView);
965    GtkAllocation allocation = { 0, 0, 800, 600 };
966    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
967    webkit_web_view_load_string(webView, textWithAttributes, 0, 0, 0);
968
969    /* Wait for the accessible objects to be created. */
970    waitForAccessibleObjects();
971
972    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
973    g_assert(object);
974
975    AtkObject* child = atk_object_ref_accessible_child(object, 0);
976    g_assert(child && ATK_IS_TEXT(child));
977    AtkText* childText = ATK_TEXT(child);
978
979    gint startOffset;
980    gint endOffset;
981    AtkAttributeSet* set1 = atk_text_get_run_attributes(childText, 0, &startOffset, &endOffset);
982    g_assert_cmpint(startOffset, ==, 0);
983    g_assert_cmpint(endOffset, ==, 12);
984    g_assert(atkAttributeSetAreEqual(set1, 0));
985
986    AtkAttributeSet* set2 = atk_text_get_run_attributes(childText, 15, &startOffset, &endOffset);
987    g_assert_cmpint(startOffset, ==, 12);
988    g_assert_cmpint(endOffset, ==, 17);
989    g_assert(atkAttributeSetAttributeHasValue(set2, ATK_TEXT_ATTR_STYLE, "italic"));
990
991    AtkAttributeSet* set3 = atk_text_get_run_attributes(childText, 17, &startOffset, &endOffset);
992    g_assert_cmpint(startOffset, ==, 17);
993    g_assert_cmpint(endOffset, ==, 40);
994    g_assert(atkAttributeSetAttributeHasValue(set3, ATK_TEXT_ATTR_WEIGHT, "700"));
995
996    AtkAttributeSet* set4 = atk_text_get_default_attributes(childText);
997    g_assert(atkAttributeSetAttributeHasValue(set4, ATK_TEXT_ATTR_STYLE, "normal"));
998    g_assert(atkAttributeSetAttributeHasValue(set4, ATK_TEXT_ATTR_JUSTIFICATION, "right"));
999    g_assert(atkAttributeSetAttributeHasValue(set4, ATK_TEXT_ATTR_SIZE, "14"));
1000    atk_attribute_set_free(set1);
1001    atk_attribute_set_free(set2);
1002    atk_attribute_set_free(set3);
1003    atk_attribute_set_free(set4);
1004
1005    child = atk_object_ref_accessible_child(object, 1);
1006    g_assert(child && ATK_IS_TEXT(child));
1007    childText = ATK_TEXT(child);
1008
1009    set1 = atk_text_get_default_attributes(childText);
1010    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_FAMILY_NAME, "monospace"));
1011    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_STYLE, "normal"));
1012    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_STRIKETHROUGH, "false"));
1013    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_WEIGHT, "400"));
1014    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_FG_COLOR, "120,121,122"));
1015
1016    set2 = atk_text_get_run_attributes(childText, 43, &startOffset, &endOffset);
1017    g_assert_cmpint(startOffset, ==, 43);
1018    g_assert_cmpint(endOffset, ==, 80);
1019    /* Checks that default attributes of text are not returned when called to atk_text_get_run_attributes. */
1020    g_assert(!atkAttributeSetAttributeHasValue(set2, ATK_TEXT_ATTR_FG_COLOR, "120,121,122"));
1021    g_assert(atkAttributeSetAttributeHasValue(set2, ATK_TEXT_ATTR_UNDERLINE, "single"));
1022    g_assert(atkAttributeSetAttributeHasValue(set2, ATK_TEXT_ATTR_BG_COLOR, "80,81,82"));
1023    atk_attribute_set_free(set1);
1024    atk_attribute_set_free(set2);
1025
1026    child = atk_object_ref_accessible_child(object, 2);
1027    g_assert(child && ATK_IS_TEXT(child));
1028    childText = ATK_TEXT(child);
1029
1030    set1 = atk_text_get_run_attributes(childText, 0, &startOffset, &endOffset);
1031    set2 = atk_text_get_run_attributes(childText, 3, &startOffset, &endOffset);
1032    g_assert(atkAttributeSetAreEqual(set1, set2));
1033    atk_attribute_set_free(set2);
1034
1035    set2 = atk_text_get_run_attributes(childText, 1, &startOffset, &endOffset);
1036    set3 = atk_text_get_run_attributes(childText, 5, &startOffset, &endOffset);
1037    g_assert(atkAttributeSetAreEqual(set2, set3));
1038    g_assert(!atkAttributeSetAreEqual(set1, set2));
1039    atk_attribute_set_free(set3);
1040
1041    set3 = atk_text_get_run_attributes(childText, 2, &startOffset, &endOffset);
1042    set4 = atk_text_get_run_attributes(childText, 6, &startOffset, &endOffset);
1043    g_assert(atkAttributeSetAreEqual(set3, set4));
1044    g_assert(!atkAttributeSetAreEqual(set1, set3));
1045    g_assert(!atkAttributeSetAreEqual(set2, set3));
1046    atk_attribute_set_free(set1);
1047    atk_attribute_set_free(set2);
1048    atk_attribute_set_free(set3);
1049    atk_attribute_set_free(set4);
1050
1051    child = atk_object_ref_accessible_child(object, 3);
1052    g_assert(child && ATK_IS_TEXT(child));
1053    childText = ATK_TEXT(child);
1054    set1 = atk_text_get_run_attributes(childText, 24, &startOffset, &endOffset);
1055    g_assert_cmpint(startOffset, ==, 21);
1056    g_assert_cmpint(endOffset, ==, 25);
1057    g_assert(atkAttributeSetAttributeHasValue(set1, ATK_TEXT_ATTR_STRIKETHROUGH, "true"));
1058
1059    set2 = atk_text_get_run_attributes(childText, 25, &startOffset, &endOffset);
1060    g_assert_cmpint(startOffset, ==, 25);
1061    g_assert_cmpint(endOffset, ==, 30);
1062    g_assert(atkAttributeSetAreEqual(set2, 0));
1063
1064    set3 = atk_text_get_default_attributes(childText);
1065    g_assert(atkAttributeSetAttributeHasValue(set3, ATK_TEXT_ATTR_JUSTIFICATION, "center"));
1066    atk_attribute_set_free(set1);
1067    atk_attribute_set_free(set2);
1068    atk_attribute_set_free(set3);
1069}
1070
1071static void testWebkitAtkTextSelections()
1072{
1073    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1074    g_object_ref_sink(webView);
1075    GtkAllocation allocation = { 0, 0, 800, 600 };
1076    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1077    webkit_web_view_load_string(webView, textForSelections, 0, 0, 0);
1078
1079    /* Wait for the accessible objects to be created. */
1080    waitForAccessibleObjects();
1081
1082    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1083    g_assert(object);
1084
1085    AtkText* paragraph1 = ATK_TEXT(atk_object_ref_accessible_child(object, 0));
1086    g_assert(ATK_IS_TEXT(paragraph1));
1087
1088    AtkText* paragraph2 = ATK_TEXT(atk_object_ref_accessible_child(object, 1));
1089    g_assert(ATK_IS_TEXT(paragraph2));
1090
1091    AtkText* link = ATK_TEXT(atk_object_ref_accessible_child(ATK_OBJECT(paragraph2), 0));
1092    g_assert(ATK_IS_TEXT(link));
1093
1094    AtkObject* list = atk_object_ref_accessible_child(object, 2);
1095    g_assert(ATK_OBJECT(list));
1096
1097    AtkText* listItem = ATK_TEXT(atk_object_ref_accessible_child(list, 0));
1098    g_assert(ATK_IS_TEXT(listItem));
1099
1100    /* First paragraph (simple text). */
1101
1102    /* Basic initial checks. */
1103    g_assert_cmpint(atk_text_get_n_selections(paragraph1), ==, 0);
1104
1105    gint startOffset;
1106    gint endOffset;
1107    gchar* selectedText = atk_text_get_selection(paragraph1, 0, &startOffset, &endOffset);
1108    g_assert_cmpint(startOffset, ==, 0);
1109    g_assert_cmpint(endOffset, ==, 0);
1110    g_assert_cmpstr(selectedText, ==, 0);
1111    g_free (selectedText);
1112
1113    /* Try removing a non existing (yet) selection. */
1114    gboolean result = atk_text_remove_selection(paragraph1, 0);
1115    g_assert(!result);
1116
1117    /* Try setting a 0-char selection. */
1118    result = atk_text_set_selection(paragraph1, 0, 5, 5);
1119    g_assert(result);
1120
1121    /* Make a selection and test it. */
1122    result = atk_text_set_selection(paragraph1, 0, 5, 25);
1123    g_assert(result);
1124    g_assert_cmpint(atk_text_get_n_selections(paragraph1), ==, 1);
1125    selectedText = atk_text_get_selection(paragraph1, 0, &startOffset, &endOffset);
1126    g_assert_cmpint(startOffset, ==, 5);
1127    g_assert_cmpint(endOffset, ==, 25);
1128    g_assert_cmpstr(selectedText, ==, "agraph with plain te");
1129    g_free (selectedText);
1130    /* Try removing the selection from other AtkText object (should fail). */
1131    result = atk_text_remove_selection(paragraph2, 0);
1132    g_assert(!result);
1133
1134    /* Remove the selection and test everything again. */
1135    result = atk_text_remove_selection(paragraph1, 0);
1136    g_assert(result);
1137    g_assert_cmpint(atk_text_get_n_selections(paragraph1), ==, 0);
1138    selectedText = atk_text_get_selection(paragraph1, 0, &startOffset, &endOffset);
1139    /* Now offsets should be the same, set to the last position of the caret. */
1140    g_assert_cmpint(startOffset, ==, endOffset);
1141    g_assert_cmpint(startOffset, ==, 25);
1142    g_assert_cmpint(endOffset, ==, 25);
1143    g_assert_cmpstr(selectedText, ==, 0);
1144    g_free (selectedText);
1145
1146    /* Second paragraph (text + link + text). */
1147
1148    /* Set a selection partially covering the link and test it. */
1149    result = atk_text_set_selection(paragraph2, 0, 7, 21);
1150    g_assert(result);
1151
1152    /* Test the paragraph first. */
1153    g_assert_cmpint(atk_text_get_n_selections(paragraph2), ==, 1);
1154    selectedText = atk_text_get_selection(paragraph2, 0, &startOffset, &endOffset);
1155    g_assert_cmpint(startOffset, ==, 7);
1156    g_assert_cmpint(endOffset, ==, 21);
1157    g_assert_cmpstr(selectedText, ==, "raph with a li");
1158    g_free (selectedText);
1159
1160    /* Now test just the link. */
1161    g_assert_cmpint(atk_text_get_n_selections(link), ==, 1);
1162    selectedText = atk_text_get_selection(link, 0, &startOffset, &endOffset);
1163    g_assert_cmpint(startOffset, ==, 0);
1164    g_assert_cmpint(endOffset, ==, 4);
1165    g_assert_cmpstr(selectedText, ==, "a li");
1166    g_free (selectedText);
1167
1168    /* Make a selection after the link and check selection for the whole paragraph. */
1169    result = atk_text_set_selection(paragraph2, 0, 27, 37);
1170    g_assert(result);
1171    g_assert_cmpint(atk_text_get_n_selections(paragraph2), ==, 1);
1172    selectedText = atk_text_get_selection(paragraph2, 0, &startOffset, &endOffset);
1173    g_assert_cmpint(startOffset, ==, 27);
1174    g_assert_cmpint(endOffset, ==, 37);
1175    g_assert_cmpstr(selectedText, ==, "the middle");
1176    g_free (selectedText);
1177
1178    /* Remove selections and text everything again. */
1179    result = atk_text_remove_selection(paragraph2, 0);
1180    g_assert(result);
1181    g_assert_cmpint(atk_text_get_n_selections(paragraph2), ==, 0);
1182    selectedText = atk_text_get_selection(paragraph2, 0, &startOffset, &endOffset);
1183    /* Now offsets should be the same (no selection). */
1184    g_assert_cmpint(startOffset, ==, endOffset);
1185    g_assert_cmpstr(selectedText, ==, 0);
1186    g_free (selectedText);
1187
1188    g_assert_cmpint(atk_text_get_n_selections(link), ==, 0);
1189    selectedText = atk_text_get_selection(link, 0, &startOffset, &endOffset);
1190    /* Now offsets should be the same (no selection). */
1191    g_assert_cmpint(startOffset, ==, endOffset);
1192    g_assert_cmpstr(selectedText, ==, 0);
1193    g_free (selectedText);
1194
1195    /* List item */
1196
1197    g_assert(atk_object_get_role(list) == ATK_ROLE_LIST);
1198    g_assert_cmpint(atk_object_get_n_accessible_children(list), ==, 1);
1199
1200    gchar* text = atk_text_get_text(listItem, 0, -1);
1201    g_assert_cmpstr(text, ==, "1. A list item");
1202    g_free (text);
1203
1204    /* It's not possible to select text inside an item's marker. */
1205    result = atk_text_set_selection (listItem, 0, 0, 9);
1206    g_assert(!result);
1207    result = atk_text_set_selection (listItem, 0, 9, 1);
1208    g_assert(!result);
1209
1210    /* It should be possible to select text inside an item's text. */
1211    result = atk_text_set_selection (listItem, 0, 3, 9);
1212    g_assert(result);
1213
1214    g_assert_cmpint(atk_text_get_n_selections(listItem), ==, 1);
1215    selectedText = atk_text_get_selection(listItem, 0, &startOffset, &endOffset);
1216    g_assert_cmpint(startOffset, ==, 3);
1217    g_assert_cmpint(endOffset, ==, 9);
1218    g_assert_cmpstr(selectedText, ==, "A list");
1219    g_free (selectedText);
1220
1221    g_object_unref(paragraph1);
1222    g_object_unref(paragraph2);
1223    g_object_unref(list);
1224    g_object_unref(listItem);
1225    g_object_unref(webView);
1226}
1227
1228static void testWebkitAtkGetExtents()
1229{
1230    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1231    g_object_ref_sink(webView);
1232    GtkAllocation allocation = { 0, 0, 800, 600 };
1233    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1234    webkit_web_view_load_string(webView, centeredContents, 0, 0, 0);
1235
1236    /* Wait for the accessible objects to be created. */
1237    waitForAccessibleObjects();
1238
1239    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1240    g_assert(object);
1241
1242    AtkText* shortText1 = ATK_TEXT(atk_object_ref_accessible_child(object, 0));
1243    g_assert(ATK_IS_TEXT(shortText1));
1244    AtkText* longText = ATK_TEXT(atk_object_ref_accessible_child(object, 1));
1245    g_assert(ATK_IS_TEXT(longText));
1246    AtkText* shortText2 = ATK_TEXT(atk_object_ref_accessible_child(object, 2));
1247    g_assert(ATK_IS_TEXT(shortText2));
1248    AtkText* multilineText = ATK_TEXT(atk_object_ref_accessible_child(object, 3));
1249    g_assert(ATK_IS_TEXT(multilineText));
1250
1251    /* Start with window extents. */
1252    AtkTextRectangle sline_window1, sline_window2, lline_window, mline_window;
1253    atk_text_get_range_extents(shortText1, 0, 10, ATK_XY_WINDOW, &sline_window1);
1254    atk_text_get_range_extents(longText, 0, 44, ATK_XY_WINDOW, &lline_window);
1255    atk_text_get_range_extents(shortText2, 0, 10, ATK_XY_WINDOW, &sline_window2);
1256    atk_text_get_range_extents(multilineText, 0, 60, ATK_XY_WINDOW, &mline_window);
1257
1258    /* Check vertical line position. */
1259    g_assert_cmpint(sline_window1.y + sline_window1.height, <=, lline_window.y);
1260    g_assert_cmpint(lline_window.y + lline_window.height + sline_window2.height, <=, mline_window.y);
1261
1262    /* Paragraphs 1 and 3 have identical text and alignment. */
1263    g_assert_cmpint(sline_window1.x, ==, sline_window2.x);
1264    g_assert_cmpint(sline_window1.width, ==, sline_window2.width);
1265    g_assert_cmpint(sline_window1.height, ==, sline_window2.height);
1266
1267    /* All lines should be the same height; line 2 is the widest line. */
1268    g_assert_cmpint(sline_window1.height, ==, lline_window.height);
1269    g_assert_cmpint(sline_window1.width, <, lline_window.width);
1270
1271    /* Make sure the character extents jive with the range extents. */
1272    gint x;
1273    gint y;
1274    gint width;
1275    gint height;
1276
1277    /* First paragraph (short text). */
1278    atk_text_get_character_extents(shortText1, 0, &x, &y, &width, &height, ATK_XY_WINDOW);
1279    g_assert_cmpint(x, ==, sline_window1.x);
1280    g_assert_cmpint(y, ==, sline_window1.y);
1281    g_assert_cmpint(height, ==, sline_window1.height);
1282
1283    atk_text_get_character_extents(shortText1, 9, &x, &y, &width, &height, ATK_XY_WINDOW);
1284    g_assert_cmpint(x, ==, sline_window1.x + sline_window1.width - width);
1285    g_assert_cmpint(y, ==, sline_window1.y);
1286    g_assert_cmpint(height, ==, sline_window1.height);
1287
1288    /* Second paragraph (long text). */
1289    atk_text_get_character_extents(longText, 0, &x, &y, &width, &height, ATK_XY_WINDOW);
1290    g_assert_cmpint(x, ==, lline_window.x);
1291    g_assert_cmpint(y, ==, lline_window.y);
1292    g_assert_cmpint(height, ==, lline_window.height);
1293
1294    atk_text_get_character_extents(longText, 43, &x, &y, &width, &height, ATK_XY_WINDOW);
1295    g_assert_cmpint(x, ==, lline_window.x + lline_window.width - width);
1296    g_assert_cmpint(y, ==, lline_window.y);
1297    g_assert_cmpint(height, ==, lline_window.height);
1298
1299    /* Third paragraph (short text). */
1300    atk_text_get_character_extents(shortText2, 0, &x, &y, &width, &height, ATK_XY_WINDOW);
1301    g_assert_cmpint(x, ==, sline_window2.x);
1302    g_assert_cmpint(y, ==, sline_window2.y);
1303    g_assert_cmpint(height, ==, sline_window2.height);
1304
1305    atk_text_get_character_extents(shortText2, 9, &x, &y, &width, &height, ATK_XY_WINDOW);
1306    g_assert_cmpint(x, ==, sline_window2.x + sline_window2.width - width);
1307    g_assert_cmpint(y, ==, sline_window2.y);
1308    g_assert_cmpint(height, ==, sline_window2.height);
1309
1310    /* Four paragraph (3 lines multi-line text). */
1311    atk_text_get_character_extents(multilineText, 0, &x, &y, &width, &height, ATK_XY_WINDOW);
1312    g_assert_cmpint(x, ==, mline_window.x);
1313    g_assert_cmpint(y, ==, mline_window.y);
1314    g_assert_cmpint(3 * height, ==, mline_window.height);
1315
1316    atk_text_get_character_extents(multilineText, 59, &x, &y, &width, &height, ATK_XY_WINDOW);
1317    /* Last line won't fill the whole width of the rectangle. */
1318    g_assert_cmpint(x, <=, mline_window.x + mline_window.width - width);
1319    g_assert_cmpint(y, ==, mline_window.y + mline_window.height - height);
1320    g_assert_cmpint(height, <=, mline_window.height);
1321
1322    /* Check that extent for a full line are the same height than for
1323       a partial section of the same line */
1324    gint startOffset;
1325    gint endOffset;
1326    gchar* text = atk_text_get_text_at_offset(multilineText, 0, ATK_TEXT_BOUNDARY_LINE_START, &startOffset, &endOffset);
1327    g_free(text);
1328
1329    AtkTextRectangle fline_window;
1330    AtkTextRectangle afline_window;
1331    atk_text_get_range_extents(multilineText, startOffset, endOffset, ATK_XY_WINDOW, &fline_window);
1332    atk_text_get_range_extents(multilineText, startOffset, endOffset - 1, ATK_XY_WINDOW, &afline_window);
1333    g_assert_cmpint(fline_window.x, ==, afline_window.x);
1334    g_assert_cmpint(fline_window.y, ==, afline_window.y);
1335    g_assert_cmpint(fline_window.height, ==, afline_window.height);
1336
1337    g_object_unref(shortText1);
1338    g_object_unref(shortText2);
1339    g_object_unref(longText);
1340    g_object_unref(multilineText);
1341    g_object_unref(webView);
1342}
1343
1344static void testWebkitAtkLayoutAndDataTables()
1345{
1346    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1347    g_object_ref_sink(webView);
1348    GtkAllocation allocation = { 0, 0, 800, 600 };
1349    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1350    webkit_web_view_load_string(webView, layoutAndDataTables, 0, 0, 0);
1351
1352    /* Wait for the accessible objects to be created. */
1353    waitForAccessibleObjects();
1354
1355    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1356    g_assert(object);
1357
1358    /* Check the non-layout table (data table). */
1359
1360    AtkObject* table1 = atk_object_ref_accessible_child(object, 0);
1361    g_assert(ATK_IS_TABLE(table1));
1362    AtkAttributeSet* set1 = atk_object_get_attributes(table1);
1363    g_assert(set1);
1364    g_assert(!atkAttributeSetContainsAttributeName(set1, "layout-guess"));
1365    atk_attribute_set_free(set1);
1366
1367    /* Check the layout table. */
1368
1369    AtkObject* table2 = atk_object_ref_accessible_child(object, 1);
1370    g_assert(ATK_IS_TABLE(table2));
1371    AtkAttributeSet* set2 = atk_object_get_attributes(table2);
1372    g_assert(set2);
1373    g_assert(atkAttributeSetContainsAttributeName(set2, "layout-guess"));
1374    g_assert(atkAttributeSetAttributeNameHasValue(set2, "layout-guess", "true"));
1375    atk_attribute_set_free(set2);
1376
1377    g_object_unref(table1);
1378    g_object_unref(table2);
1379    g_object_unref(webView);
1380}
1381
1382static void testWebkitAtkLinksWithInlineImages()
1383{
1384    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1385    g_object_ref_sink(webView);
1386    GtkAllocation allocation = { 0, 0, 800, 600 };
1387    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1388    webkit_web_view_load_string(webView, linksWithInlineImages, 0, 0, 0);
1389
1390    /* Wait for the accessible objects to be created. */
1391    waitForAccessibleObjects();
1392
1393    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1394    g_assert(object);
1395
1396    /* First paragraph (link at the beginning). */
1397    AtkObject* paragraph = atk_object_ref_accessible_child(object, 0);
1398    g_assert(ATK_IS_TEXT(paragraph));
1399    gint startOffset;
1400    gint endOffset;
1401    gchar* text = atk_text_get_text_at_offset(ATK_TEXT(paragraph), 0, ATK_TEXT_BOUNDARY_LINE_START, &startOffset, &endOffset);
1402    g_assert(text);
1403    g_assert_cmpstr(text, ==, "foo bar baz");
1404    g_assert_cmpint(startOffset, ==, 0);
1405    g_assert_cmpint(endOffset, ==, 11);
1406    g_free(text);
1407    g_object_unref(paragraph);
1408
1409    /* Second paragraph (link in the middle). */
1410    paragraph = atk_object_ref_accessible_child(object, 1);
1411    g_assert(ATK_IS_TEXT(paragraph));
1412    text = atk_text_get_text_at_offset(ATK_TEXT(paragraph), 0, ATK_TEXT_BOUNDARY_LINE_START, &startOffset, &endOffset);
1413    g_assert(text);
1414    g_assert_cmpstr(text, ==, "foo bar baz");
1415    g_assert_cmpint(startOffset, ==, 0);
1416    g_assert_cmpint(endOffset, ==, 11);
1417    g_free(text);
1418    g_object_unref(paragraph);
1419
1420    /* Third paragraph (link at the end). */
1421    paragraph = atk_object_ref_accessible_child(object, 2);
1422    g_assert(ATK_IS_TEXT(paragraph));
1423    text = atk_text_get_text_at_offset(ATK_TEXT(paragraph), 0, ATK_TEXT_BOUNDARY_LINE_START, &startOffset, &endOffset);
1424    g_assert(text);
1425    g_assert_cmpstr(text, ==, "foo bar baz");
1426    g_assert_cmpint(startOffset, ==, 0);
1427    g_assert_cmpint(endOffset, ==, 11);
1428    g_free(text);
1429    g_object_unref(paragraph);
1430
1431    g_object_unref(webView);
1432}
1433
1434static void testWebkitAtkHypertextAndHyperlinks()
1435{
1436    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1437    g_object_ref_sink(webView);
1438    GtkAllocation allocation = { 0, 0, 800, 600 };
1439    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1440    webkit_web_view_load_string(webView, hypertextAndHyperlinks, 0, 0, 0);
1441
1442    /* Wait for the accessible objects to be created. */
1443    waitForAccessibleObjects();
1444
1445    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1446    g_assert(object);
1447
1448    AtkObject* paragraph1 = atk_object_ref_accessible_child(object, 0);
1449    g_assert(ATK_OBJECT(paragraph1));
1450    g_assert(atk_object_get_role(paragraph1) == ATK_ROLE_PARAGRAPH);
1451    g_assert(ATK_IS_HYPERTEXT(paragraph1));
1452
1453    /* No links in the first paragraph. */
1454    gint nLinks = atk_hypertext_get_n_links(ATK_HYPERTEXT(paragraph1));
1455    g_assert_cmpint(nLinks, ==, 0);
1456
1457    AtkObject* paragraph2 = atk_object_ref_accessible_child(object, 1);
1458    g_assert(ATK_OBJECT(paragraph2));
1459    g_assert(atk_object_get_role(paragraph2) == ATK_ROLE_PARAGRAPH);
1460    g_assert(ATK_IS_HYPERTEXT(paragraph2));
1461
1462    /* Check links in the second paragraph.
1463       nLinks = atk_hypertext_get_n_links(ATK_HYPERTEXT(paragraph2));
1464       g_assert_cmpint(nLinks, ==, 3); */
1465
1466    AtkHyperlink* hLink1 = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph2), 0);
1467    g_assert(ATK_HYPERLINK(hLink1));
1468    AtkObject* hLinkObject1 = atk_hyperlink_get_object(hLink1, 0);
1469    g_assert(ATK_OBJECT(hLinkObject1));
1470    g_assert(atk_object_get_role(hLinkObject1) == ATK_ROLE_LINK);
1471    g_assert_cmpint(atk_hyperlink_get_start_index(hLink1), ==, 0);
1472    g_assert_cmpint(atk_hyperlink_get_end_index(hLink1), ==, 6);
1473    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink1), ==, 1);
1474    g_assert_cmpstr(atk_hyperlink_get_uri(hLink1, 0), ==, "http://foo.bar.baz/");
1475
1476    AtkHyperlink* hLink2 = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph2), 1);
1477    g_assert(ATK_HYPERLINK(hLink2));
1478    AtkObject* hLinkObject2 = atk_hyperlink_get_object(hLink2, 0);
1479    g_assert(ATK_OBJECT(hLinkObject2));
1480    g_assert(atk_object_get_role(hLinkObject2) == ATK_ROLE_LINK);
1481    g_assert_cmpint(atk_hyperlink_get_start_index(hLink2), ==, 12);
1482    g_assert_cmpint(atk_hyperlink_get_end_index(hLink2), ==, 32);
1483    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink2), ==, 1);
1484    g_assert_cmpstr(atk_hyperlink_get_uri(hLink2, 0), ==, "http://bar.baz.foo/");
1485
1486    AtkHyperlink* hLink3 = atk_hypertext_get_link(ATK_HYPERTEXT(paragraph2), 2);
1487    g_assert(ATK_HYPERLINK(hLink3));
1488    AtkObject* hLinkObject3 = atk_hyperlink_get_object(hLink3, 0);
1489    g_assert(ATK_OBJECT(hLinkObject3));
1490    g_assert(atk_object_get_role(hLinkObject3) == ATK_ROLE_LINK);
1491    g_assert_cmpint(atk_hyperlink_get_start_index(hLink3), ==, 65);
1492    g_assert_cmpint(atk_hyperlink_get_end_index(hLink3), ==, 75);
1493    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLink3), ==, 1);
1494    g_assert_cmpstr(atk_hyperlink_get_uri(hLink3, 0), ==, "http://baz.foo.bar/");
1495
1496    AtkObject* list = atk_object_ref_accessible_child(object, 2);
1497    g_assert(ATK_OBJECT(list));
1498    g_assert(atk_object_get_role(list) == ATK_ROLE_LIST);
1499    g_assert_cmpint(atk_object_get_n_accessible_children(list), ==, 1);
1500
1501    AtkObject* listItem = atk_object_ref_accessible_child(list, 0);
1502    g_assert(ATK_IS_TEXT(listItem));
1503    g_assert(ATK_IS_HYPERTEXT(listItem));
1504
1505    AtkHyperlink* hLinkInListItem = atk_hypertext_get_link(ATK_HYPERTEXT(listItem), 0);
1506    g_assert(ATK_HYPERLINK(hLinkInListItem));
1507    AtkObject* hLinkObject = atk_hyperlink_get_object(hLinkInListItem, 0);
1508    g_assert(ATK_OBJECT(hLinkObject));
1509    g_assert(atk_object_get_role(hLinkObject) == ATK_ROLE_LINK);
1510    g_assert_cmpint(atk_hyperlink_get_start_index(hLinkInListItem), ==, 20);
1511    g_assert_cmpint(atk_hyperlink_get_end_index(hLinkInListItem), ==, 43);
1512    g_assert_cmpint(atk_hyperlink_get_n_anchors(hLinkInListItem), ==, 1);
1513    g_assert_cmpstr(atk_hyperlink_get_uri(hLinkInListItem, 0), ==, "http://foo.bar.baz/");
1514
1515    /* Finally check the AtkAction interface for a given AtkHyperlink. */
1516    g_assert(ATK_IS_ACTION(hLink1));
1517    g_assert_cmpint(atk_action_get_n_actions(ATK_ACTION(hLink1)), ==, 1);
1518    g_assert_cmpstr(atk_action_get_keybinding(ATK_ACTION(hLink1), 0), ==, "");
1519    g_assert_cmpstr(atk_action_get_name(ATK_ACTION(hLink1), 0), ==, "jump");
1520    g_assert(atk_action_do_action(ATK_ACTION(hLink1), 0));
1521
1522    g_object_unref(paragraph1);
1523    g_object_unref(paragraph2);
1524    g_object_unref(list);
1525    g_object_unref(listItem);
1526    g_object_unref(webView);
1527}
1528
1529static void testWebkitAtkListsOfItems()
1530{
1531    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1532    g_object_ref_sink(webView);
1533    GtkAllocation allocation = { 0, 0, 800, 600 };
1534    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1535    webkit_web_view_load_string(webView, listsOfItems, 0, 0, 0);
1536
1537    /* Wait for the accessible objects to be created. */
1538    waitForAccessibleObjects();
1539
1540    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1541    g_assert(object);
1542
1543    /* Unordered list. */
1544
1545    AtkObject* uList = atk_object_ref_accessible_child(object, 0);
1546    g_assert(ATK_OBJECT(uList));
1547    g_assert(atk_object_get_role(uList) == ATK_ROLE_LIST);
1548    g_assert_cmpint(atk_object_get_n_accessible_children(uList), ==, 3);
1549
1550    AtkObject* item1 = atk_object_ref_accessible_child(uList, 0);
1551    g_assert(ATK_IS_TEXT(item1));
1552    AtkObject* item2 = atk_object_ref_accessible_child(uList, 1);
1553    g_assert(ATK_IS_TEXT(item2));
1554    AtkObject* item3 = atk_object_ref_accessible_child(uList, 2);
1555    g_assert(ATK_IS_TEXT(item3));
1556
1557    g_assert_cmpint(atk_object_get_n_accessible_children(item1), ==, 0);
1558    g_assert_cmpint(atk_object_get_n_accessible_children(item2), ==, 1);
1559    g_assert_cmpint(atk_object_get_n_accessible_children(item3), ==, 1);
1560
1561    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item1), 0, -1), ==, "\342\200\242 text only");
1562    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item2), 0, -1), ==, "\342\200\242 link only");
1563    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item3), 0, -1), ==, "\342\200\242 text and a link");
1564
1565    g_object_unref(item1);
1566    g_object_unref(item2);
1567    g_object_unref(item3);
1568
1569    /* Ordered list. */
1570
1571    AtkObject* oList = atk_object_ref_accessible_child(object, 1);
1572    g_assert(ATK_OBJECT(oList));
1573    g_assert(atk_object_get_role(oList) == ATK_ROLE_LIST);
1574    g_assert_cmpint(atk_object_get_n_accessible_children(oList), ==, 3);
1575
1576    item1 = atk_object_ref_accessible_child(oList, 0);
1577    g_assert(ATK_IS_TEXT(item1));
1578    item2 = atk_object_ref_accessible_child(oList, 1);
1579    g_assert(ATK_IS_TEXT(item2));
1580    item3 = atk_object_ref_accessible_child(oList, 2);
1581    g_assert(ATK_IS_TEXT(item3));
1582
1583    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item1), 0, -1), ==, "1. text only");
1584    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item2), 0, -1), ==, "2. link only");
1585    g_assert_cmpstr(atk_text_get_text(ATK_TEXT(item3), 0, -1), ==, "3. text and a link");
1586
1587    g_assert_cmpint(atk_object_get_n_accessible_children(item1), ==, 0);
1588    g_assert_cmpint(atk_object_get_n_accessible_children(item2), ==, 1);
1589    g_assert_cmpint(atk_object_get_n_accessible_children(item3), ==, 1);
1590
1591    g_object_unref(item1);
1592    g_object_unref(item2);
1593    g_object_unref(item3);
1594
1595    g_object_unref(uList);
1596    g_object_unref(oList);
1597    g_object_unref(webView);
1598}
1599
1600static gboolean textInserted = FALSE;
1601static gboolean textDeleted = FALSE;
1602
1603static void textChangedCb(AtkText* text, gint pos, gint len, const gchar* detail)
1604{
1605    g_assert(text && ATK_IS_OBJECT(text));
1606
1607    if (!g_strcmp0(detail, "insert"))
1608        textInserted = TRUE;
1609    else if (!g_strcmp0(detail, "delete"))
1610        textDeleted = TRUE;
1611}
1612
1613static gboolean checkTextChanges(gpointer unused)
1614{
1615    g_assert_cmpint(textInserted, ==, TRUE);
1616    g_assert_cmpint(textDeleted, ==, TRUE);
1617    return FALSE;
1618}
1619
1620static void testWebkitAtkTextChangedNotifications()
1621{
1622    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
1623    g_object_ref_sink(webView);
1624    GtkAllocation allocation = { 0, 0, 800, 600 };
1625    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
1626    webkit_web_view_load_string(webView, formWithTextInputs, 0, 0, 0);
1627
1628    /* Wait for the accessible objects to be created. */
1629    waitForAccessibleObjects();
1630
1631    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
1632    g_assert(object);
1633
1634    AtkObject* form = atk_object_ref_accessible_child(object, 0);
1635    g_assert(ATK_IS_OBJECT(form));
1636
1637    AtkObject* textEntry = atk_object_ref_accessible_child(form, 0);
1638    g_assert(ATK_IS_EDITABLE_TEXT(textEntry));
1639    g_assert(atk_object_get_role(ATK_OBJECT(textEntry)) == ATK_ROLE_ENTRY);
1640
1641    g_signal_connect(textEntry, "text-changed::insert",
1642                     G_CALLBACK(textChangedCb),
1643                     (gpointer)"insert");
1644    g_signal_connect(textEntry, "text-changed::delete",
1645                     G_CALLBACK(textChangedCb),
1646                     (gpointer)"delete");
1647
1648    gint pos = 0;
1649    atk_editable_text_insert_text(ATK_EDITABLE_TEXT(textEntry), "foo bar baz", 11, &pos);
1650    atk_editable_text_delete_text(ATK_EDITABLE_TEXT(textEntry), 4, 7);
1651    textInserted = FALSE;
1652    textDeleted = FALSE;
1653
1654    g_idle_add((GSourceFunc)checkTextChanges, 0);
1655
1656    g_object_unref(form);
1657    g_object_unref(textEntry);
1658    g_object_unref(webView);
1659}
1660
1661int main(int argc, char** argv)
1662{
1663    g_thread_init(0);
1664    gtk_test_init(&argc, &argv, 0);
1665
1666    g_test_bug_base("https://bugs.webkit.org/");
1667    g_test_add_func("/webkit/atk/caretOffsets", testWebkitAtkCaretOffsets);
1668    g_test_add_func("/webkit/atk/caretOffsetsAndExtranousWhiteSpaces", testWebkitAtkCaretOffsetsAndExtranousWhiteSpaces);
1669    g_test_add_func("/webkit/atk/comboBox", testWebkitAtkComboBox);
1670    g_test_add_func("/webkit/atk/embeddedObjects", testWebkitAtkEmbeddedObjects);
1671    g_test_add_func("/webkit/atk/getTextAtOffset", testWebkitAtkGetTextAtOffset);
1672    g_test_add_func("/webkit/atk/getTextAtOffsetForms", testWebkitAtkGetTextAtOffsetForms);
1673    g_test_add_func("/webkit/atk/getTextAtOffsetNewlines", testWebkitAtkGetTextAtOffsetNewlines);
1674    g_test_add_func("/webkit/atk/getTextAtOffsetTextarea", testWebkitAtkGetTextAtOffsetTextarea);
1675    g_test_add_func("/webkit/atk/getTextAtOffsetTextInput", testWebkitAtkGetTextAtOffsetTextInput);
1676    g_test_add_func("/webkit/atk/getTextAtOffsetWithSpecialCharacters", testWebkitAtkGetTextAtOffsetWithSpecialCharacters);
1677    g_test_add_func("/webkit/atk/getTextInParagraphAndBodySimple", testWebkitAtkGetTextInParagraphAndBodySimple);
1678    g_test_add_func("/webkit/atk/getTextInParagraphAndBodyModerate", testWebkitAtkGetTextInParagraphAndBodyModerate);
1679    g_test_add_func("/webkit/atk/getTextInTable", testWebkitAtkGetTextInTable);
1680    g_test_add_func("/webkit/atk/getHeadersInTable", testWebkitAtkGetHeadersInTable);
1681    g_test_add_func("/webkit/atk/textAttributes", testWebkitAtkTextAttributes);
1682    g_test_add_func("/webkit/atk/textSelections", testWebkitAtkTextSelections);
1683    g_test_add_func("/webkit/atk/getExtents", testWebkitAtkGetExtents);
1684    g_test_add_func("/webkit/atk/hypertextAndHyperlinks", testWebkitAtkHypertextAndHyperlinks);
1685    g_test_add_func("/webkit/atk/layoutAndDataTables", testWebkitAtkLayoutAndDataTables);
1686    g_test_add_func("/webkit/atk/linksWithInlineImages", testWebkitAtkLinksWithInlineImages);
1687    g_test_add_func("/webkit/atk/listsOfItems", testWebkitAtkListsOfItems);
1688    g_test_add_func("/webkit/atk/textChangedNotifications", testWebkitAtkTextChangedNotifications);
1689    return g_test_run ();
1690}
1691
1692#else
1693int main(int argc, char** argv)
1694{
1695    g_critical("You will need gtk-2.14.0 to run the unit tests. Doing nothing now.");
1696    return 0;
1697}
1698
1699#endif
1700