2011-02-06 Maciej Stachowiak <mjs@apple.com>
[WebKit.git] / Tools / WebKitTestRunner / InjectedBundle / LayoutTestController.cpp
1 /*
2  * Copyright (C) 2010 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "LayoutTestController.h"
27
28 #include "InjectedBundle.h"
29 #include "InjectedBundlePage.h"
30 #include "JSLayoutTestController.h"
31 #include "StringFunctions.h"
32 #include <WebKit2/WKBundleBackForwardList.h>
33 #include <WebKit2/WKBundleFrame.h>
34 #include <WebKit2/WKBundleFramePrivate.h>
35 #include <WebKit2/WKBundleInspector.h>
36 #include <WebKit2/WKBundlePagePrivate.h>
37 #include <WebKit2/WKBundleScriptWorld.h>
38 #include <WebKit2/WKBundlePrivate.h>
39 #include <WebKit2/WKRetainPtr.h>
40 #include <WebKit2/WebKit2.h>
41 #include <wtf/HashMap.h>
42
43 namespace WTR {
44
45 // This is lower than DumpRenderTree's timeout, to make it easier to work through the failures
46 // Eventually it should be changed to match.
47 const double LayoutTestController::waitToDumpWatchdogTimerInterval = 6;
48
49 static JSValueRef propertyValue(JSContextRef context, JSObjectRef object, const char* propertyName)
50 {
51     if (!object)
52         return 0;
53     JSRetainPtr<JSStringRef> propertyNameString(Adopt, JSStringCreateWithUTF8CString(propertyName));
54     JSValueRef exception;
55     return JSObjectGetProperty(context, object, propertyNameString.get(), &exception);
56 }
57
58 static JSObjectRef propertyObject(JSContextRef context, JSObjectRef object, const char* propertyName)
59 {
60     JSValueRef value = propertyValue(context, object, propertyName);
61     if (!value || !JSValueIsObject(context, value))
62         return 0;
63     return const_cast<JSObjectRef>(value);
64 }
65
66 static JSObjectRef getElementById(WKBundleFrameRef frame, JSStringRef elementId)
67 {
68     JSContextRef context = WKBundleFrameGetJavaScriptContext(frame);
69     JSObjectRef document = propertyObject(context, JSContextGetGlobalObject(context), "document");
70     if (!document)
71         return 0;
72     JSValueRef getElementById = propertyObject(context, document, "getElementById");
73     if (!getElementById || !JSValueIsObject(context, getElementById))
74         return 0;
75     JSValueRef elementIdValue = JSValueMakeString(context, elementId);
76     JSValueRef exception;
77     JSValueRef element = JSObjectCallAsFunction(context, const_cast<JSObjectRef>(getElementById), document, 1, &elementIdValue, &exception);
78     if (!element || !JSValueIsObject(context, element))
79         return 0;
80     return const_cast<JSObjectRef>(element);
81 }
82
83 PassRefPtr<LayoutTestController> LayoutTestController::create()
84 {
85     return adoptRef(new LayoutTestController);
86 }
87
88 LayoutTestController::LayoutTestController()
89     : m_whatToDump(RenderTree)
90     , m_shouldDumpAllFrameScrollPositions(false)
91     , m_shouldDumpBackForwardListsForAllWindows(false)
92     , m_shouldAllowEditing(true)
93     , m_shouldCloseExtraWindows(false)
94     , m_dumpEditingCallbacks(false)
95     , m_dumpStatusCallbacks(false)
96     , m_dumpTitleChanges(false)
97     , m_waitToDump(false)
98     , m_testRepaint(false)
99     , m_testRepaintSweepHorizontally(false)
100     , m_willSendRequestReturnsNull(false)
101 {
102     platformInitialize();
103 }
104
105 LayoutTestController::~LayoutTestController()
106 {
107 }
108
109 JSClassRef LayoutTestController::wrapperClass()
110 {
111     return JSLayoutTestController::layoutTestControllerClass();
112 }
113
114 void LayoutTestController::display()
115 {
116     // FIXME: actually implement, once we want pixel tests
117 }
118
119 void LayoutTestController::waitUntilDone()
120 {
121     m_waitToDump = true;
122     initializeWaitToDumpWatchdogTimerIfNeeded();
123 }
124
125 void LayoutTestController::waitToDumpWatchdogTimerFired()
126 {
127     invalidateWaitToDumpWatchdogTimer();
128     const char* message = "FAIL: Timed out waiting for notifyDone to be called\n";
129     InjectedBundle::shared().os() << message << "\n";
130     InjectedBundle::shared().done();
131 }
132
133 void LayoutTestController::notifyDone()
134 {
135     if (!InjectedBundle::shared().isTestRunning())
136         return;
137
138     if (m_waitToDump && !InjectedBundle::shared().topLoadingFrame())
139         InjectedBundle::shared().page()->dump();
140
141     m_waitToDump = false;
142 }
143
144 unsigned LayoutTestController::numberOfActiveAnimations() const
145 {
146     // FIXME: Is it OK this works only for the main frame?
147     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
148     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
149     return WKBundleFrameGetNumberOfActiveAnimations(mainFrame);
150 }
151
152 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
153 {
154     // FIXME: Is it OK this works only for the main frame?
155     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
156     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
157     return WKBundleFramePauseAnimationOnElementWithId(mainFrame, toWK(animationName).get(), toWK(elementId).get(), time);
158 }
159
160 void LayoutTestController::suspendAnimations()
161 {
162     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
163     WKBundleFrameSuspendAnimations(mainFrame);
164 }
165
166 void LayoutTestController::resumeAnimations()
167 {
168     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
169     WKBundleFrameResumeAnimations(mainFrame);
170 }
171
172 JSRetainPtr<JSStringRef> LayoutTestController::layerTreeAsText() const
173 {
174     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
175     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyLayerTreeAsText(mainFrame));
176     return toJS(text);
177 }
178
179 void LayoutTestController::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
180 {
181     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
182     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
183
184     WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
185         (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
186         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
187 }
188
189 void LayoutTestController::addUserStyleSheet(JSStringRef source, bool allFrames)
190 {
191     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
192     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
193
194     WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
195         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
196 }
197
198 void LayoutTestController::keepWebHistory()
199 {
200     WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
201 }
202
203 JSValueRef LayoutTestController::computedStyleIncludingVisitedInfo(JSValueRef element)
204 {
205     // FIXME: Is it OK this works only for the main frame?
206     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
207     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
208     if (!JSValueIsObject(context, element))
209         return JSValueMakeUndefined(context);
210     JSValueRef value = WKBundleFrameGetComputedStyleIncludingVisitedInfo(mainFrame, const_cast<JSObjectRef>(element));
211     if (!value)
212         return JSValueMakeUndefined(context);
213     return value;
214 }
215
216 JSRetainPtr<JSStringRef> LayoutTestController::counterValueForElementById(JSStringRef elementId)
217 {
218     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
219     JSObjectRef element = getElementById(mainFrame, elementId);
220     if (!element)
221         return 0;
222     WKRetainPtr<WKStringRef> value(AdoptWK, WKBundleFrameCopyCounterValue(mainFrame, const_cast<JSObjectRef>(element)));
223     return toJS(value);
224 }
225
226 JSRetainPtr<JSStringRef> LayoutTestController::markerTextForListItem(JSValueRef element)
227 {
228     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
229     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
230     if (!element || !JSValueIsObject(context, element))
231         return 0;
232     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyMarkerText(mainFrame, const_cast<JSObjectRef>(element)));
233     if (WKStringIsEmpty(text.get()))
234         return 0;
235     return toJS(text);
236 }
237
238 void LayoutTestController::execCommand(JSStringRef name, JSStringRef argument)
239 {
240     WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
241 }
242
243 bool LayoutTestController::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
244 {
245     WKFindOptions options = 0;
246
247     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
248     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
249     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
250     JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
251     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
252     if (!JSValueIsNumber(context, lengthValue))
253         return false;
254
255     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
256     for (size_t i = 0; i < length; ++i) {
257         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
258         if (!JSValueIsString(context, value))
259             continue;
260
261         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
262
263         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
264             options |= kWKFindOptionsCaseInsensitive;
265         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
266             options |= kWKFindOptionsAtWordStarts;
267         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
268             options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
269         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
270             options |= kWKFindOptionsBackwards;
271         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
272             options |= kWKFindOptionsWrapAround;
273         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
274             // FIXME: No kWKFindOptionsStartInSelection.
275         }
276     }
277
278     return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
279 }
280
281 bool LayoutTestController::isCommandEnabled(JSStringRef name)
282 {
283     return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
284 }
285
286 void LayoutTestController::setCanOpenWindows(bool)
287 {
288     // It's not clear if or why any tests require opening windows be forbidden.
289     // For now, just ignore this setting, and if we find later it's needed we can add it.
290 }
291
292 void LayoutTestController::setXSSAuditorEnabled(bool enabled)
293 {
294     WKBundleOverrideXSSAuditorEnabledForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), true);
295 }
296
297 unsigned LayoutTestController::windowCount()
298 {
299     return InjectedBundle::shared().pageCount();
300 }
301
302 void LayoutTestController::clearBackForwardList()
303 {
304     WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
305 }
306
307 // Object Creation
308
309 void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
310 {
311     setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
312 }
313
314 void LayoutTestController::showWebInspector()
315 {
316     WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
317 }
318
319 void LayoutTestController::closeWebInspector()
320 {
321     WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
322 }
323
324 void LayoutTestController::evaluateInWebInspector(long callID, JSStringRef script)
325 {
326     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
327     WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get());
328 }
329
330 void LayoutTestController::setTimelineProfilingEnabled(bool enabled)
331 {
332     WKBundleInspectorSetPageProfilingEnabled(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), enabled);
333 }
334
335 typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap;
336 static WorldMap& worldMap()
337 {
338     static WorldMap& map = *new WorldMap;
339     return map;
340 }
341
342 unsigned LayoutTestController::worldIDForWorld(WKBundleScriptWorldRef world)
343 {
344     WorldMap::const_iterator end = worldMap().end();
345     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
346         if (it->second == world)
347             return it->first;
348     }
349
350     return 0;
351 }
352
353 void LayoutTestController::evaluateScriptInIsolatedWorld(JSContextRef context, unsigned worldID, JSStringRef script)
354 {
355     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
356     // that is created once and cached forever.
357     WKRetainPtr<WKBundleScriptWorldRef> world;
358     if (!worldID)
359         world.adopt(WKBundleScriptWorldCreateWorld());
360     else {
361         WKRetainPtr<WKBundleScriptWorldRef>& worldSlot = worldMap().add(worldID, 0).first->second;
362         if (!worldSlot)
363             worldSlot.adopt(WKBundleScriptWorldCreateWorld());
364         world = worldSlot;
365     }
366
367     WKBundleFrameRef frame = WKBundleFrameForJavaScriptContext(context);
368     if (!frame)
369         frame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
370
371     JSGlobalContextRef jsContext = WKBundleFrameGetJavaScriptContextForWorld(frame, world.get());
372     JSEvaluateScript(jsContext, script, 0, 0, 0, 0); 
373 }
374
375 void LayoutTestController::setPOSIXLocale(JSStringRef locale)
376 {
377     char localeBuf[32];
378     JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf));
379     setlocale(LC_ALL, localeBuf);
380 }
381
382 } // namespace WTR