/* * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "ScriptController.h" #include "CString.h" #include "Event.h" #include "EventNames.h" #include "Frame.h" #include "GCController.h" #include "HTMLPlugInElement.h" #include "JSDocument.h" #include "NP_jsobject.h" #include "Page.h" #include "PageGroup.h" #include "ScriptSourceCode.h" #include "ScriptValue.h" #include "Settings.h" #include "StorageNamespace.h" #include "XSSAuditor.h" #include "npruntime_impl.h" #include "runtime_root.h" #include #include #include using namespace JSC; namespace WebCore { void ScriptController::initializeThreading() { JSC::initializeThreading(); } ScriptController::ScriptController(Frame* frame) : m_frame(frame) , m_handlerLineNumber(0) , m_sourceURL(0) , m_inExecuteScript(false) , m_processingTimerCallback(false) , m_paused(false) , m_allowPopupsFromPlugin(false) #if ENABLE(NETSCAPE_PLUGIN_API) , m_windowScriptNPObject(0) #endif #if PLATFORM(MAC) , m_windowScriptObject(0) #endif , m_XSSAuditor(new XSSAuditor(frame)) { #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE) static bool initializedJavaJSBindings; if (!initializedJavaJSBindings) { initializedJavaJSBindings = true; initJavaJSBindings(); } #endif } ScriptController::~ScriptController() { if (!m_windowShells.isEmpty()) { for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) iter->first->forgetScriptController(this); m_windowShells.clear(); // It's likely that releasing the global object has created a lot of garbage. gcController().garbageCollectSoon(); } disconnectPlatformScriptObjects(); } ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world) { const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); String sourceURL = jsSourceCode.provider()->url(); if (!m_XSSAuditor->canEvaluate(sourceCode.source())) { // This script is not safe to be evaluated. return JSValue(); } // evaluate code. Returns the JS return value or 0 // if there was none, an error occured or the type couldn't be converted. // inlineCode is true for // and false for . Check if it has the // expected value in all cases. // See smart window.open policy for where this is used. JSDOMWindowShell* shell = windowShell(world); ExecState* exec = shell->window()->globalExec(); const String* savedSourceURL = m_sourceURL; m_sourceURL = &sourceURL; JSLock lock(SilenceAssertionsOnly); RefPtr protect = m_frame; exec->globalData().timeoutChecker.start(); Completion comp = WebCore::evaluateInWorld(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell, world); exec->globalData().timeoutChecker.stop(); // Evaluating the JavaScript could cause the frame to be deallocated // so we start the keep alive timer here. m_frame->keepAlive(); if (comp.complType() == Normal || comp.complType() == ReturnValue) { m_sourceURL = savedSourceURL; return comp.value(); } if (comp.complType() == Throw || comp.complType() == Interrupted) reportException(exec, comp.value()); m_sourceURL = savedSourceURL; return JSValue(); } ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) { return evaluateInWorld(sourceCode, mainThreadNormalWorld()); } // An DOMWrapperWorld other than the thread's normal world. class IsolatedWorld : public DOMWrapperWorld { public: IsolatedWorld(JSGlobalData* globalData) : DOMWrapperWorld(globalData) { JSGlobalData::ClientData* clientData = globalData->clientData; ASSERT(clientData); static_cast(clientData)->rememberWorld(this); } static PassRefPtr create(JSGlobalData* globalData) { return adoptRef(new IsolatedWorld(globalData)); } }; static PassRefPtr findWorld(unsigned worldID) { if (!worldID) return IsolatedWorld::create(JSDOMWindow::commonJSGlobalData()); typedef HashMap > WorldMap; DEFINE_STATIC_LOCAL(WorldMap, isolatedWorlds, ()); WorldMap::iterator iter = isolatedWorlds.find(worldID); if (iter != isolatedWorlds.end()) return iter->second; RefPtr newWorld = IsolatedWorld::create(JSDOMWindow::commonJSGlobalData()); isolatedWorlds.add(worldID, newWorld); return newWorld; } JSDOMWindow* ScriptController::globalObject(unsigned worldID) { RefPtr world = findWorld(worldID); return windowShell(world.get())->window(); } ScriptValue ScriptController::evaluateInIsolatedWorld(unsigned worldID, const ScriptSourceCode& sourceCode) { RefPtr world = findWorld(worldID); return evaluateInWorld(sourceCode, world.get()); } void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector& sourceCode) { RefPtr world = findWorld(worldID); unsigned size = sourceCode.size(); for (unsigned i = 0; i < size; ++i) evaluateInWorld(sourceCode[i], world.get()); } void ScriptController::clearWindowShell() { if (m_windowShells.isEmpty()) return; JSLock lock(SilenceAssertionsOnly); // Clear the debugger from the current window before setting the new window. DOMWrapperWorld* debugWorld = debuggerWorld(); attachDebugger(0); for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { DOMWrapperWorld* world = iter->first; JSDOMWindowShell* windowShell = iter->second; windowShell->window()->willRemoveFromWindowShell(); windowShell->setWindow(m_frame->domWindow()); if (Page* page = m_frame->page()) { if (world == debugWorld) attachDebugger(page->debugger()); windowShell->window()->setProfileGroup(page->group().identifier()); } } // There is likely to be a lot of garbage now. gcController().garbageCollectSoon(); } JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world) { ASSERT(!m_windowShells.contains(world)); JSLock lock(SilenceAssertionsOnly); JSDOMWindowShell* windowShell = new JSDOMWindowShell(m_frame->domWindow()); m_windowShells.add(world, windowShell); world->rememberScriptController(this); windowShell->window()->updateDocument(world); if (Page* page = m_frame->page()) { if (world == debuggerWorld()) attachDebugger(page->debugger()); windowShell->window()->setProfileGroup(page->group().identifier()); } { EnterDOMWrapperWorld worldEntry(*JSDOMWindow::commonJSGlobalData(), world); m_frame->loader()->dispatchWindowObjectAvailable(); } return windowShell; } bool ScriptController::processingUserGesture() const { return m_allowPopupsFromPlugin || processingUserGestureEvent() || isJavaScriptAnchorNavigation(); } bool ScriptController::processingUserGestureEvent() const { JSDOMWindowShell* shell = existingWindowShell(mainThreadNormalWorld()); if (!shell) return false; if (Event* event = shell->window()->currentEvent()) { if (event->createdByDOM()) return false; const AtomicString& type = event->type(); if ( // mouse events type == eventNames().clickEvent || type == eventNames().mousedownEvent || type == eventNames().mouseupEvent || type == eventNames().dblclickEvent || // keyboard events type == eventNames().keydownEvent || type == eventNames().keypressEvent || type == eventNames().keyupEvent || #if ENABLE(TOUCH_EVENTS) // Android // touch events type == eventNames().touchstartEvent || type == eventNames().touchmoveEvent || type == eventNames().touchendEvent || type == eventNames().touchcancelEvent || #endif // other accepted events type == eventNames().selectEvent || type == eventNames().changeEvent || type == eventNames().focusEvent || type == eventNames().blurEvent || type == eventNames().submitEvent) return true; } return false; } // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor. bool ScriptController::isJavaScriptAnchorNavigation() const { // This is the