1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "JSHistory.h"
31
32#include "Frame.h"
33#include "JSDOMBinding.h"
34#include "SerializedScriptValue.h"
35#include <runtime/JSFunction.h>
36
37using namespace JSC;
38
39namespace WebCore {
40
41bool JSHistory::getOwnPropertySlotDelegate(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
42{
43    // When accessing History cross-domain, functions are always the native built-in ones.
44    // See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
45
46    // Our custom code is only needed to implement the Window cross-domain scheme, so if access is
47    // allowed, return false so the normal lookup will take place.
48    String message;
49    if (shouldAllowAccessToFrame(exec, impl().frame(), message))
50        return false;
51
52    // Check for the few functions that we allow, even when called cross-domain.
53    // Make these read-only / non-configurable to prevent writes via defineProperty.
54    if (propertyName == exec->propertyNames().back) {
55        slot.setCustom(this, ReadOnly | DontDelete | DontEnum, nonCachingStaticFunctionGetter<jsHistoryPrototypeFunctionBack, 0>);
56        return true;
57    }
58    if (propertyName == exec->propertyNames().forward) {
59        slot.setCustom(this, ReadOnly | DontDelete | DontEnum, nonCachingStaticFunctionGetter<jsHistoryPrototypeFunctionForward, 0>);
60        return true;
61    }
62    if (propertyName == exec->propertyNames().go) {
63        slot.setCustom(this, ReadOnly | DontDelete | DontEnum, nonCachingStaticFunctionGetter<jsHistoryPrototypeFunctionGo, 1>);
64        return true;
65    }
66    // Allow access to toString() cross-domain, but always Object.toString.
67    if (propertyName == exec->propertyNames().toString) {
68        slot.setCustom(this, ReadOnly | DontDelete | DontEnum, objectToStringFunctionGetter);
69        return true;
70    }
71
72    printErrorMessageForFrame(impl().frame(), message);
73    slot.setUndefined();
74    return true;
75}
76
77bool JSHistory::putDelegate(ExecState* exec, PropertyName, JSValue, PutPropertySlot&)
78{
79    // Only allow putting by frames in the same origin.
80    if (!shouldAllowAccessToFrame(exec, impl().frame()))
81        return true;
82    return false;
83}
84
85bool JSHistory::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
86{
87    JSHistory* thisObject = jsCast<JSHistory*>(cell);
88    // Only allow deleting by frames in the same origin.
89    if (!shouldAllowAccessToFrame(exec, thisObject->impl().frame()))
90        return false;
91    return Base::deleteProperty(thisObject, exec, propertyName);
92}
93
94bool JSHistory::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
95{
96    JSHistory* thisObject = jsCast<JSHistory*>(cell);
97    // Only allow deleting by frames in the same origin.
98    if (!shouldAllowAccessToFrame(exec, thisObject->impl().frame()))
99        return false;
100    return Base::deletePropertyByIndex(thisObject, exec, propertyName);
101}
102
103void JSHistory::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
104{
105    JSHistory* thisObject = jsCast<JSHistory*>(object);
106    // Only allow the history object to enumerated by frames in the same origin.
107    if (!shouldAllowAccessToFrame(exec, thisObject->impl().frame()))
108        return;
109    Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
110}
111
112JSValue JSHistory::state(ExecState *exec) const
113{
114    History& history = impl();
115
116    JSValue cachedValue = m_state.get();
117    if (!cachedValue.isEmpty() && !history.stateChanged())
118        return cachedValue;
119
120    RefPtr<SerializedScriptValue> serialized = history.state();
121    JSValue result = serialized ? serialized->deserialize(exec, globalObject(), 0) : jsNull();
122    const_cast<JSHistory*>(this)->m_state.set(exec->vm(), this, result);
123    return result;
124}
125
126JSValue JSHistory::pushState(ExecState* exec)
127{
128    RefPtr<SerializedScriptValue> historyState = SerializedScriptValue::create(exec, exec->argument(0), 0, 0);
129    if (exec->hadException())
130        return jsUndefined();
131
132    String title = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(1));
133    if (exec->hadException())
134        return jsUndefined();
135
136    String url;
137    if (exec->argumentCount() > 2) {
138        url = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2));
139        if (exec->hadException())
140            return jsUndefined();
141    }
142
143    ExceptionCode ec = 0;
144    impl().stateObjectAdded(historyState.release(), title, url, History::StateObjectType::Push, ec);
145    setDOMException(exec, ec);
146
147    m_state.clear();
148
149    return jsUndefined();
150}
151
152JSValue JSHistory::replaceState(ExecState* exec)
153{
154    RefPtr<SerializedScriptValue> historyState = SerializedScriptValue::create(exec, exec->argument(0), 0, 0);
155    if (exec->hadException())
156        return jsUndefined();
157
158    String title = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(1));
159    if (exec->hadException())
160        return jsUndefined();
161
162    String url;
163    if (exec->argumentCount() > 2) {
164        url = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2));
165        if (exec->hadException())
166            return jsUndefined();
167    }
168
169    ExceptionCode ec = 0;
170    impl().stateObjectAdded(historyState.release(), title, url, History::StateObjectType::Replace, ec);
171    setDOMException(exec, ec);
172
173    m_state.clear();
174
175    return jsUndefined();
176}
177
178} // namespace WebCore
179