[ES5] Implement Object.keys
[WebKit-https.git] / WebCore / bindings / js / JSLocationCustom.cpp
1 /*
2  *  Copyright (C) 2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
4  *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reseved.
5  *  Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Lesser General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this library; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
20  *  USA
21  */
22
23 #include "config.h"
24 #include "JSLocationCustom.h"
25
26 #include "DOMWindow.h"
27 #include "Frame.h"
28 #include "FrameLoader.h"
29 #include "JSDOMBinding.h"
30 #include "JSDOMWindowCustom.h"
31 #include "KURL.h"
32 #include "Location.h"
33 #include "ScriptController.h"
34 #include <runtime/JSFunction.h>
35 #include <runtime/PrototypeFunction.h>
36
37 using namespace JSC;
38
39 namespace WebCore {
40
41 static JSValue nonCachingStaticReplaceFunctionGetter(ExecState* exec, const Identifier& propertyName, const PropertySlot&)
42 {
43     return new (exec) NativeFunctionWrapper(exec, exec->lexicalGlobalObject()->prototypeFunctionStructure(), 1, propertyName, jsLocationPrototypeFunctionReplace);
44 }
45
46 static JSValue nonCachingStaticReloadFunctionGetter(ExecState* exec, const Identifier& propertyName, const PropertySlot&)
47 {
48     return new (exec) NativeFunctionWrapper(exec, exec->lexicalGlobalObject()->prototypeFunctionStructure(), 0, propertyName, jsLocationPrototypeFunctionReload);
49 }
50
51 static JSValue nonCachingStaticAssignFunctionGetter(ExecState* exec, const Identifier& propertyName, const PropertySlot&)
52 {
53     return new (exec) NativeFunctionWrapper(exec, exec->lexicalGlobalObject()->prototypeFunctionStructure(), 1, propertyName, jsLocationPrototypeFunctionAssign);
54 }
55
56 bool JSLocation::getOwnPropertySlotDelegate(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
57 {
58     Frame* frame = impl()->frame();
59     if (!frame) {
60         slot.setUndefined();
61         return true;
62     }
63
64     // When accessing Location cross-domain, functions are always the native built-in ones.
65     // See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
66
67     // Our custom code is only needed to implement the Window cross-domain scheme, so if access is
68     // allowed, return false so the normal lookup will take place.
69     String message;
70     if (allowsAccessFromFrame(exec, frame, message))
71         return false;
72
73     // Check for the few functions that we allow, even when called cross-domain.
74     const HashEntry* entry = JSLocationPrototype::s_info.propHashTable(exec)->entry(exec, propertyName);
75     if (entry && (entry->attributes() & Function)) {
76         if (entry->function() == jsLocationPrototypeFunctionReplace) {
77             slot.setCustom(this, nonCachingStaticReplaceFunctionGetter);
78             return true;
79         } else if (entry->function() == jsLocationPrototypeFunctionReload) {
80             slot.setCustom(this, nonCachingStaticReloadFunctionGetter);
81             return true;
82         } else if (entry->function() == jsLocationPrototypeFunctionAssign) {
83             slot.setCustom(this, nonCachingStaticAssignFunctionGetter);
84             return true;
85         }
86     }
87
88     // FIXME: Other implementers of the Window cross-domain scheme (Window, History) allow toString,
89     // but for now we have decided not to, partly because it seems silly to return "[Object Location]" in
90     // such cases when normally the string form of Location would be the URL.
91
92     printErrorMessageForFrame(frame, message);
93     slot.setUndefined();
94     return true;
95 }
96
97 bool JSLocation::getOwnPropertyDescriptorDelegate(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
98 {
99     Frame* frame = impl()->frame();
100     if (!frame) {
101         descriptor.setUndefined();
102         return true;
103     }
104     
105     // When accessing Location cross-domain, functions are always the native built-in ones.
106     // See JSDOMWindow::getOwnPropertySlotDelegate for additional details.
107     
108     // Our custom code is only needed to implement the Window cross-domain scheme, so if access is
109     // allowed, return false so the normal lookup will take place.
110     String message;
111     if (allowsAccessFromFrame(exec, frame, message))
112         return false;
113     
114     // Check for the few functions that we allow, even when called cross-domain.
115     const HashEntry* entry = JSLocationPrototype::s_info.propHashTable(exec)->entry(exec, propertyName);
116     PropertySlot slot;
117     if (entry && (entry->attributes() & Function)) {
118         if (entry->function() == jsLocationPrototypeFunctionReplace) {
119             slot.setCustom(this, nonCachingStaticReplaceFunctionGetter);
120             descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
121             return true;
122         } else if (entry->function() == jsLocationPrototypeFunctionReload) {
123             slot.setCustom(this, nonCachingStaticReloadFunctionGetter);
124             descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
125             return true;
126         } else if (entry->function() == jsLocationPrototypeFunctionAssign) {
127             slot.setCustom(this, nonCachingStaticAssignFunctionGetter);
128             descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes());
129             return true;
130         }
131     }
132     
133     // FIXME: Other implementers of the Window cross-domain scheme (Window, History) allow toString,
134     // but for now we have decided not to, partly because it seems silly to return "[Object Location]" in
135     // such cases when normally the string form of Location would be the URL.
136     
137     printErrorMessageForFrame(frame, message);
138     descriptor.setUndefined();
139     return true;
140 }
141
142 bool JSLocation::putDelegate(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
143 {
144     Frame* frame = impl()->frame();
145     if (!frame)
146         return true;
147
148     if (propertyName == exec->propertyNames().toString || propertyName == exec->propertyNames().valueOf)
149         return true;
150
151     bool sameDomainAccess = allowsAccessFromFrame(exec, frame);
152
153     const HashEntry* entry = JSLocation::s_info.propHashTable(exec)->entry(exec, propertyName);
154     if (!entry) {
155         if (sameDomainAccess)
156             JSObject::put(exec, propertyName, value, slot);
157         return true;
158     }
159
160     // Cross-domain access to the location is allowed when assigning the whole location,
161     // but not when assigning the individual pieces, since that might inadvertently
162     // disclose other parts of the original location.
163     if (entry->propertyPutter() != setJSLocationHref && !sameDomainAccess)
164         return true;
165
166     return false;
167 }
168
169 bool JSLocation::deleteProperty(ExecState* exec, const Identifier& propertyName)
170 {
171     // Only allow deleting by frames in the same origin.
172     if (!allowsAccessFromFrame(exec, impl()->frame()))
173         return false;
174     return Base::deleteProperty(exec, propertyName);
175 }
176
177 void JSLocation::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
178 {
179     // Only allow the location object to enumerated by frames in the same origin.
180     if (!allowsAccessFromFrame(exec, impl()->frame()))
181         return;
182     Base::getOwnPropertyNames(exec, propertyNames);
183 }
184
185 void JSLocation::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction)
186 {
187     if (propertyName == exec->propertyNames().toString || propertyName == exec->propertyNames().valueOf)
188         return;
189     Base::defineGetter(exec, propertyName, getterFunction);
190 }
191
192 static void navigateIfAllowed(ExecState* exec, Frame* frame, const KURL& url, bool lockHistory, bool lockBackForwardList)
193 {
194     Frame* lexicalFrame = toLexicalFrame(exec);
195     if (!lexicalFrame)
196         return;
197
198     if (!protocolIsJavaScript(url) || allowsAccessFromFrame(exec, frame))
199         frame->loader()->scheduleLocationChange(url.string(), lexicalFrame->loader()->outgoingReferrer(), lockHistory, lockBackForwardList, processingUserGesture(exec));
200 }
201
202 void JSLocation::setHref(ExecState* exec, JSValue value)
203 {
204     Frame* frame = impl()->frame();
205     ASSERT(frame);
206
207     if (!shouldAllowNavigation(exec, frame))
208         return;
209
210     KURL url = completeURL(exec, value.toString(exec));
211     if (url.isNull())
212         return;
213
214     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
215 }
216
217 void JSLocation::setProtocol(ExecState* exec, JSValue value)
218 {
219     Frame* frame = impl()->frame();
220     ASSERT(frame);
221
222     KURL url = frame->loader()->url();
223     url.setProtocol(value.toString(exec));
224
225     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
226 }
227
228 void JSLocation::setHost(ExecState* exec, JSValue value)
229 {
230     Frame* frame = impl()->frame();
231     ASSERT(frame);
232
233     KURL url = frame->loader()->url();
234     url.setHostAndPort(value.toString(exec));
235
236     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
237 }
238
239 void JSLocation::setHostname(ExecState* exec, JSValue value)
240 {
241     Frame* frame = impl()->frame();
242     ASSERT(frame);
243
244     KURL url = frame->loader()->url();
245     url.setHost(value.toString(exec));
246
247     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
248 }
249
250 void JSLocation::setPort(ExecState* exec, JSValue value)
251 {
252     Frame* frame = impl()->frame();
253     ASSERT(frame);
254
255     KURL url = frame->loader()->url();
256     // FIXME: Could make this a little less ugly if String provided a toUnsignedShort function.
257     const UString& portString = value.toString(exec);
258     int port = charactersToInt(portString.data(), portString.size());
259     if (port < 0 || port > 0xFFFF)
260         port = 0;
261     url.setPort(port);
262
263     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
264 }
265
266 void JSLocation::setPathname(ExecState* exec, JSValue value)
267 {
268     Frame* frame = impl()->frame();
269     ASSERT(frame);
270
271     KURL url = frame->loader()->url();
272     url.setPath(value.toString(exec));
273
274     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
275 }
276
277 void JSLocation::setSearch(ExecState* exec, JSValue value)
278 {
279     Frame* frame = impl()->frame();
280     ASSERT(frame);
281
282     KURL url = frame->loader()->url();
283     url.setQuery(value.toString(exec));
284
285     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
286 }
287
288 void JSLocation::setHash(ExecState* exec, JSValue value)
289 {
290     Frame* frame = impl()->frame();
291     ASSERT(frame);
292
293     KURL url = frame->loader()->url();
294     String oldFragmentIdentifier = url.fragmentIdentifier();
295     String str = value.toString(exec);
296     if (str.startsWith("#"))
297         str = str.substring(1);
298     if (equalIgnoringNullity(oldFragmentIdentifier, str))
299         return;
300     url.setFragmentIdentifier(str);
301
302     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
303 }
304
305 JSValue JSLocation::replace(ExecState* exec, const ArgList& args)
306 {
307     Frame* frame = impl()->frame();
308     if (!frame)
309         return jsUndefined();
310
311     if (!shouldAllowNavigation(exec, frame))
312         return jsUndefined();
313
314     KURL url = completeURL(exec, args.at(0).toString(exec));
315     if (url.isNull())
316         return jsUndefined();
317
318     navigateIfAllowed(exec, frame, url, true, true);
319     return jsUndefined();
320 }
321
322 JSValue JSLocation::reload(ExecState* exec, const ArgList&)
323 {
324     Frame* frame = impl()->frame();
325     if (!frame || !allowsAccessFromFrame(exec, frame))
326         return jsUndefined();
327
328     if (!protocolIsJavaScript(frame->loader()->url()))
329         frame->loader()->scheduleRefresh(processingUserGesture(exec));
330     return jsUndefined();
331 }
332
333 JSValue JSLocation::assign(ExecState* exec, const ArgList& args)
334 {
335     Frame* frame = impl()->frame();
336     if (!frame)
337         return jsUndefined();
338
339     if (!shouldAllowNavigation(exec, frame))
340         return jsUndefined();
341
342     KURL url = completeURL(exec, args.at(0).toString(exec));
343     if (url.isNull())
344         return jsUndefined();
345
346     // We want a new history item if this JS was called via a user gesture
347     navigateIfAllowed(exec, frame, url, !frame->script()->anyPageIsProcessingUserGesture(), false);
348     return jsUndefined();
349 }
350
351 JSValue JSLocation::toString(ExecState* exec, const ArgList&)
352 {
353     Frame* frame = impl()->frame();
354     if (!frame || !allowsAccessFromFrame(exec, frame))
355         return jsUndefined();
356
357     return jsString(exec, impl()->toString());
358 }
359
360 bool JSLocationPrototype::putDelegate(ExecState* exec, const Identifier& propertyName, JSValue, PutPropertySlot&)
361 {
362     return (propertyName == exec->propertyNames().toString || propertyName == exec->propertyNames().valueOf);
363 }
364
365 void JSLocationPrototype::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction)
366 {
367     if (propertyName == exec->propertyNames().toString || propertyName == exec->propertyNames().valueOf)
368         return;
369     Base::defineGetter(exec, propertyName, getterFunction);
370 }
371
372 } // namespace WebCore