/* * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "JSDOMWindowCustom.h" #include "AtomicString.h" #include "Chrome.h" #include "DOMWindow.h" #include "Document.h" #include "ExceptionCode.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoadRequest.h" #include "FrameLoader.h" #include "FrameTree.h" #include "FrameView.h" #include "HTMLCollection.h" #include "HTMLDocument.h" #include "History.h" #include "JSAudioConstructor.h" #include "JSDOMWindowShell.h" #include "JSEvent.h" #include "JSEventListener.h" #include "JSEventSource.h" #include "JSHTMLCollection.h" #include "JSHistory.h" #include "JSImageConstructor.h" #include "JSLocation.h" #include "JSMessageChannel.h" #include "JSMessagePort.h" #include "JSMessagePortCustom.h" #include "JSOptionConstructor.h" #if ENABLE(SHARED_WORKERS) #include "JSSharedWorker.h" #endif #if ENABLE(3D_CANVAS) #include "JSArrayBuffer.h" #include "JSInt8Array.h" #include "JSUint8Array.h" #include "JSInt32Array.h" #include "JSUint32Array.h" #include "JSInt16Array.h" #include "JSUint16Array.h" #include "JSFloat32Array.h" #endif #include "JSWebKitCSSMatrix.h" #include "JSWebKitPoint.h" #if ENABLE(WEB_SOCKETS) #include "JSWebSocket.h" #endif #include "JSWorker.h" #include "JSXMLHttpRequest.h" #include "JSXSLTProcessor.h" #include "Location.h" #include "MediaPlayer.h" #include "MessagePort.h" #include "NotificationCenter.h" #include "Page.h" #include "PlatformScreen.h" #include "RegisteredEventListener.h" #include "ScheduledAction.h" #include "ScriptController.h" #include "SerializedScriptValue.h" #include "Settings.h" #include "SharedWorkerRepository.h" #include "WindowFeatures.h" #include #include #include #include using namespace JSC; namespace WebCore { void JSDOMWindow::markChildren(MarkStack& markStack) { Base::markChildren(markStack); impl()->markJSEventListeners(markStack); JSGlobalData& globalData = *Heap::heap(this)->globalData(); markDOMObjectWrapper(markStack, globalData, impl()->optionalConsole()); markDOMObjectWrapper(markStack, globalData, impl()->optionalHistory()); markDOMObjectWrapper(markStack, globalData, impl()->optionalLocationbar()); markDOMObjectWrapper(markStack, globalData, impl()->optionalMenubar()); markDOMObjectWrapper(markStack, globalData, impl()->optionalNavigator()); markDOMObjectWrapper(markStack, globalData, impl()->optionalPersonalbar()); markDOMObjectWrapper(markStack, globalData, impl()->optionalScreen()); markDOMObjectWrapper(markStack, globalData, impl()->optionalScrollbars()); markDOMObjectWrapper(markStack, globalData, impl()->optionalSelection()); markDOMObjectWrapper(markStack, globalData, impl()->optionalStatusbar()); markDOMObjectWrapper(markStack, globalData, impl()->optionalToolbar()); markDOMObjectWrapper(markStack, globalData, impl()->optionalLocation()); markDOMObjectWrapper(markStack, globalData, impl()->optionalMedia()); #if ENABLE(DOM_STORAGE) markDOMObjectWrapper(markStack, globalData, impl()->optionalSessionStorage()); markDOMObjectWrapper(markStack, globalData, impl()->optionalLocalStorage()); #endif #if ENABLE(OFFLINE_WEB_APPLICATIONS) markDOMObjectWrapper(markStack, globalData, impl()->optionalApplicationCache()); #endif } template JSValue nonCachingStaticFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName) { return new (exec) NativeFunctionWrapper(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->prototypeFunctionStructure(), length, propertyName, nativeFunction); } static JSValue childFrameGetter(ExecState* exec, JSValue slotBase, const Identifier& propertyName) { return toJS(exec, static_cast(asObject(slotBase))->impl()->frame()->tree()->child(identifierToAtomicString(propertyName))->domWindow()); } static JSValue indexGetter(ExecState* exec, JSValue slotBase, unsigned index) { return toJS(exec, static_cast(asObject(slotBase))->impl()->frame()->tree()->child(index)->domWindow()); } static JSValue namedItemGetter(ExecState* exec, JSValue slotBase, const Identifier& propertyName) { JSDOMWindowBase* thisObj = static_cast(asObject(slotBase)); Document* document = thisObj->impl()->frame()->document(); ASSERT(thisObj->allowsAccessFrom(exec)); ASSERT(document); ASSERT(document->isHTMLDocument()); RefPtr collection = document->windowNamedItems(identifierToString(propertyName)); if (collection->length() == 1) return toJS(exec, collection->firstItem()); return toJS(exec, collection.get()); } bool JSDOMWindow::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) { // When accessing a Window cross-domain, functions are always the native built-in ones, and they // are not affected by properties changed on the Window or anything in its prototype chain. // This is consistent with the behavior of Firefox. const HashEntry* entry; // We don't want any properties other than "close" and "closed" on a closed window. if (!impl()->frame()) { // The following code is safe for cross-domain and same domain use. // It ignores any custom properties that might be set on the DOMWindow (including a custom prototype). entry = s_info.propHashTable(exec)->entry(exec, propertyName); if (entry && !(entry->attributes() & Function) && entry->propertyGetter() == jsDOMWindowClosed) { slot.setCustom(this, entry->propertyGetter()); return true; } entry = JSDOMWindowPrototype::s_info.propHashTable(exec)->entry(exec, propertyName); if (entry && (entry->attributes() & Function) && entry->function() == jsDOMWindowPrototypeFunctionClose) { slot.setCustom(this, nonCachingStaticFunctionGetter); return true; } // FIXME: We should have a message here that explains why the property access/function call was // not allowed. slot.setUndefined(); return true; } // We need to check for cross-domain access here without printing the generic warning message // because we always allow access to some function, just different ones depending whether access // is allowed. String errorMessage; bool allowsAccess = allowsAccessFrom(exec, errorMessage); // Look for overrides before looking at any of our own properties, but ignore overrides completely // if this is cross-domain access. if (allowsAccess && JSGlobalObject::getOwnPropertySlot(exec, propertyName, slot)) return true; // We need this code here because otherwise JSDOMWindowBase will stop the search before we even get to the // prototype due to the blanket same origin (allowsAccessFrom) check at the end of getOwnPropertySlot. // Also, it's important to get the implementation straight out of the DOMWindow prototype regardless of // what prototype is actually set on this object. entry = JSDOMWindowPrototype::s_info.propHashTable(exec)->entry(exec, propertyName); if (entry) { if (entry->attributes() & Function) { if (entry->function() == jsDOMWindowPrototypeFunctionBlur) { if (!allowsAccess) { slot.setCustom(this, nonCachingStaticFunctionGetter); return true; } } else if (entry->function() == jsDOMWindowPrototypeFunctionClose) { if (!allowsAccess) { slot.setCustom(this, nonCachingStaticFunctionGetter); return true; } } else if (entry->function() == jsDOMWindowPrototypeFunctionFocus) { if (!allowsAccess) { slot.setCustom(this, nonCachingStaticFunctionGetter); return true; } } else if (entry->function() == jsDOMWindowPrototypeFunctionPostMessage) { if (!allowsAccess) { slot.setCustom(this, nonCachingStaticFunctionGetter); return true; } } else if (entry->function() == jsDOMWindowPrototypeFunctionShowModalDialog) { if (!DOMWindow::canShowModalDialog(impl()->frame())) { slot.setUndefined(); return true; } } } } else { // Allow access to toString() cross-domain, but always Object.prototype.toString. if (propertyName == exec->propertyNames().toString) { if (!allowsAccess) { slot.setCustom(this, objectToStringFunctionGetter); return true; } } } entry = JSDOMWindow::s_info.propHashTable(exec)->entry(exec, propertyName); if (entry) { slot.setCustom(this, entry->propertyGetter()); return true; } // Check for child frames by name before built-in properties to // match Mozilla. This does not match IE, but some sites end up // naming frames things that conflict with window properties that // are in Moz but not IE. Since we have some of these, we have to do // it the Moz way. if (impl()->frame()->tree()->child(identifierToAtomicString(propertyName))) { slot.setCustom(this, childFrameGetter); return true; } // Do prototype lookup early so that functions and attributes in the prototype can have // precedence over the index and name getters. JSValue proto = prototype(); if (proto.isObject()) { if (asObject(proto)->getPropertySlot(exec, propertyName, slot)) { if (!allowsAccess) { printErrorMessage(errorMessage); slot.setUndefined(); } return true; } } // FIXME: Search the whole frame hierarchy somewhere around here. // We need to test the correct priority order. // allow window[1] or parent[1] etc. (#56983) bool ok; unsigned i = propertyName.toArrayIndex(&ok); if (ok && i < impl()->frame()->tree()->childCount()) { slot.setCustomIndex(this, i, indexGetter); return true; } if (!allowsAccess) { printErrorMessage(errorMessage); slot.setUndefined(); return true; } // Allow shortcuts like 'Image1' instead of document.images.Image1 Document* document = impl()->frame()->document(); if (document->isHTMLDocument()) { AtomicStringImpl* atomicPropertyName = findAtomicString(propertyName); if (atomicPropertyName && (static_cast(document)->hasNamedItem(atomicPropertyName) || document->hasElementWithId(atomicPropertyName))) { slot.setCustom(this, namedItemGetter); return true; } } return Base::getOwnPropertySlot(exec, propertyName, slot); } bool JSDOMWindow::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) { // Never allow cross-domain getOwnPropertyDescriptor if (!allowsAccessFrom(exec)) return false; const HashEntry* entry; // We don't want any properties other than "close" and "closed" on a closed window. if (!impl()->frame()) { // The following code is safe for cross-domain and same domain use. // It ignores any custom properties that might be set on the DOMWindow (including a custom prototype). entry = s_info.propHashTable(exec)->entry(exec, propertyName); if (entry && !(entry->attributes() & Function) && entry->propertyGetter() == jsDOMWindowClosed) { descriptor.setDescriptor(jsBoolean(true), ReadOnly | DontDelete | DontEnum); return true; } entry = JSDOMWindowPrototype::s_info.propHashTable(exec)->entry(exec, propertyName); if (entry && (entry->attributes() & Function) && entry->function() == jsDOMWindowPrototypeFunctionClose) { PropertySlot slot; slot.setCustom(this, nonCachingStaticFunctionGetter); descriptor.setDescriptor(slot.getValue(exec, propertyName), ReadOnly | DontDelete | DontEnum); return true; } descriptor.setUndefined(); return true; } entry = JSDOMWindow::s_info.propHashTable(exec)->entry(exec, propertyName); if (entry) { PropertySlot slot; slot.setCustom(this, entry->propertyGetter()); descriptor.setDescriptor(slot.getValue(exec, propertyName), entry->attributes()); return true; } // Check for child frames by name before built-in properties to // match Mozilla. This does not match IE, but some sites end up // naming frames things that conflict with window properties that // are in Moz but not IE. Since we have some of these, we have to do // it the Moz way. if (impl()->frame()->tree()->child(identifierToAtomicString(propertyName))) { PropertySlot slot; slot.setCustom(this, childFrameGetter); descriptor.setDescriptor(slot.getValue(exec, propertyName), ReadOnly | DontDelete | DontEnum); return true; } bool ok; unsigned i = propertyName.toArrayIndex(&ok); if (ok && i < impl()->frame()->tree()->childCount()) { PropertySlot slot; slot.setCustomIndex(this, i, indexGetter); descriptor.setDescriptor(slot.getValue(exec, propertyName), ReadOnly | DontDelete | DontEnum); return true; } // Allow shortcuts like 'Image1' instead of document.images.Image1 Document* document = impl()->frame()->document(); if (document->isHTMLDocument()) { AtomicStringImpl* atomicPropertyName = findAtomicString(propertyName); if (atomicPropertyName && (static_cast(document)->hasNamedItem(atomicPropertyName) || document->hasElementWithId(atomicPropertyName))) { PropertySlot slot; slot.setCustom(this, namedItemGetter); descriptor.setDescriptor(slot.getValue(exec, propertyName), ReadOnly | DontDelete | DontEnum); return true; } } return Base::getOwnPropertyDescriptor(exec, propertyName, descriptor); } void JSDOMWindow::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot) { if (!impl()->frame()) return; // Optimization: access JavaScript global variables directly before involving the DOM. if (JSGlobalObject::hasOwnPropertyForWrite(exec, propertyName)) { if (allowsAccessFrom(exec)) JSGlobalObject::put(exec, propertyName, value, slot); return; } if (lookupPut(exec, propertyName, value, s_info.propHashTable(exec), this)) return; if (allowsAccessFrom(exec)) Base::put(exec, propertyName, value, slot); } bool JSDOMWindow::deleteProperty(ExecState* exec, const Identifier& propertyName) { // Only allow deleting properties by frames in the same origin. if (!allowsAccessFrom(exec)) return false; return Base::deleteProperty(exec, propertyName); } void JSDOMWindow::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { // Only allow the window to enumerated by frames in the same origin. if (!allowsAccessFrom(exec)) return; Base::getPropertyNames(exec, propertyNames, mode); } void JSDOMWindow::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { // Only allow the window to enumerated by frames in the same origin. if (!allowsAccessFrom(exec)) return; Base::getOwnPropertyNames(exec, propertyNames, mode); } void JSDOMWindow::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction, unsigned attributes) { // Only allow defining getters by frames in the same origin. if (!allowsAccessFrom(exec)) return; // Don't allow shadowing location using defineGetter. if (propertyName == "location") return; Base::defineGetter(exec, propertyName, getterFunction, attributes); } void JSDOMWindow::defineSetter(ExecState* exec, const Identifier& propertyName, JSObject* setterFunction, unsigned attributes) { // Only allow defining setters by frames in the same origin. if (!allowsAccessFrom(exec)) return; Base::defineSetter(exec, propertyName, setterFunction, attributes); } bool JSDOMWindow::defineOwnProperty(JSC::ExecState* exec, const JSC::Identifier& propertyName, JSC::PropertyDescriptor& descriptor, bool shouldThrow) { // Only allow defining properties in this way by frames in the same origin, as it allows setters to be introduced. if (!allowsAccessFrom(exec)) return false; return Base::defineOwnProperty(exec, propertyName, descriptor, shouldThrow); } JSValue JSDOMWindow::lookupGetter(ExecState* exec, const Identifier& propertyName) { // Only allow looking-up getters by frames in the same origin. if (!allowsAccessFrom(exec)) return jsUndefined(); return Base::lookupGetter(exec, propertyName); } JSValue JSDOMWindow::lookupSetter(ExecState* exec, const Identifier& propertyName) { // Only allow looking-up setters by frames in the same origin. if (!allowsAccessFrom(exec)) return jsUndefined(); return Base::lookupSetter(exec, propertyName); } // Custom Attributes JSValue JSDOMWindow::history(ExecState* exec) const { History* history = impl()->history(); if (DOMObject* wrapper = getCachedDOMObjectWrapper(exec, history)) return wrapper; JSDOMWindow* window = const_cast(this); JSHistory* jsHistory = new (exec) JSHistory(getDOMStructure(exec, window), window, history); cacheDOMObjectWrapper(exec, history, jsHistory); return jsHistory; } JSValue JSDOMWindow::location(ExecState* exec) const { Location* location = impl()->location(); if (DOMObject* wrapper = getCachedDOMObjectWrapper(exec, location)) return wrapper; JSDOMWindow* window = const_cast(this); JSLocation* jsLocation = new (exec) JSLocation(getDOMStructure(exec, window), window, location); cacheDOMObjectWrapper(exec, location, jsLocation); return jsLocation; } void JSDOMWindow::setLocation(ExecState* exec, JSValue value) { Frame* lexicalFrame = toLexicalFrame(exec); if (!lexicalFrame) return; #if ENABLE(DASHBOARD_SUPPORT) // To avoid breaking old widgets, make "var location =" in a top-level frame create // a property named "location" instead of performing a navigation (). if (Settings* settings = lexicalFrame->settings()) { if (settings->usesDashboardBackwardCompatibilityMode() && !lexicalFrame->tree()->parent()) { if (allowsAccessFrom(exec)) putDirect(Identifier(exec, "location"), value); return; } } #endif Frame* frame = impl()->frame(); ASSERT(frame); KURL url = completeURL(exec, ustringToString(value.toString(exec))); if (url.isNull()) return; if (!shouldAllowNavigation(exec, frame)) return; if (!protocolIsJavaScript(url) || allowsAccessFrom(exec)) { // We want a new history item if this JS was called via a user gesture frame->redirectScheduler()->scheduleLocationChange(url, lexicalFrame->loader()->outgoingReferrer(), !lexicalFrame->script()->anyPageIsProcessingUserGesture(), false, processingUserGesture(exec)); } } JSValue JSDOMWindow::crypto(ExecState*) const { return jsUndefined(); } JSValue JSDOMWindow::event(ExecState* exec) const { Event* event = currentEvent(); if (!event) return jsUndefined(); return toJS(exec, event); } #if ENABLE(EVENTSOURCE) JSValue JSDOMWindow::eventSource(ExecState* exec) const { return getDOMConstructor(exec, this); } #endif JSValue JSDOMWindow::image(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::option(ExecState* exec) const { return getDOMConstructor(exec, this); } #if ENABLE(VIDEO) JSValue JSDOMWindow::audio(ExecState* exec) const { if (!MediaPlayer::isAvailable()) return jsUndefined(); return getDOMConstructor(exec, this); } #endif JSValue JSDOMWindow::webKitPoint(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webKitCSSMatrix(ExecState* exec) const { return getDOMConstructor(exec, this); } #if ENABLE(3D_CANVAS) JSValue JSDOMWindow::arrayBuffer(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::int8Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::uint8Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::int32Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::uint32Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::int16Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::uint16Array(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::float32Array(ExecState* exec) const { return getDOMConstructor(exec, this); } // Temporary aliases to keep current WebGL content working during transition period to TypedArray spec. // To be removed before WebGL spec is finalized. (FIXME) JSValue JSDOMWindow::webGLArrayBuffer(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLByteArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLUnsignedByteArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLIntArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLUnsignedIntArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLShortArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLUnsignedShortArray(ExecState* exec) const { return getDOMConstructor(exec, this); } JSValue JSDOMWindow::webGLFloatArray(ExecState* exec) const { return getDOMConstructor(exec, this); } #endif JSValue JSDOMWindow::xmlHttpRequest(ExecState* exec) const { return getDOMConstructor(exec, this); } #if ENABLE(XSLT) JSValue JSDOMWindow::xsltProcessor(ExecState* exec) const { return getDOMConstructor(exec, this); } #endif #if ENABLE(CHANNEL_MESSAGING) JSValue JSDOMWindow::messageChannel(ExecState* exec) const { return getDOMConstructor(exec, this); } #endif #if ENABLE(WORKERS) JSValue JSDOMWindow::worker(ExecState* exec) const { return getDOMConstructor(exec, this); } #endif #if ENABLE(SHARED_WORKERS) JSValue JSDOMWindow::sharedWorker(ExecState* exec) const { if (SharedWorkerRepository::isAvailable()) return getDOMConstructor(exec, this); return jsUndefined(); } #endif #if ENABLE(WEB_SOCKETS) JSValue JSDOMWindow::webSocket(ExecState* exec) const { Frame* frame = impl()->frame(); if (!frame) return jsUndefined(); Settings* settings = frame->settings(); if (!settings) return jsUndefined(); return getDOMConstructor(exec, this); } #endif // Custom functions // Helper for window.open() and window.showModalDialog() static Frame* createWindow(ExecState* exec, Frame* lexicalFrame, Frame* dynamicFrame, Frame* openerFrame, const String& url, const String& frameName, const WindowFeatures& windowFeatures, JSValue dialogArgs) { ASSERT(lexicalFrame); ASSERT(dynamicFrame); ResourceRequest request; // For whatever reason, Firefox uses the dynamicGlobalObject to determine // the outgoingReferrer. We replicate that behavior here. String referrer = dynamicFrame->loader()->outgoingReferrer(); request.setHTTPReferrer(referrer); FrameLoader::addHTTPOriginIfNeeded(request, dynamicFrame->loader()->outgoingOrigin()); FrameLoadRequest frameRequest(request, frameName); // FIXME: It's much better for client API if a new window starts with a URL, here where we // know what URL we are going to open. Unfortunately, this code passes the empty string // for the URL, but there's a reason for that. Before loading we have to set up the opener, // openedByDOM, and dialogArguments values. Also, to decide whether to use the URL we currently // do an allowsAccessFrom call using the window we create, which can't be done before creating it. // We'd have to resolve all those issues to pass the URL instead of "". bool created; // We pass in the opener frame here so it can be used for looking up the frame name, in case the active frame // is different from the opener frame, and the name references a frame relative to the opener frame, for example // "_self" or "_parent". Frame* newFrame = lexicalFrame->loader()->createWindow(openerFrame->loader(), frameRequest, windowFeatures, created); if (!newFrame) return 0; newFrame->loader()->setOpener(openerFrame); newFrame->page()->setOpenedByDOM(); // FIXME: If a window is created from an isolated world, what are the consequences of this? 'dialogArguments' only appears back in the normal world? JSDOMWindow* newWindow = toJSDOMWindow(newFrame, normalWorld(exec->globalData())); if (dialogArgs) newWindow->putDirect(Identifier(exec, "dialogArguments"), dialogArgs); if (!protocolIsJavaScript(url) || newWindow->allowsAccessFrom(exec)) { KURL completedURL = url.isEmpty() ? KURL(ParsedURLString, "") : completeURL(exec, url); bool userGesture = processingUserGesture(exec); if (created) newFrame->loader()->changeLocation(completedURL, referrer, false, false, userGesture); else if (!url.isEmpty()) newFrame->redirectScheduler()->scheduleLocationChange(completedURL.string(), referrer, !lexicalFrame->script()->anyPageIsProcessingUserGesture(), false, userGesture); } return newFrame; } static bool domWindowAllowPopUp(Frame* activeFrame, ExecState* exec) { ASSERT(activeFrame); if (activeFrame->script()->processingUserGesture(currentWorld(exec))) return true; return DOMWindow::allowPopUp(activeFrame); } JSValue JSDOMWindow::open(ExecState* exec) { String urlString = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(0)); AtomicString frameName = exec->argument(1).isUndefinedOrNull() ? "_blank" : ustringToAtomicString(exec->argument(1).toString(exec)); WindowFeatures windowFeatures(valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2))); Frame* frame = impl()->frame(); if (!frame) return jsUndefined(); Frame* lexicalFrame = toLexicalFrame(exec); if (!lexicalFrame) return jsUndefined(); Frame* dynamicFrame = toDynamicFrame(exec); if (!dynamicFrame) return jsUndefined(); Page* page = frame->page(); // Because FrameTree::find() returns true for empty strings, we must check for empty framenames. // Otherwise, illegitimate window.open() calls with no name will pass right through the popup blocker. if (!domWindowAllowPopUp(dynamicFrame, exec) && (frameName.isEmpty() || !frame->tree()->find(frameName))) return jsUndefined(); // Get the target frame for the special cases of _top and _parent. In those // cases, we can schedule a location change right now and return early. bool topOrParent = false; if (frameName == "_top") { frame = frame->tree()->top(); topOrParent = true; } else if (frameName == "_parent") { if (Frame* parent = frame->tree()->parent()) frame = parent; topOrParent = true; } if (topOrParent) { String completedURL; if (!urlString.isEmpty()) completedURL = completeURL(exec, urlString).string(); if (!shouldAllowNavigation(exec, frame)) return jsUndefined(); const JSDOMWindow* targetedWindow = toJSDOMWindow(frame, currentWorld(exec)); if (!completedURL.isEmpty() && (!protocolIsJavaScript(completedURL) || (targetedWindow && targetedWindow->allowsAccessFrom(exec)))) { bool userGesture = processingUserGesture(exec); // For whatever reason, Firefox uses the dynamicGlobalObject to // determine the outgoingReferrer. We replicate that behavior // here. String referrer = dynamicFrame->loader()->outgoingReferrer(); frame->redirectScheduler()->scheduleLocationChange(completedURL, referrer, !lexicalFrame->script()->anyPageIsProcessingUserGesture(), false, userGesture); } return toJS(exec, frame->domWindow()); } // In the case of a named frame or a new window, we'll use the createWindow() helper FloatRect windowRect(windowFeatures.xSet ? windowFeatures.x : 0, windowFeatures.ySet ? windowFeatures.y : 0, windowFeatures.widthSet ? windowFeatures.width : 0, windowFeatures.heightSet ? windowFeatures.height : 0); DOMWindow::adjustWindowRect(screenAvailableRect(page ? page->mainFrame()->view() : 0), windowRect, windowRect); windowFeatures.x = windowRect.x(); windowFeatures.y = windowRect.y(); windowFeatures.height = windowRect.height(); windowFeatures.width = windowRect.width(); frame = createWindow(exec, lexicalFrame, dynamicFrame, frame, urlString, frameName, windowFeatures, JSValue()); if (!frame) return jsUndefined(); return toJS(exec, frame->domWindow()); } JSValue JSDOMWindow::showModalDialog(ExecState* exec) { String url = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(0)); JSValue dialogArgs = exec->argument(1); String featureArgs = valueToStringWithUndefinedOrNullCheck(exec, exec->argument(2)); Frame* frame = impl()->frame(); if (!frame) return jsUndefined(); Frame* lexicalFrame = toLexicalFrame(exec); if (!lexicalFrame) return jsUndefined(); Frame* dynamicFrame = toDynamicFrame(exec); if (!dynamicFrame) return jsUndefined(); if (!DOMWindow::canShowModalDialogNow(frame) || !domWindowAllowPopUp(dynamicFrame, exec)) return jsUndefined(); HashMap features; DOMWindow::parseModalDialogFeatures(featureArgs, features); const bool trusted = false; // The following features from Microsoft's documentation are not implemented: // - default font settings // - width, height, left, and top specified in units other than "px" // - edge (sunken or raised, default is raised) // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?) // - unadorned: trusted && boolFeature(features, "unadorned"); FloatRect screenRect = screenAvailableRect(frame->view()); WindowFeatures wargs; wargs.width = WindowFeatures::floatFeature(features, "dialogwidth", 100, screenRect.width(), 620); // default here came from frame size of dialog in MacIE wargs.widthSet = true; wargs.height = WindowFeatures::floatFeature(features, "dialogheight", 100, screenRect.height(), 450); // default here came from frame size of dialog in MacIE wargs.heightSet = true; wargs.x = WindowFeatures::floatFeature(features, "dialogleft", screenRect.x(), screenRect.right() - wargs.width, -1); wargs.xSet = wargs.x > 0; wargs.y = WindowFeatures::floatFeature(features, "dialogtop", screenRect.y(), screenRect.bottom() - wargs.height, -1); wargs.ySet = wargs.y > 0; if (WindowFeatures::boolFeature(features, "center", true)) { if (!wargs.xSet) { wargs.x = screenRect.x() + (screenRect.width() - wargs.width) / 2; wargs.xSet = true; } if (!wargs.ySet) { wargs.y = screenRect.y() + (screenRect.height() - wargs.height) / 2; wargs.ySet = true; } } wargs.dialog = true; wargs.resizable = WindowFeatures::boolFeature(features, "resizable"); wargs.scrollbarsVisible = WindowFeatures::boolFeature(features, "scroll", true); wargs.statusBarVisible = WindowFeatures::boolFeature(features, "status", !trusted); wargs.menuBarVisible = false; wargs.toolBarVisible = false; wargs.locationBarVisible = false; wargs.fullscreen = false; Frame* dialogFrame = createWindow(exec, lexicalFrame, dynamicFrame, frame, url, "", wargs, dialogArgs); if (!dialogFrame) return jsUndefined(); JSDOMWindow* dialogWindow = toJSDOMWindow(dialogFrame, currentWorld(exec)); dialogFrame->page()->chrome()->runModal(); Identifier returnValue(exec, "returnValue"); if (dialogWindow->allowsAccessFromNoErrorMessage(exec)) { PropertySlot slot; // This is safe, we have already performed the origin security check and we are // not interested in any of the DOM properties of the window. if (dialogWindow->JSGlobalObject::getOwnPropertySlot(exec, returnValue, slot)) return slot.getValue(exec, returnValue); } return jsUndefined(); } JSValue JSDOMWindow::postMessage(ExecState* exec) { DOMWindow* window = impl(); DOMWindow* source = asJSDOMWindow(exec->lexicalGlobalObject())->impl(); PassRefPtr message = SerializedScriptValue::create(exec, exec->argument(0)); if (exec->hadException()) return jsUndefined(); MessagePortArray messagePorts; if (exec->argumentCount() > 2) fillMessagePortArray(exec, exec->argument(1), messagePorts); if (exec->hadException()) return jsUndefined(); String targetOrigin = valueToStringWithUndefinedOrNullCheck(exec, exec->argument((exec->argumentCount() == 2) ? 1 : 2)); if (exec->hadException()) return jsUndefined(); ExceptionCode ec = 0; window->postMessage(message, &messagePorts, targetOrigin, source, ec); setDOMException(exec, ec); return jsUndefined(); } JSValue JSDOMWindow::setTimeout(ExecState* exec) { OwnPtr action = ScheduledAction::create(exec, currentWorld(exec)); if (exec->hadException()) return jsUndefined(); int delay = exec->argument(1).toInt32(exec); ExceptionCode ec = 0; int result = impl()->setTimeout(action.release(), delay, ec); setDOMException(exec, ec); return jsNumber(exec, result); } JSValue JSDOMWindow::setInterval(ExecState* exec) { OwnPtr action = ScheduledAction::create(exec, currentWorld(exec)); if (exec->hadException()) return jsUndefined(); int delay = exec->argument(1).toInt32(exec); ExceptionCode ec = 0; int result = impl()->setInterval(action.release(), delay, ec); setDOMException(exec, ec); return jsNumber(exec, result); } JSValue JSDOMWindow::addEventListener(ExecState* exec) { Frame* frame = impl()->frame(); if (!frame) return jsUndefined(); JSValue listener = exec->argument(1); if (!listener.isObject()) return jsUndefined(); impl()->addEventListener(ustringToAtomicString(exec->argument(0).toString(exec)), JSEventListener::create(asObject(listener), this, false, currentWorld(exec)), exec->argument(2).toBoolean(exec)); return jsUndefined(); } JSValue JSDOMWindow::removeEventListener(ExecState* exec) { Frame* frame = impl()->frame(); if (!frame) return jsUndefined(); JSValue listener = exec->argument(1); if (!listener.isObject()) return jsUndefined(); impl()->removeEventListener(ustringToAtomicString(exec->argument(0).toString(exec)), JSEventListener::create(asObject(listener), this, false, currentWorld(exec)).get(), exec->argument(2).toBoolean(exec)); return jsUndefined(); } DOMWindow* toDOMWindow(JSValue value) { if (!value.isObject()) return 0; JSObject* object = asObject(value); if (object->inherits(&JSDOMWindow::s_info)) return static_cast(object)->impl(); if (object->inherits(&JSDOMWindowShell::s_info)) return static_cast(object)->impl(); return 0; } } // namespace WebCore