From 2e714d4681ecf1b3a49eaa4cd3bc208fa72e046d Mon Sep 17 00:00:00 2001 From: "jst%mozilla.jstenback.com" Date: Thu, 26 Aug 2004 03:49:17 +0000 Subject: [PATCH] Fixing bug 256932. Exposing elements by their ID/NAME in the global scope in quirks mode documents for more IE/Opera/Konqueror compatibility. r+sr=brendan@mozilla.org git-svn-id: svn://10.0.0.236/trunk@161300 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/dom/src/base/nsDOMClassInfo.cpp | 414 ++++++++++++++++++------ mozilla/dom/src/base/nsDOMClassInfo.h | 23 +- mozilla/dom/src/base/nsGlobalWindow.cpp | 15 +- 3 files changed, 338 insertions(+), 114 deletions(-) diff --git a/mozilla/dom/src/base/nsDOMClassInfo.cpp b/mozilla/dom/src/base/nsDOMClassInfo.cpp index d5cc191869a..24ca256ce83 100644 --- a/mozilla/dom/src/base/nsDOMClassInfo.cpp +++ b/mozilla/dom/src/base/nsDOMClassInfo.cpp @@ -96,6 +96,7 @@ #include "nsIDOMNSLocation.h" #include "nsIDOMLocation.h" #include "nsIDOMWindowInternal.h" +#include "nsPIDOMWindow.h" #include "nsIDOMJSWindow.h" #include "nsIDOMWindowCollection.h" #include "nsIDOMHistory.h" @@ -925,7 +926,7 @@ nsIXPConnect *nsDOMClassInfo::sXPConnect = nsnull; nsIScriptSecurityManager *nsDOMClassInfo::sSecMan = nsnull; PRBool nsDOMClassInfo::sIsInitialized = PR_FALSE; PRBool nsDOMClassInfo::sDisableDocumentAllSupport = PR_FALSE; - +PRBool nsDOMClassInfo::sDisableGlobalScopePollutionSupport = PR_FALSE; jsval nsDOMClassInfo::sTop_id = JSVAL_VOID; @@ -996,11 +997,76 @@ jsval nsDOMClassInfo::sAdd_id = JSVAL_VOID; jsval nsDOMClassInfo::sAll_id = JSVAL_VOID; jsval nsDOMClassInfo::sTags_id = JSVAL_VOID; -const JSClass *nsDOMClassInfo::sObjectClass = nsnull; +const JSClass *nsDOMClassInfo::sObjectClass = nsnull; PRBool nsDOMClassInfo::sDoSecurityCheckInAddProperty = PR_TRUE; +static void +PrintWarningOnConsole(JSContext *cx, const char *stringBundleProperty) +{ + nsCOMPtr + stringService(do_GetService(NS_STRINGBUNDLE_CONTRACTID)); + if (!stringService) { + return; + } + + nsCOMPtr bundle; + stringService->CreateBundle(kDOMStringBundleURL, getter_AddRefs(bundle)); + if (!bundle) { + return; + } + + nsXPIDLString msg; + bundle->GetStringFromName(NS_ConvertASCIItoUTF16(stringBundleProperty).get(), + getter_Copies(msg)); + + if (msg.IsEmpty()) { + NS_ERROR("Failed to get strings from dom.properties!"); + return; + } + + nsCOMPtr consoleService + (do_GetService("@mozilla.org/consoleservice;1")); + if (!consoleService) { + return; + } + + nsCOMPtr scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (!scriptError) { + return; + } + + JSStackFrame *fp, *iterator = nsnull; + fp = ::JS_FrameIterator(cx, &iterator); + PRUint32 lineno = 0; + nsAutoString sourcefile; + if (fp) { + JSScript* script = ::JS_GetFrameScript(cx, fp); + if (script) { + const char* filename = ::JS_GetScriptFilename(cx, script); + if (filename) { + CopyUTF8toUTF16(nsDependentCString(filename), sourcefile); + } + jsbytecode* pc = ::JS_GetFramePC(cx, fp); + if (pc) { + lineno = ::JS_PCToLineNumber(cx, script, pc); + } + } + } + nsresult rv = scriptError->Init(msg.get(), + sourcefile.get(), + EmptyString().get(), + lineno, + 0, // column for error is not available + nsIScriptError::warningFlag, + "DOM:HTML"); + if (NS_SUCCEEDED(rv)){ + consoleService->LogMessage(scriptError); + } +} + static inline JSObject * GetGlobalJSObject(JSContext *cx, JSObject *obj) { @@ -2515,6 +2581,9 @@ nsDOMClassInfo::Init() sDisableDocumentAllSupport = nsContentUtils::GetBoolPref("browser.dom.document.all.disabled"); + sDisableGlobalScopePollutionSupport = + nsContentUtils::GetBoolPref("browser.dom.global_scope_pollution.disabled"); + sIsInitialized = PR_TRUE; return NS_OK; @@ -2697,10 +2766,6 @@ nsDOMClassInfo::PostCreate(nsIXPConnectWrappedNative *wrapper, JSClass *proto_proto_class = JS_GET_CLASS(cx, proto_proto); - if (!sObjectClass) { - sObjectClass = proto_proto_class; - } - if (proto_proto_class != sObjectClass) { // We've just wrapped an object of a type that has been wrapped on // this scope already so the prototype of the xpcwrapped native's @@ -3227,9 +3292,23 @@ nsDOMClassInfo::doCheckPropertyAccess(JSContext *cx, JSObject *obj, jsval id, } NS_IMETHODIMP -nsWindowSH::PreCreate(nsISupports *nativeObj, JSContext * cx, - JSObject * globalObj, JSObject * *parentObj) +nsWindowSH::PreCreate(nsISupports *nativeObj, JSContext *cx, + JSObject *globalObj, JSObject **parentObj) { + // Since this is one of the first calls we'll get from XPConnect, + // grab the pointer to the Object class so we'll have it later on. + + if (!sObjectClass) { + JSObject *obj, *proto = globalObj; + + do { + obj = proto; + proto = ::JS_GetPrototype(cx, obj); + } while (proto); + + sObjectClass = JS_GET_CLASS(cx, obj); + } + // Normally ::PreCreate() is used to give XPConnect the parent // object for the object that's being wrapped, this parent object is // set as the parent of the wrapper and it's also used to find the @@ -3262,6 +3341,186 @@ nsWindowSH::PreCreate(nsISupports *nativeObj, JSContext * cx, return NS_OK; } + +// This JS class piggybacks on nsHTMLDocumentSH::ReleaseDocument()... + +static JSClass sGlobalScopePolluterClass = { + "Global Scope Polluter", + JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE, + nsWindowSH::SecurityCheckOnSetProp, nsWindowSH::SecurityCheckOnSetProp, + nsWindowSH::GlobalScopePolluterGetProperty, + nsWindowSH::SecurityCheckOnSetProp, JS_EnumerateStub, + (JSResolveOp)nsWindowSH::GlobalScopePolluterNewResolve, JS_ConvertStub, + nsHTMLDocumentSH::ReleaseDocument +}; + + +// static +JSBool JS_DLL_CALLBACK +nsWindowSH::GlobalScopePolluterGetProperty(JSContext *cx, JSObject *obj, + jsval id, jsval *vp) +{ + // Someone is accessing a element by referencing its name/id in the + // global scope, do a security check to make sure that's ok. + + nsresult rv = + sSecMan->CheckPropertyAccess(cx, GetGlobalJSObject(cx, obj), "Window", id, + nsIXPCSecurityManager::ACCESS_GET_PROPERTY); + + if (NS_FAILED(rv)) { + // The security check failed. The security manager set a JS + // exception for us. + + return JS_FALSE; + } + + // Print a warning on the console so developers have a chance to + // catch and fix these mistakes. + PrintWarningOnConsole(cx, "GlobalScopeElementReference"); + + return JS_TRUE; +} + +// static +JSBool JS_DLL_CALLBACK +nsWindowSH::SecurityCheckOnSetProp(JSContext *cx, JSObject *obj, jsval id, + jsval *vp) +{ + // Someone is accessing a element by referencing its name/id in the + // global scope, do a security check to make sure that's ok. + + nsresult rv = + sSecMan->CheckPropertyAccess(cx, GetGlobalJSObject(cx, obj), "Window", id, + nsIXPCSecurityManager::ACCESS_SET_PROPERTY); + + // If !NS_SUCCEEDED(rv) the security check failed. The security + // manager set a JS exception for us. + return NS_SUCCEEDED(rv); +} + +// static +JSBool JS_DLL_CALLBACK +nsWindowSH::GlobalScopePolluterNewResolve(JSContext *cx, JSObject *obj, + jsval id, uintN flags, + JSObject **objp) +{ + if (flags & (JSRESOLVE_ASSIGNING | JSRESOLVE_QUALIFIED) || + JSVAL_IS_INT(id)) { + // Nothing to do here if we're assigning, or if we're doing a + // qualified resolve, or resolving a number + + return JS_TRUE; + } + + nsIHTMLDocument *doc = (nsIHTMLDocument *)::JS_GetPrivate(cx, obj); + + if (!doc || doc->GetCompatibilityMode() != eCompatibility_NavQuirks) { + // If we don't have a document, or if the document is not in + // quirks mode, return early. + + return JS_TRUE; + } + + JSObject *proto = ::JS_GetPrototype(cx, obj); + JSString *jsstr = JSVAL_TO_STRING(id); + JSBool hasProp; + + if (!proto || !::JS_HasUCProperty(cx, proto, ::JS_GetStringChars(jsstr), + ::JS_GetStringLength(jsstr), &hasProp) || + hasProp) { + // No prototype, or the property exists on the prototype. Do + // nothing. + + return JS_TRUE; + } + + nsDependentJSString str(jsstr); + nsCOMPtr result; + + { + nsCOMPtr dom_doc(do_QueryInterface(doc)); + nsCOMPtr element; + + dom_doc->GetElementById(str, getter_AddRefs(element)); + + result = element; + } + + if (!result) { + doc->ResolveName(str, nsnull, getter_AddRefs(result)); + } + + if (result) { + jsval v; + nsresult rv = WrapNative(cx, GetGlobalJSObject(cx, obj), result, + NS_GET_IID(nsISupports), &v); + NS_ENSURE_SUCCESS(rv, rv); + + if (!::JS_DefineUCProperty(cx, obj, ::JS_GetStringChars(jsstr), + ::JS_GetStringLength(jsstr), v, nsnull, nsnull, + 0)) { + nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); + + return JS_FALSE; + } + + *objp = obj; + } + + return JS_TRUE; +} + +// static +nsresult +nsWindowSH::InstallGlobalScopePolluter(JSContext *cx, JSObject *obj, + nsIHTMLDocument *doc) +{ + // If we didn't get a document, don't bother setting up a global + // scope polluter. + if (!doc || sDisableGlobalScopePollutionSupport) { + return NS_OK; + } + + JSObject *gsp = ::JS_NewObject(cx, &sGlobalScopePolluterClass, nsnull, obj); + if (!gsp) { + return NS_ERROR_OUT_OF_MEMORY; + } + + JSObject *o = obj, *proto; + + // Find the place in the prototype chain where we want this global + // scope polluter (right before Object.prototype). + + while ((proto = ::JS_GetPrototype(cx, o))) { + if (JS_GET_CLASS(cx, proto) == sObjectClass) { + // Set the global scope polluters prototype to Object.prototype + if (!::JS_SetPrototype(cx, gsp, proto)) { + return NS_ERROR_UNEXPECTED; + } + + break; + } + + o = proto; + } + + // And then set the ptototype of the object whose prototype was + // Object.prototype to be the global scope polluter. + if (!::JS_SetPrototype(cx, o, gsp)) { + return NS_ERROR_UNEXPECTED; + } + + if (!::JS_SetPrivate(cx, gsp, doc)) { + return NS_ERROR_UNEXPECTED; + } + + // The global scope polluter will release doc on destruction (or + // reinitialzation). + NS_ADDREF(doc); + + return NS_OK; +} + NS_IMETHODIMP nsWindowSH::GetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx, JSObject *obj, jsval id, jsval *vp, PRBool *_retval) @@ -4210,8 +4469,8 @@ nsWindowSH::GlobalResolve(nsISupports *native, JSContext *cx, JSObject *obj, // static nsresult -nsWindowSH::CacheDocumentProperty(JSContext *cx, JSObject *obj, - nsIDOMWindow *window) +nsWindowSH::OnDocumentChanged(JSContext *cx, JSObject *obj, + nsIDOMWindow *window) { nsCOMPtr document; nsresult rv = window->GetDocument(getter_AddRefs(document)); @@ -4230,7 +4489,9 @@ nsWindowSH::CacheDocumentProperty(JSContext *cx, JSObject *obj, return NS_ERROR_FAILURE; } - return NS_OK; + nsCOMPtr html_doc(do_QueryInterface(document)); + + return InstallGlobalScopePolluter(cx, obj, html_doc); } NS_IMETHODIMP @@ -4485,7 +4746,7 @@ nsWindowSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx, nsCOMPtr window(do_QueryInterface(native)); NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED); - rv = CacheDocumentProperty(cx, obj, window); + rv = OnDocumentChanged(cx, obj, window); NS_ENSURE_SUCCESS(rv, rv); *objp = obj; @@ -5577,7 +5838,7 @@ nsHTMLDocumentSH::DocumentOpen(JSContext *cx, JSObject *obj, uintN argc, static JSClass sHTMLDocumentAllClass = { "HTML document.all class", - JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1), JS_PropertyStub, JS_PropertyStub, nsHTMLDocumentSH::DocumentAllGetProperty, JS_PropertyStub, JS_EnumerateStub, @@ -5776,8 +6037,8 @@ nsHTMLDocumentSH::DocumentAllGetProperty(JSContext *cx, JSObject *obj, } JSBool JS_DLL_CALLBACK -nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JSObject *obj, - jsval id, uintN flags, JSObject **objp) +nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JSObject *obj, jsval id, + uintN flags, JSObject **objp) { if (flags & JSRESOLVE_ASSIGNING) { // Nothing to do here if we're assigning @@ -5848,6 +6109,9 @@ nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JSObject *obj, return ok; } +// Finalize hook used by document related JS objects, but also by +// sGlobalScopePolluterClass! + void JS_DLL_CALLBACK nsHTMLDocumentSH::ReleaseDocument(JSContext *cx, JSObject *obj) { @@ -5907,71 +6171,6 @@ GetDocumentAllHelper(JSContext *cx, JSObject *obj) return obj; } -static void -PrintDocumentAllWarningOnConsole(JSContext *cx) -{ - nsCOMPtr - stringService(do_GetService(NS_STRINGBUNDLE_CONTRACTID)); - if (!stringService) { - return; - } - - nsCOMPtr bundle; - stringService->CreateBundle(kDOMStringBundleURL, getter_AddRefs(bundle)); - if (!bundle) { - return; - } - - nsXPIDLString msg; - bundle->GetStringFromName(NS_LITERAL_STRING("DocumentAllUsed").get(), - getter_Copies(msg)); - - if (msg.IsEmpty()) { - NS_ERROR("Failed to get strings from dom.properties!"); - return; - } - - nsCOMPtr consoleService - (do_GetService("@mozilla.org/consoleservice;1")); - if (!consoleService) { - return; - } - - nsCOMPtr scriptError = - do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); - if (!scriptError) { - return; - } - - JSStackFrame *fp, *iterator = nsnull; - fp = ::JS_FrameIterator(cx, &iterator); - PRUint32 lineno = 0; - nsAutoString sourcefile; - if (fp) { - JSScript* script = ::JS_GetFrameScript(cx, fp); - if (script) { - const char* filename = ::JS_GetScriptFilename(cx, script); - if (filename) { - CopyUTF8toUTF16(nsDependentCString(filename), sourcefile); - } - jsbytecode* pc = ::JS_GetFramePC(cx, fp); - if (pc) { - lineno = ::JS_PCToLineNumber(cx, script, pc); - } - } - } - nsresult rv = scriptError->Init(msg.get(), - sourcefile.get(), - EmptyString().get(), - lineno, - 0, // column for error is not available - nsIScriptError::warningFlag, - "DOM:HTML"); - if (NS_SUCCEEDED(rv)){ - consoleService->LogMessage(scriptError); - } -} - JSBool JS_DLL_CALLBACK nsHTMLDocumentSH::DocumentAllHelperGetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) @@ -6003,7 +6202,7 @@ nsHTMLDocumentSH::DocumentAllHelperGetProperty(JSContext *cx, JSObject *obj, // qualified name. Expose the document.all collection. // Print a warning so developers can stop using document.all - PrintDocumentAllWarningOnConsole(cx); + PrintWarningOnConsole(cx, "DocumentAllUsed"); if (!JSVAL_IS_OBJECT(*vp)) { // First time through, create the collection, and set the @@ -6162,39 +6361,46 @@ nsHTMLDocumentSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx, } if (id == sAll_id && !sDisableDocumentAllSupport) { - JSObject *helper = GetDocumentAllHelper(cx, ::JS_GetPrototype(cx, obj)); + wrapper->GetNative(getter_AddRefs(result)); + nsCOMPtr doc(do_QueryInterface(result)); - // If we don't already have a helper, and we're - // resolving document.all qualified, and we're *not* detecting - // document.all, e.g. if (document.all), create a helper. - if (!helper && flags & JSRESOLVE_QUALIFIED && - !(flags & JSRESOLVE_DETECTING)) { - helper = JS_NewObject(cx, &sHTMLDocumentAllHelperClass, - ::JS_GetPrototype(cx, obj), - GetGlobalJSObject(cx, obj)); + if (doc->GetCompatibilityMode() == eCompatibility_NavQuirks) { + JSObject *helper = + GetDocumentAllHelper(cx, ::JS_GetPrototype(cx, obj)); - if (!helper) { - return NS_ERROR_OUT_OF_MEMORY; + // If we don't already have a helper, and we're resolving + // document.all qualified, and we're *not* detecting + // document.all, e.g. if (document.all), create a helper. + if (!helper && flags & JSRESOLVE_QUALIFIED && + !(flags & JSRESOLVE_DETECTING)) { + helper = JS_NewObject(cx, &sHTMLDocumentAllHelperClass, + ::JS_GetPrototype(cx, obj), + GetGlobalJSObject(cx, obj)); + + if (!helper) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Insert the helper into our prototype chain. helper's prototype + // is already obj's current prototype. + if (!::JS_SetPrototype(cx, obj, helper)) { + nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); + + return NS_ERROR_UNEXPECTED; + } } - // Insert the helper into our prototype chain. helper's prototype - // is already obj's current prototype. - if (!::JS_SetPrototype(cx, obj, helper)) { + // If we have (or just created) a helper, pass the resolve flags + // to the helper as its private data. + if (helper && + !::JS_SetPrivate(cx, helper, + JSVAL_TO_PRIVATE(INT_TO_JSVAL(flags)))) { nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); return NS_ERROR_UNEXPECTED; } } - // If we have (or just created) a helper, pass the resolve flags - // to the helper as its private data. - if (helper && !::JS_SetPrivate(cx, helper, - JSVAL_TO_PRIVATE(INT_TO_JSVAL(flags)))) { - nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED); - - return NS_ERROR_UNEXPECTED; - } - return NS_OK; } } diff --git a/mozilla/dom/src/base/nsDOMClassInfo.h b/mozilla/dom/src/base/nsDOMClassInfo.h index 42468e87c65..85d9fccd305 100644 --- a/mozilla/dom/src/base/nsDOMClassInfo.h +++ b/mozilla/dom/src/base/nsDOMClassInfo.h @@ -50,6 +50,7 @@ class nsIPluginInstance; class nsIForm; class nsIDOMNodeList; class nsIDOMDocument; +class nsIHTMLDocument; struct nsDOMClassInfoData; @@ -199,6 +200,7 @@ protected: static PRBool sIsInitialized; static PRBool sDisableDocumentAllSupport; + static PRBool sDisableGlobalScopePollutionSupport; static jsval sTop_id; static jsval sParent_id; @@ -356,8 +358,23 @@ public: NS_IMETHOD Finalize(nsIXPConnectWrappedNative *wrapper, JSContext *cx, JSObject *obj); - static nsresult CacheDocumentProperty(JSContext *cx, JSObject *obj, - nsIDOMWindow *window); + static nsresult OnDocumentChanged(JSContext *cx, JSObject *obj, + nsIDOMWindow *window); + + static JSBool JS_DLL_CALLBACK GlobalScopePolluterNewResolve(JSContext *cx, + JSObject *obj, + jsval id, + uintN flags, + JSObject **objp); + static JSBool JS_DLL_CALLBACK GlobalScopePolluterGetProperty(JSContext *cx, + JSObject *obj, + jsval id, + jsval *vp); + static JSBool JS_DLL_CALLBACK SecurityCheckOnSetProp(JSContext *cx, + JSObject *obj, jsval id, + jsval *vp); + static nsresult InstallGlobalScopePolluter(JSContext *cx, JSObject *obj, + nsIHTMLDocument *doc); static nsIClassInfo *doCreate(nsDOMClassInfoData* aData) { @@ -434,7 +451,7 @@ protected: public: NS_IMETHOD PostCreate(nsIXPConnectWrappedNative *wrapper, JSContext *cx, - JSObject *obj); + JSObject *obj); static nsIClassInfo *doCreate(nsDOMClassInfoData* aData) { diff --git a/mozilla/dom/src/base/nsGlobalWindow.cpp b/mozilla/dom/src/base/nsGlobalWindow.cpp index b882084cbdf..2377b4c75db 100644 --- a/mozilla/dom/src/base/nsGlobalWindow.cpp +++ b/mozilla/dom/src/base/nsGlobalWindow.cpp @@ -645,14 +645,15 @@ GlobalWindowImpl::SetNewDocument(nsIDOMDocument* aDocument, if (mIsScopeClear) { mContext->InitContext(this); - } else if (mJSObject) { - // If we didn't clear the scope (i.e. the old document was - // about:blank) then we need to update the cached document - // property on the window to reflect the new document and not - // the old one. - - nsWindowSH::CacheDocumentProperty(cx, mJSObject, this); } + + // If we didn't clear the scope (i.e. the old document was + // about:blank) then we need to update the cached document + // property on the window to reflect the new document and not the + // old one. And even if we did clear the scope, we need to notify + // the scriptable helper about the document change. + + nsWindowSH::OnDocumentChanged(cx, mJSObject, this); } // Clear our mutation bitfield.