blob: a1c43764d442cf272df758f92a767d462b2250e7 [file] [log] [blame]
/*
* 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 "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 "XSSAuditor.h"
#include "npruntime_impl.h"
#include "runtime_root.h"
#include <debugger/Debugger.h>
#include <runtime/JSLock.h>
using namespace JSC;
namespace WebCore {
ScriptController::ScriptController(Frame* frame)
: m_frame(frame)
, m_handlerLineNumber(0)
, m_sourceURL(0)
, 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_windowShell) {
m_windowShell = 0;
// It's likely that releasing the global object has created a lot of garbage.
gcController().garbageCollectSoon();
}
disconnectPlatformScriptObjects();
}
ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
{
const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
String sourceURL = jsSourceCode.provider()->url();
if (sourceURL.isNull() && !m_XSSAuditor->canEvaluateJavaScriptURL(sourceCode.source())) {
// This JavaScript URL is not safe to be evaluated.
return JSValue();
}
if (!sourceURL.isNull() && !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.
initScriptIfNeeded();
// inlineCode is true for <a href="javascript:doSomething()">
// and false for <script>doSomething()</script>. Check if it has the
// expected value in all cases.
// See smart window.open policy for where this is used.
ExecState* exec = m_windowShell->window()->globalExec();
const String* savedSourceURL = m_sourceURL;
m_sourceURL = &sourceURL;
JSLock lock(SilenceAssertionsOnly);
RefPtr<Frame> protect = m_frame;
m_windowShell->window()->globalData()->timeoutChecker.start();
Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, m_windowShell);
m_windowShell->window()->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();
}
void ScriptController::clearWindowShell()
{
if (!m_windowShell)
return;
JSLock lock(SilenceAssertionsOnly);
// Clear the debugger from the current window before setting the new window.
attachDebugger(0);
m_windowShell->window()->willRemoveFromWindowShell();
m_windowShell->setWindow(m_frame->domWindow());
if (Page* page = m_frame->page()) {
attachDebugger(page->debugger());
m_windowShell->window()->setProfileGroup(page->group().identifier());
}
// There is likely to be a lot of garbage now.
gcController().garbageCollectSoon();
}
void ScriptController::initScript()
{
if (m_windowShell)
return;
JSLock lock(SilenceAssertionsOnly);
m_windowShell = new JSDOMWindowShell(m_frame->domWindow());
m_windowShell->window()->updateDocument();
if (Page* page = m_frame->page()) {
attachDebugger(page->debugger());
m_windowShell->window()->setProfileGroup(page->group().identifier());
}
m_frame->loader()->dispatchWindowObjectAvailable();
}
bool ScriptController::processingUserGesture() const
{
return m_allowPopupsFromPlugin || processingUserGestureEvent() || isJavaScriptAnchorNavigation();
}
bool ScriptController::processingUserGestureEvent() const
{
if (!m_windowShell)
return false;
if (Event* event = m_windowShell->window()->currentEvent()) {
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 ||
// 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 <a href="javascript:window.open('...')> case -> we let it through
if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
return true;
// This is the <script>window.open(...)</script> case or a timer callback -> block it
return false;
}
bool ScriptController::anyPageIsProcessingUserGesture() const
{
Page* page = m_frame->page();
if (!page)
return false;
const HashSet<Page*>& pages = page->group().pages();
HashSet<Page*>::const_iterator end = pages.end();
for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
if (frame->script()->processingUserGesture())
return true;
}
}
return false;
}
bool ScriptController::isEnabled()
{
Settings* settings = m_frame->settings();
return (settings && settings->isJavaScriptEnabled());
}
void ScriptController::attachDebugger(JSC::Debugger* debugger)
{
if (!m_windowShell)
return;
if (debugger)
debugger->attach(m_windowShell->window());
else if (JSC::Debugger* currentDebugger = m_windowShell->window()->debugger())
currentDebugger->detach(m_windowShell->window());
}
void ScriptController::updateDocument()
{
if (!m_frame->document())
return;
JSLock lock(SilenceAssertionsOnly);
if (m_windowShell)
m_windowShell->window()->updateDocument();
}
void ScriptController::updateSecurityOrigin()
{
// Our bindings do not do anything in this case.
}
Bindings::RootObject* ScriptController::bindingRootObject()
{
if (!isEnabled())
return 0;
if (!m_bindingRootObject) {
JSLock lock(SilenceAssertionsOnly);
m_bindingRootObject = Bindings::RootObject::create(0, globalObject());
}
return m_bindingRootObject.get();
}
PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
{
RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
if (it != m_rootObjects.end())
return it->second;
RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject());
m_rootObjects.set(nativeHandle, rootObject);
return rootObject.release();
}
#if ENABLE(NETSCAPE_PLUGIN_API)
NPObject* ScriptController::windowScriptNPObject()
{
if (!m_windowScriptNPObject) {
if (isEnabled()) {
// JavaScript is enabled, so there is a JavaScript window object.
// Return an NPObject bound to the window object.
JSC::JSLock lock(SilenceAssertionsOnly);
JSObject* win = windowShell()->window();
ASSERT(win);
Bindings::RootObject* root = bindingRootObject();
m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
} else {
// JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
// Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
m_windowScriptNPObject = _NPN_CreateNoScriptObject();
}
}
return m_windowScriptNPObject;
}
NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
{
JSObject* object = jsObjectForPluginElement(plugin);
if (!object)
return _NPN_CreateNoScriptObject();
// Wrap the JSObject in an NPObject
return _NPN_CreateScriptObject(0, object, bindingRootObject());
}
#endif
JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
{
// Can't create JSObjects when JavaScript is disabled
if (!isEnabled())
return 0;
// Create a JSObject bound to this element
JSLock lock(SilenceAssertionsOnly);
ExecState* exec = globalObject()->globalExec();
JSValue jsElementValue = toJS(exec, globalObject(), plugin);
if (!jsElementValue || !jsElementValue.isObject())
return 0;
return jsElementValue.getObject();
}
#if !PLATFORM(MAC)
void ScriptController::updatePlatformScriptObjects()
{
}
void ScriptController::disconnectPlatformScriptObjects()
{
}
#endif
void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
{
RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
if (it == m_rootObjects.end())
return;
it->second->invalidate();
m_rootObjects.remove(it);
}
void ScriptController::clearScriptObjects()
{
JSLock lock(SilenceAssertionsOnly);
RootObjectMap::const_iterator end = m_rootObjects.end();
for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
it->second->invalidate();
m_rootObjects.clear();
if (m_bindingRootObject) {
m_bindingRootObject->invalidate();
m_bindingRootObject = 0;
}
#if ENABLE(NETSCAPE_PLUGIN_API)
if (m_windowScriptNPObject) {
// Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
// script object properly.
// This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
_NPN_DeallocateObject(m_windowScriptNPObject);
m_windowScriptNPObject = 0;
}
#endif
}
} // namespace WebCore