Standard reference
No.
Problem description
Binding an event in the attribute of an element, in effect creating an inline event handler (such as
alert"); ...>...inline event handlers have a special scope chain, and there are differences in the implementation details of each browser.
The impact
Improper use of variables or calls in an inline event-handling function of an element causes the script to run an error.
Affected browsers
Problem analysis
1. Scope chain of inline event handler functions
Unlike other functions, the scope chain of an inline event handler starts from the head: The calling object, the DOM object for the element, the DOM object (if any) of the FORM that the element belongs to, the Document object, the Window object (Global object).
such as the following code:
<form action= "." Method= "get" >
<input type= "button" value= "Compatmode" onclick= "alert (compatmode);" >
</form>
Equivalent to 1:
<form action= "." Method= "get" >
<input type= "button" value= "Compatmode" >
</form>
< script>
document.getelementsbytagname ("input") [0].onclick=function () {with [
document] {with (this
2.form) 3{with ( This
The code in both of these ways will pop up the Document.compatmode value in all browsers.
Replacing ' Compatmode ' in the above code with ' method ' will pop up ' get ' in each browser, that is, the value of the method property of the Form object that the INPUT element is in.
Note:
1. This code simulates the behavior of browsers only to illustrate the problem, and does not mean that all browsers are so implemented.
2. Whether to use this keyword or use this DOM object directly, there are differences in each browser, please see the contents of this article 2.1.
3. Whether to add a FORM object to the scope chain, the browser in the implementation of the differences, please see the details of the contents of this article 2.2.
2. The difference between the scope chain of the inline event handler function and the browser
Refer to WebKit Source code:
void V8lazyeventlistener::p reparelistenerobject (scriptexecutioncontext* context) {if (Hasexistinglistenerobject ())
Return
V8::handlescope Handlescope;
v8proxy* Proxy = V8proxy::retrieve (context);
if (!proxy) return;
Use the outer scope to hold context.
v8::local<v8::context> V8context = Worldcontext (). Adjustedcontext (proxy);
Bail out if we cannot get to the context.
if (V8context.isempty ()) return;
V8::context::scope Scope (V8context);
Fixme:cache the wrapper function. //Nodes other than the document object, when executing inline event handlers push document, form, and the target node on The scope chain.We are using ' with ' statement. chrome/fast/forms/form-action.html//chrome/fast/forms/selected-index-value.html//Base/fast/overflow/ons croll-layer-self-destruct.html////Don ' t use new lines so, lines in the modified handler//have the same numb
ERs as in the original code. String code = "(function (evt) {" \ "with ( This. ownerdocument? This. Ownerdocument: {}) {"\" with (This . Form? This. Form: {}) {"\" with ( This) {"\" return (function (evt) {);
Code.append (M_code);
Insert ' \ n ' Otherwise//-style comments could break the handler.
Code.append ("\ n}). Call (this, evt);}}}");
v8::handle<v8::string> codeexternalstring = v8externalstring (code);
v8::handle<v8::script> Script = V8proxy::compilescript (codeexternalstring, M_sourceurl, M_lineNumber); if (!script.
IsEmpty ()) {v8::local<v8::value> Value = proxy->runscript (script, false); if (!value.
IsEmpty ()) {ASSERT (value->isfunction ());
v8::local<v8::function> wrappedfunction = v8::local<v8::function>::cast (value); Change the toString function on the wrapper function to avoid it//returning the source for the actual wrapper F Unction. Instead IT//returns source for a clean wrapper function with the event//argument wrapping the event source Code.
The reason for this is//, some Web sites use toString on event functions and eval the Source returned (sometimes a RegExp is applied as OK) for some//other use.
That fails miserably if the actual wrapper source is//returned.
Define_static_local (V8::P ersistent<v8::functiontemplate>, Tostringtemplate, ()); if (Tostringtemplate.isempty ()) tostringtemplate = V8::P ersistent<v8::functiontemplate>::new (V8::FunctionTem
Plate::new (v8lazyeventlistenertostring));
V8::local<v8::function> tostringfunction;
if (!tostringtemplate.isempty ()) tostringfunction = Tostringtemplate->getfunction ();
if (!tostringfunction.isempty ()) {String Tostringresult = ' function ';
Tostringresult.append (M_functionname);
Tostringresult.append ("("); Tostringresult.append (m_issvgevent?)
"EVT": "Event");
Tostringresult.append (") {\ n");
Tostringresult.append (M_code);
Tostringresult.append ("\ n}"); Wrappedfunction->sethiddenvalue (v8hiddenpropertyname::tostRingstring (), v8externalstring (Tostringresult));
Wrappedfunction->set (V8::string::new ("toString"), tostringfunction);
} wrappedfunction->setname (V8::string::new (fromwebcorestring (M_functionname), M_functionname.length ()));
Setlistenerobject (wrappedfunction);
}
}
}
As you can see from the code above, WebKit uses the ' this ' keyword when adding objects to the scope chain, and determines whether to add a Form object to the scope chain by determining whether ' this.form ' exists.
There are similar implementations in other browsers, however, in each browser, there are differences in how the target object (that is, the object that binds this inline event handler ) is added to the scope chain, and the method of determining whether to add a FORM object in the scope chain is different.
2.1. Browsers use different methods to add target objects when generating this particular scope chain
Each browser adds the DOM object of the element that the inline event-handler function belongs to to the scope chain, but the way it is joined is different.
such as the following code:
<input type= "button" value= "Hello" onclick= "alert (value);" >
In all browsers, ' hello ' will pop up.
Then modify the code to change the execution context of the inline event handler function for the INPUT element:
<input type= "button" value= "Hello" onclick= "alert (value);" >
<script>
var $target =document.getelementsbytagname ("input") [0];
var o={
onclick: $target. onclick,
value: "Hi, I ' m here!"
};
O.onclick ();
</script>
The results of running in each browser are as follows:
IE Chrome |
Hi, I ' m here! |
Firefox Safari Opera |
Hello |
As you can see, browsers have different ways to add the DOM object of the element that the inline event handler functions to the scope chain.
The way you add in IE Chrome is similar to the following code:
<input type= "button" value= "Hello" >
<script>
var $target =document.getelementsbytagname ("input ") [0];
$target. Onclick=function () {with (
document) {
alert (value);
}}} </script>
The addition of Firefox Safari Opera is similar to the following code:
<input type= "button" value= "Hello" >
<script>
var $target =document.getelementsbytagname (" Input ") [0];
$target. Onclick=function () {with (
document) {with
($target) {
alert (value);
}}} </script>
The effect of this difference is rare because there is very little need to change the execution context of inline event handlers .
2.2. Browsers have a different understanding of how to add a FORM object when creating this particular scope chain
Each browser adds the form object that the inline event handler functions to the scope chain, but how to determine whether the element is "part of" a form object or not is handled differently by browsers.
such as the following code:
<form action= "." Method= "get" >
<div>
<span onclick= "alert (method);" >click</span>
</div>
</form>
<script>
document.method= " Document.method ";
</script>
In each browser, the information that pops up after you click on the SPAN element is as follows:
IE Safari Opera |
Get |
Chrome Firefox |
Document.method |
Visible:
- IE Safari Opera Adds a Form object to the scope chain of an inline event handler , whether adding a Form object appears to be determined by whether the element is a descendant of a form. So in these browsers, the variable ' method ' in the function ultimately gets the value of the FORM's ' method '.
- Chrome Firefox does not add a Form object to the scope chain of an inline event handler to determine whether the join form object depends on whether the ' form ' property of the target object to which the function is bound exists. From the WebKit source above, you can see that Chrome is using ' this.form ' to judge that the ' form ' attribute exists only if the target element is a descendant of a form and the target element is a FORM element. The SPAN element in this example is not a form element, so the variable ' method ' eventually gets the value of ' Document.method '.
If you replace the SPAN element in the above code with an INPUT element or another form element, the performance will be consistent across all browsers.
3. An instance of the problem caused by this particular scope chain of the inline event handler function
3.1. The problem that a variable accessed in an inline event-handling function of an element unexpectedly has the same name as the other object of the global object in the scope chain of that function
When a variable accessed in an inline event-handler function unexpectedly has the same name as the property of another object in the scope chain of the function that is not a global object (window), the actual value of the variable is not the expected value.
Suppose you have the following code:
<button onclick= "Onsearch ()" > click here </button>
<script>
function Onsearch () {
Alert ("click!");
}
</script>
The author's intention is to click the button that pops up "click!" Information, but the HtmlElement object for the WebKit engine browser has an event listener named Onsearch, which will cause the code above to be executed in Chrome Safari as expected. In this case, because the listener is undefined (null), an error of "uncaught Typeerror:object is not a function" will be reported.
Attached: In the above code, append the following code to confirm the location of ' Onsearch ':
<script>
var o=document.getelementsbytagname ("button") [0];
if ("Onsearch" in O) alert ("The current object has a Onsearch property.") ");
if (O.hasownproperty ("Onsearch")) Alert ("Onsearch property is private to the current object. ");
</script>
3.2. The problem that occurs when an attempt is made to invoke a property or method of a form in an inline event-handling function that is not a descendant of a form element in the form
Suppose you have the following code:
<form action= "xxx" method= "get" > ...
<a href= "#" onclick= "submit ();" >click</a>
</form>
The author intended to call form's ' Submit ' method after clicking A element, but Chrome Firefox did not add the form object to the scope chain of the inline event handler , so the above code does not work in Chrome Firefox 。
Solution
1. Do not use the inline event handler function , the DOM standard event registration method for the element registration event handler functions, such as:
<button> Click here </button>
<script>
function Onsearch () {
alert ("click!");
}
function bind ($target, eventname,onevent) {
$target. AddEventListener $target. AddEventListener (EventName, Onevent,false): $target. attachevent ("on" +eventname,onevent);
Bind (document.getElementsByTagName ("button") [0], "click", Onsearch);
</script>
2. When you must use an inline event handler, make sure that the variable you are trying to access within the function is in the global scope, and that it is not referenced to unexpected objects because of the function's unique scope chain. The easiest way to do this is to use prefixes such as ' my_onsearch '.