LayoutTestController.cpp revision f05b935882198ccf7d81675736e3aeb089c5113a
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/WKBundlePagePrivate.h>
36#include <WebKit2/WKBundleScriptWorld.h>
37#include <WebKit2/WKBundlePrivate.h>
38#include <WebKit2/WKRetainPtr.h>
39#include <WebKit2/WebKit2.h>
40
41namespace WTR {
42
43// This is lower than DumpRenderTree's timeout, to make it easier to work through the failures
44// Eventually it should be changed to match.
45const double LayoutTestController::waitToDumpWatchdogTimerInterval = 6;
46
47static JSValueRef propertyValue(JSContextRef context, JSObjectRef object, const char* propertyName)
48{
49    if (!object)
50        return 0;
51    JSRetainPtr<JSStringRef> propertyNameString(Adopt, JSStringCreateWithUTF8CString(propertyName));
52    JSValueRef exception;
53    return JSObjectGetProperty(context, object, propertyNameString.get(), &exception);
54}
55
56static JSObjectRef propertyObject(JSContextRef context, JSObjectRef object, const char* propertyName)
57{
58    JSValueRef value = propertyValue(context, object, propertyName);
59    if (!value || !JSValueIsObject(context, value))
60        return 0;
61    return const_cast<JSObjectRef>(value);
62}
63
64static JSObjectRef getElementById(WKBundleFrameRef frame, JSStringRef elementId)
65{
66    JSContextRef context = WKBundleFrameGetJavaScriptContext(frame);
67    JSObjectRef document = propertyObject(context, JSContextGetGlobalObject(context), "document");
68    if (!document)
69        return 0;
70    JSValueRef getElementById = propertyObject(context, document, "getElementById");
71    if (!getElementById || !JSValueIsObject(context, getElementById))
72        return 0;
73    JSValueRef elementIdValue = JSValueMakeString(context, elementId);
74    JSValueRef exception;
75    JSValueRef element = JSObjectCallAsFunction(context, const_cast<JSObjectRef>(getElementById), document, 1, &elementIdValue, &exception);
76    if (!element || !JSValueIsObject(context, element))
77        return 0;
78    return const_cast<JSObjectRef>(element);
79}
80
81PassRefPtr<LayoutTestController> LayoutTestController::create()
82{
83    return adoptRef(new LayoutTestController);
84}
85
86LayoutTestController::LayoutTestController()
87    : m_whatToDump(RenderTree)
88    , m_shouldDumpAllFrameScrollPositions(false)
89    , m_shouldDumpBackForwardListsForAllWindows(false)
90    , m_shouldAllowEditing(true)
91    , m_shouldCloseExtraWindows(false)
92    , m_dumpEditingCallbacks(false)
93    , m_dumpStatusCallbacks(false)
94    , m_dumpTitleChanges(false)
95    , m_waitToDump(false)
96    , m_testRepaint(false)
97    , m_testRepaintSweepHorizontally(false)
98{
99    platformInitialize();
100}
101
102LayoutTestController::~LayoutTestController()
103{
104}
105
106JSClassRef LayoutTestController::wrapperClass()
107{
108    return JSLayoutTestController::layoutTestControllerClass();
109}
110
111void LayoutTestController::display()
112{
113    // FIXME: actually implement, once we want pixel tests
114}
115
116void LayoutTestController::waitUntilDone()
117{
118    m_waitToDump = true;
119    initializeWaitToDumpWatchdogTimerIfNeeded();
120}
121
122void LayoutTestController::waitToDumpWatchdogTimerFired()
123{
124    invalidateWaitToDumpWatchdogTimer();
125    const char* message = "FAIL: Timed out waiting for notifyDone to be called\n";
126    InjectedBundle::shared().os() << message << "\n";
127    InjectedBundle::shared().done();
128}
129
130void LayoutTestController::notifyDone()
131{
132    if (m_waitToDump && !InjectedBundle::shared().page()->isLoading())
133        InjectedBundle::shared().page()->dump();
134    m_waitToDump = false;
135}
136
137unsigned LayoutTestController::numberOfActiveAnimations() const
138{
139    // FIXME: Is it OK this works only for the main frame?
140    // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
141    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
142    return WKBundleFrameGetNumberOfActiveAnimations(mainFrame);
143}
144
145bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
146{
147    // FIXME: Is it OK this works only for the main frame?
148    // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
149    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
150    return WKBundleFramePauseAnimationOnElementWithId(mainFrame, toWK(animationName).get(), toWK(elementId).get(), time);
151}
152
153void LayoutTestController::suspendAnimations()
154{
155    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
156    WKBundleFrameSuspendAnimations(mainFrame);
157}
158
159void LayoutTestController::resumeAnimations()
160{
161    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
162    WKBundleFrameResumeAnimations(mainFrame);
163}
164
165JSRetainPtr<JSStringRef> LayoutTestController::layerTreeAsText() const
166{
167    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
168    WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyLayerTreeAsText(mainFrame));
169    return toJS(text);
170}
171
172void LayoutTestController::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
173{
174    WKRetainPtr<WKStringRef> sourceWK = toWK(source);
175    WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
176
177    WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
178        (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
179        (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
180}
181
182void LayoutTestController::addUserStyleSheet(JSStringRef source, bool allFrames)
183{
184    WKRetainPtr<WKStringRef> sourceWK = toWK(source);
185    WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
186
187    WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
188        (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
189}
190
191void LayoutTestController::keepWebHistory()
192{
193    WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
194}
195
196JSValueRef LayoutTestController::computedStyleIncludingVisitedInfo(JSValueRef element)
197{
198    // FIXME: Is it OK this works only for the main frame?
199    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
200    JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
201    if (!JSValueIsObject(context, element))
202        return JSValueMakeUndefined(context);
203    JSValueRef value = WKBundleFrameGetComputedStyleIncludingVisitedInfo(mainFrame, const_cast<JSObjectRef>(element));
204    if (!value)
205        return JSValueMakeUndefined(context);
206    return value;
207}
208
209JSRetainPtr<JSStringRef> LayoutTestController::counterValueForElementById(JSStringRef elementId)
210{
211    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
212    JSObjectRef element = getElementById(mainFrame, elementId);
213    if (!element)
214        return 0;
215    WKRetainPtr<WKStringRef> value(AdoptWK, WKBundleFrameCopyCounterValue(mainFrame, const_cast<JSObjectRef>(element)));
216    return toJS(value);
217}
218
219JSRetainPtr<JSStringRef> LayoutTestController::markerTextForListItem(JSValueRef element)
220{
221    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
222    JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
223    if (!element || !JSValueIsObject(context, element))
224        return 0;
225    WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyMarkerText(mainFrame, const_cast<JSObjectRef>(element)));
226    if (WKStringIsEmpty(text.get()))
227        return 0;
228    return toJS(text);
229}
230
231void LayoutTestController::execCommand(JSStringRef name, JSStringRef argument)
232{
233    WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
234}
235
236bool LayoutTestController::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
237{
238    WKFindOptions options = 0;
239
240    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
241    JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
242    JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
243    JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
244    JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
245    if (!JSValueIsNumber(context, lengthValue))
246        return false;
247
248    size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
249    for (size_t i = 0; i < length; ++i) {
250        JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
251        if (!JSValueIsString(context, value))
252            continue;
253
254        JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
255
256        if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
257            options |= kWKFindOptionsCaseInsensitive;
258        else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
259            options |= kWKFindOptionsAtWordStarts;
260        else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
261            options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
262        else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
263            options |= kWKFindOptionsBackwards;
264        else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
265            options |= kWKFindOptionsWrapAround;
266        else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
267            // FIXME: No kWKFindOptionsStartInSelection.
268        }
269    }
270
271    return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
272}
273
274bool LayoutTestController::isCommandEnabled(JSStringRef name)
275{
276    return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
277}
278
279void LayoutTestController::setCanOpenWindows(bool)
280{
281    // It's not clear if or why any tests require opening windows be forbidden.
282    // For now, just ignore this setting, and if we find later it's needed we can add it.
283}
284
285void LayoutTestController::setXSSAuditorEnabled(bool enabled)
286{
287    WKBundleOverrideXSSAuditorEnabledForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), true);
288}
289
290unsigned LayoutTestController::windowCount()
291{
292    return InjectedBundle::shared().pageCount();
293}
294
295void LayoutTestController::clearBackForwardList()
296{
297    WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
298}
299
300// Object Creation
301
302void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
303{
304    setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
305}
306
307} // namespace WTR
308