2008-04-17 Anders Carlsson <andersca@apple.com>
[WebKit-https.git] / WebCore / bindings / js / JSDOMWindowCustom.cpp
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
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 "config.h"
21 #include "JSDOMWindow.h"
22
23 #include "AtomicString.h"
24 #include "DOMWindow.h"
25 #include "Document.h"
26 #include "ExceptionCode.h"
27 #include "Frame.h"
28 #include "FrameLoader.h"
29 #include "FrameTree.h"
30 #include "JSDOMWindowWrapper.h"
31 #include "Settings.h"
32 #include "kjs_proxy.h"
33 #include <kjs/object.h>
34
35 using namespace KJS;
36
37 namespace WebCore {
38
39 static void markDOMObjectWrapper(void* object)
40 {
41     if (!object)
42         return;
43     DOMObject* wrapper = ScriptInterpreter::getDOMObject(object);
44     if (!wrapper || wrapper->marked())
45         return;
46     wrapper->mark();
47 }
48
49 void JSDOMWindow::mark()
50 {
51     Base::mark();
52     markDOMObjectWrapper(impl()->optionalConsole());
53     markDOMObjectWrapper(impl()->optionalHistory());
54     markDOMObjectWrapper(impl()->optionalLocationbar());
55     markDOMObjectWrapper(impl()->optionalMenubar());
56     markDOMObjectWrapper(impl()->optionalNavigator());
57     markDOMObjectWrapper(impl()->optionalPersonalbar());
58     markDOMObjectWrapper(impl()->optionalScreen());
59     markDOMObjectWrapper(impl()->optionalScrollbars());
60     markDOMObjectWrapper(impl()->optionalSelection());
61     markDOMObjectWrapper(impl()->optionalStatusbar());
62     markDOMObjectWrapper(impl()->optionalToolbar());
63     markDOMObjectWrapper(impl()->optionalLocation());
64 #if ENABLE(DOM_STORAGE)
65     markDOMObjectWrapper(impl()->optionalSessionStorage());
66     markDOMObjectWrapper(impl()->optionalLocalStorage());
67 #endif
68 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
69     markDOMObjectWrapper(impl()->optionalApplicationCache());
70 #endif
71 }
72
73 bool JSDOMWindow::customGetOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
74 {
75     // When accessing a Window cross-domain, functions are always the native built-in ones, and they
76     // are not affected by properties changed on the Window or anything in its prototype chain.
77     // This is consistent with the behavior of Firefox.
78
79     const HashEntry* entry;
80
81     // We don't want any properties other than "close" and "closed" on a closed window.
82     if (!impl()->frame()) {
83         // The following code is safe for cross-domain and same domain use.
84         // It ignores any custom properties that might be set on the DOMWindow (including a custom prototype).
85         entry = s_info.propHashTable->entry(propertyName);
86         if (entry && !(entry->attributes & Function) && entry->integerValue == ClosedAttrNum) {
87             slot.setStaticEntry(this, entry, staticValueGetter<JSDOMWindow>);
88             return true;
89         }
90         entry = JSDOMWindowPrototype::s_info.propHashTable->entry(propertyName);
91         if (entry && (entry->attributes & Function) && entry->functionValue == jsDOMWindowPrototypeFunctionClose) {
92             slot.setStaticEntry(this, entry, nonCachingStaticFunctionGetter);
93             return true;
94         }
95
96         // FIXME: We should have a message here that explains why the property access/function call was
97         // not allowed. 
98         slot.setUndefined(this);
99         return true;
100     }
101
102     // We need to check for cross-domain access here without printing the generic warning message
103     // because we always allow access to some function, just different ones depending whether access
104     // is allowed.
105     bool allowsAccess = allowsAccessFromNoErrorMessage(exec);
106
107     // Look for overrides before looking at any of our own properties.
108     if (JSGlobalObject::getOwnPropertySlot(exec, propertyName, slot)) {
109         // But ignore overrides completely if this is cross-domain access.
110         if (allowsAccess)
111             return true;
112     }
113
114     // We need this code here because otherwise KJS::Window will stop the search before we even get to the
115     // prototype due to the blanket same origin (allowsAccessFrom) check at the end of getOwnPropertySlot.
116     // Also, it's important to get the implementation straight out of the DOMWindow prototype regardless of
117     // what prototype is actually set on this object.
118     entry = JSDOMWindowPrototype::s_info.propHashTable->entry(propertyName);
119     if (entry) {
120         if ((entry->attributes & Function)
121                 && (entry->functionValue == jsDOMWindowPrototypeFunctionBlur
122                     || entry->functionValue == jsDOMWindowPrototypeFunctionClose
123                     || entry->functionValue == jsDOMWindowPrototypeFunctionFocus
124 #if ENABLE(CROSS_DOCUMENT_MESSAGING)
125                     || entry->functionValue == jsDOMWindowPrototypeFunctionPostMessage
126 #endif
127                     )) {
128             if (!allowsAccess) {
129                 slot.setStaticEntry(this, entry, nonCachingStaticFunctionGetter);
130                 return true;
131             }
132         }
133     } else {
134         // Allow access to toString() cross-domain, but always Object.prototype.toString.
135         if (propertyName == exec->propertyNames().toString) {
136             if (!allowsAccess) {
137                 slot.setCustom(this, objectToStringFunctionGetter);
138                 return true;
139             }
140         }
141     }
142
143     return false;
144 }
145
146 bool JSDOMWindow::customPut(ExecState* exec, const Identifier& propertyName, JSValue* value)
147 {
148     if (!impl()->frame())
149         return true;
150
151     // We have a local override (e.g. "var location"), save time and jump directly to JSGlobalObject.
152     PropertySlot slot;
153     if (JSGlobalObject::getOwnPropertySlot(exec, propertyName, slot)) {
154         if (allowsAccessFrom(exec))
155             JSGlobalObject::put(exec, propertyName, value);
156         return true;
157     }
158
159     return false;
160 }
161
162 bool JSDOMWindow::deleteProperty(ExecState* exec, const Identifier& propertyName)
163 {
164     // Only allow deleting properties by frames in the same origin.
165     if (!allowsAccessFrom(exec))
166         return false;
167     return Base::deleteProperty(exec, propertyName);
168 }
169
170 bool JSDOMWindow::customGetPropertyNames(ExecState* exec, PropertyNameArray&)
171 {
172     // Only allow the window to enumerated by frames in the same origin.
173     if (!allowsAccessFrom(exec))
174         return true;
175     return false;
176 }
177
178 void JSDOMWindow::setLocation(ExecState* exec, JSValue* value)
179 {
180     Frame* activeFrame = toJSDOMWindow(exec->dynamicGlobalObject())->impl()->frame();
181     if (!activeFrame)
182         return;
183
184     // To avoid breaking old widgets, make "var location =" in a top-level frame create
185     // a property named "location" instead of performing a navigation (<rdar://problem/5688039>).
186     if (Settings* settings = activeFrame->settings()) {
187         if (settings->usesDashboardBackwardCompatibilityMode() && !activeFrame->tree()->parent()) {
188             if (allowsAccessFrom(exec))
189                 putDirect("location", value);
190             return;
191         }
192     }
193
194     if (!activeFrame->loader()->shouldAllowNavigation(impl()->frame()))
195         return;
196     String dstUrl = activeFrame->loader()->completeURL(value->toString(exec)).string();
197     if (!protocolIs(dstUrl, "javascript") || allowsAccessFrom(exec)) {
198         bool userGesture = activeFrame->scriptProxy()->processingUserGesture();
199         // We want a new history item if this JS was called via a user gesture
200         impl()->frame()->loader()->scheduleLocationChange(dstUrl, activeFrame->loader()->outgoingReferrer(), false, userGesture);
201     }
202 }
203
204 #if ENABLE(CROSS_DOCUMENT_MESSAGING)
205 JSValue* JSDOMWindow::postMessage(ExecState* exec, const List& args)
206 {
207     DOMWindow* window = impl();
208
209     DOMWindow* source = toJSDOMWindow(exec->dynamicGlobalObject())->impl();
210     String domain = source->frame()->loader()->url().host();
211     String uri = source->frame()->loader()->url().string();
212     String message = args[0]->toString(exec);
213
214     if (exec->hadException())
215         return jsUndefined();
216
217     window->postMessage(message, domain, uri, source);
218
219     return jsUndefined();
220 }
221 #endif
222
223 DOMWindow* toDOMWindow(JSValue* val)
224 {
225     if (val->isObject(&JSDOMWindow::s_info))
226         return static_cast<JSDOMWindow*>(val)->impl();
227     if (val->isObject(&JSDOMWindowWrapper::s_info))
228         return static_cast<JSDOMWindowWrapper*>(val)->impl();
229     return 0;
230 }
231
232 } // namespace WebCore