2 // System.Web.UI.ClientScriptManager.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Gonzalo Paniagua (gonzalo@ximian.com)
7 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
8 // Lluis Sanchez (lluis@novell.com)
10 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2003 Novell, Inc. (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections;
38 using System.Collections.Generic;
41 using System.Collections.Specialized;
42 using System.Web.Util;
43 using System.Globalization;
45 namespace System.Web.UI
52 class ClientScriptManager
54 internal const string EventStateFieldName = "__EVENTVALIDATION";
56 Hashtable registeredArrayDeclares;
57 ScriptEntry clientScriptBlocks;
58 ScriptEntry startupScriptBlocks;
59 internal Hashtable hiddenFields;
60 ScriptEntry submitStatements;
63 int [] eventValidationValues;
64 int eventValidationPos = 0;
65 Hashtable expandoAttributes;
66 bool _hasRegisteredForEventValidationOnCallback;
70 internal ClientScriptManager (Page page)
76 public string GetPostBackClientEvent (Control control, string argument)
78 return GetPostBackEventReference (control, argument);
82 public string GetPostBackClientHyperlink (Control control, string argument)
84 return "javascript:" + GetPostBackEventReference (control, argument);
88 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
90 if (registerForEventValidation)
91 RegisterForEventValidation (control.UniqueID, argument);
92 return "javascript:" + GetPostBackEventReference (control, argument);
101 string GetPostBackEventReference (Control control, string argument)
104 throw new ArgumentNullException ("control");
106 page.RequiresPostBackScript ();
108 return page.theForm + ".__doPostBack('" + control.UniqueID + "','" + argument + "')";
110 return "__doPostBack('" + control.UniqueID + "','" + argument + "')";
114 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
117 throw new ArgumentNullException ("control");
119 if (registerForEventValidation)
120 RegisterForEventValidation (control.UniqueID, argument);
121 return GetPostBackEventReference (control, argument);
124 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
127 throw new ArgumentNullException ("options");
128 if (registerForEventValidation)
129 RegisterForEventValidation (options);
130 return GetPostBackEventReference (options);
133 public string GetPostBackEventReference (PostBackOptions options)
136 throw new ArgumentNullException ("options");
138 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
139 !options.AutoPostBack && !options.PerformValidation)
141 if (!options.ClientSubmit)
144 if (options.RequiresJavaScriptProtocol)
145 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
147 return GetPostBackEventReference (options.TargetControl, options.Argument);
150 RegisterWebFormClientScript ();
152 string actionUrl = options.ActionUrl;
153 if (actionUrl != null)
154 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
156 if(options.TrackFocus)
157 RegisterHiddenField (Page.LastFocusID, String.Empty);
159 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
160 if (page.IsMultiForm)
161 prefix += page.theForm + ".";
163 return prefix + "WebForm_DoPostback(" +
164 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID) + "," +
165 ClientScriptManager.GetScriptLiteral (options.Argument) + "," +
166 ClientScriptManager.GetScriptLiteral (actionUrl) + "," +
167 ClientScriptManager.GetScriptLiteral (options.AutoPostBack) + "," +
168 ClientScriptManager.GetScriptLiteral (options.PerformValidation) + "," +
169 ClientScriptManager.GetScriptLiteral (options.TrackFocus) + "," +
170 ClientScriptManager.GetScriptLiteral (options.ClientSubmit) + "," +
171 ClientScriptManager.GetScriptLiteral (options.ValidationGroup) + ")";
174 internal void RegisterWebFormClientScript ()
176 if (_webFormClientScriptRequired)
179 page.RequiresPostBackScript ();
180 _webFormClientScriptRequired = true;
183 bool _webFormClientScriptRendered;
184 bool _webFormClientScriptRequired;
186 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
187 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
189 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"), typeof (Page), "webform.js");
190 WriteBeginScriptBlock (writer);
191 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
192 WriteEndScriptBlock (writer);
193 _webFormClientScriptRendered = true;
197 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
199 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
202 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
204 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
207 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
210 throw new ArgumentNullException ("control");
211 if(!(control is ICallbackEventHandler))
212 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
214 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
217 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
219 RegisterWebFormClientScript ();
221 string formReference = page.IsMultiForm ? page.theForm + "." : String.Empty;
223 return formReference + "WebForm_DoCallback(" +
225 ((argument == null) ? "null" : argument) + "," +
226 clientCallback + "," +
227 ((context == null) ? "null" : context) + "," +
228 ((clientErrorCallback == null) ? "null" : clientErrorCallback) + "," +
229 (useAsync ? "true" : "false") + ")";
238 string GetWebResourceUrl(Type type, string resourceName)
241 throw new ArgumentNullException ("type");
243 if (resourceName == null || resourceName.Length == 0)
244 throw new ArgumentNullException ("type");
246 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
250 public bool IsClientScriptBlockRegistered (string key)
252 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
255 public bool IsClientScriptBlockRegistered (Type type, string key)
257 return IsScriptRegistered (clientScriptBlocks, type, key);
260 public bool IsStartupScriptRegistered (string key)
262 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
265 public bool IsStartupScriptRegistered (Type type, string key)
267 return IsScriptRegistered (startupScriptBlocks, type, key);
270 public bool IsOnSubmitStatementRegistered (string key)
272 return IsScriptRegistered (submitStatements, GetType(), key);
275 public bool IsOnSubmitStatementRegistered (Type type, string key)
277 return IsScriptRegistered (submitStatements, type, key);
280 public bool IsClientScriptIncludeRegistered (string key)
282 return IsClientScriptIncludeRegistered (GetType (), key);
285 public bool IsClientScriptIncludeRegistered (Type type, string key)
287 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
290 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
292 while (scriptList != null) {
293 if (scriptList.Type == type && scriptList.Key == key)
295 scriptList = scriptList.Next;
300 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
302 if (registeredArrayDeclares == null)
303 registeredArrayDeclares = new Hashtable();
305 if (!registeredArrayDeclares.ContainsKey (arrayName))
306 registeredArrayDeclares.Add (arrayName, new ArrayList());
308 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
311 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
313 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
316 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
318 ScriptEntry last = null;
319 ScriptEntry entry = scriptList;
321 while (entry != null) {
322 if (entry.Type == type && entry.Key == key)
328 entry = new ScriptEntry (type, key, script, format);
330 if (last != null) last.Next = entry;
331 else scriptList = entry;
334 internal void RegisterClientScriptBlock (string key, string script)
336 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
339 public void RegisterClientScriptBlock (Type type, string key, string script)
341 RegisterClientScriptBlock (type, key, script, false);
344 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
347 throw new ArgumentNullException ("type");
349 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
352 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
354 if (hiddenFields == null)
355 hiddenFields = new Hashtable ();
357 if (!hiddenFields.ContainsKey (hiddenFieldName))
358 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
361 internal void RegisterOnSubmitStatement (string key, string script)
363 RegisterScript (ref submitStatements, GetType (), key, script, false);
366 public void RegisterOnSubmitStatement (Type type, string key, string script)
369 throw new ArgumentNullException ("type");
371 RegisterScript (ref submitStatements, type, key, script, false);
374 internal void RegisterStartupScript (string key, string script)
376 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
379 public void RegisterStartupScript (Type type, string key, string script)
381 RegisterStartupScript (type, key, script, false);
384 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
387 throw new ArgumentNullException ("type");
389 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
392 public void RegisterClientScriptInclude (string key, string url)
394 RegisterClientScriptInclude (GetType (), key, url);
397 public void RegisterClientScriptInclude (Type type, string key, string url)
400 throw new ArgumentNullException ("type");
401 if (url == null || url.Length == 0)
402 throw new ArgumentException ("url");
404 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
408 public void RegisterClientScriptResource (Type type, string resourceName)
410 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
413 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
415 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
418 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
420 if (controlId == null)
421 throw new ArgumentNullException ("controlId");
423 if (attributeName == null)
424 throw new ArgumentNullException ("attributeName");
426 if (expandoAttributes == null)
427 expandoAttributes = new Hashtable ();
429 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
431 list = new ListDictionary ();
432 expandoAttributes [controlId] = list;
435 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
438 private void EnsureEventValidationArray ()
440 if (eventValidationValues == null || eventValidationValues.Length == 0)
441 eventValidationValues = new int [64];
443 int len = eventValidationValues.Length;
445 if (eventValidationPos >= len) {
446 int [] tmp = new int [len * 2];
447 Array.Copy (eventValidationValues, tmp, len);
448 eventValidationValues = tmp;
452 internal void ResetEventValidationState ()
454 _pageInRender = true;
455 eventValidationPos = 0;
458 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
459 private int CalculateEventHash (string uniqueId, string argument)
461 int uniqueIdHash = uniqueId.GetHashCode ();
462 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
463 return (uniqueIdHash ^ argumentHash);
466 public void RegisterForEventValidation (PostBackOptions options)
468 // MS.NET does not check for options == null, so we won't too...
469 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
472 public void RegisterForEventValidation (string uniqueId)
474 RegisterForEventValidation (uniqueId, null);
477 public void RegisterForEventValidation (string uniqueId, string argument)
479 if (!page.EnableEventValidation)
481 if (uniqueId == null || uniqueId.Length == 0)
484 _hasRegisteredForEventValidationOnCallback = true;
485 else if (!_pageInRender)
486 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
488 EnsureEventValidationArray ();
490 int hash = CalculateEventHash (uniqueId, argument);
491 for (int i = 0; i < eventValidationPos; i++)
492 if (eventValidationValues [i] == hash)
494 eventValidationValues [eventValidationPos++] = hash;
497 public void ValidateEvent (string uniqueId)
499 ValidateEvent (uniqueId, null);
502 public void ValidateEvent (string uniqueId, string argument)
504 if (uniqueId == null || uniqueId.Length == 0)
505 throw new ArgumentException ("must not be null or empty", "uniqueId");
506 if (!page.EnableEventValidation)
508 if (eventValidationValues == null)
511 int hash = CalculateEventHash (uniqueId, argument);
512 for (int i = 0; i < eventValidationValues.Length; i++)
513 if (eventValidationValues [i] == hash)
517 throw new ArgumentException ("Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation=\"true\"/> in configuration or <%@ Page EnableEventValidation=\"true\" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.");
520 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
522 if (scriptList == null)
527 while (scriptList != null) {
528 switch (scriptList.Format) {
529 case ScriptEntryFormat.AddScriptTag:
530 EnsureBeginScriptBlock (writer);
531 writer.Write (scriptList.Script);
533 case ScriptEntryFormat.Include:
534 EnsureEndScriptBlock (writer);
535 WriteClientScriptInclude (writer, scriptList.Script, scriptList.Type, scriptList.Key);
538 EnsureEndScriptBlock (writer);
539 writer.WriteLine (scriptList.Script);
542 scriptList = scriptList.Next;
544 EnsureEndScriptBlock (writer);
547 bool _scriptTagOpened;
549 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
550 if (!_scriptTagOpened) {
551 WriteBeginScriptBlock (writer);
552 _scriptTagOpened = true;
556 void EnsureEndScriptBlock (HtmlTextWriter writer) {
557 if (_scriptTagOpened) {
558 WriteEndScriptBlock (writer);
559 _scriptTagOpened = false;
564 internal void RestoreEventValidationState (string fieldValue)
566 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
568 IStateFormatter fmt = page.GetFormatter ();
569 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
570 eventValidationPos = eventValidationValues.Length;
573 internal void SaveEventValidationState ()
575 if (!page.EnableEventValidation)
578 string eventValidation = GetEventValidationStateFormatted ();
579 if (eventValidation == null)
582 RegisterHiddenField (EventStateFieldName, eventValidation);
585 internal string GetEventValidationStateFormatted ()
587 if (eventValidationValues == null || eventValidationValues.Length == 0)
590 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
593 IStateFormatter fmt = page.GetFormatter ();
594 int [] array = new int [eventValidationPos];
595 Array.Copy (eventValidationValues, array, eventValidationPos);
596 return fmt.Serialize (array);
599 internal void WriteExpandoAttributes (HtmlTextWriter writer)
601 if (expandoAttributes == null)
605 WriteBeginScriptBlock (writer);
607 foreach (string controlId in expandoAttributes.Keys) {
608 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
609 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
610 foreach (string attributeName in attrs.Keys) {
611 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
614 WriteEndScriptBlock (writer);
621 internal const string SCRIPT_BLOCK_START = "//<![CDATA[";
622 internal const string SCRIPT_BLOCK_END = "//]]>";
624 internal const string SCRIPT_BLOCK_START = "<!--";
625 internal const string SCRIPT_BLOCK_END ="// -->";
628 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
630 writer.WriteLine ("<script"+
632 " language=\"javascript\""+
634 " type=\"text/javascript\">");
635 writer.WriteLine (SCRIPT_BLOCK_START);
638 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
640 writer.WriteLine (SCRIPT_BLOCK_END);
641 writer.WriteLine ("</script>");
644 internal void WriteHiddenFields (HtmlTextWriter writer)
646 if (hiddenFields == null)
650 writer.RenderBeginTag (HtmlTextWriterTag.Div);
652 foreach (string key in hiddenFields.Keys) {
653 string value = hiddenFields [key] as string;
654 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
657 writer.RenderEndTag (); // DIV
662 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path, Type type, string key) {
663 if (!page.IsMultiForm)
664 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
666 string scriptKey = "inc_" + (type.FullName + key).GetHashCode ().ToString ("X");
667 writer.WriteLine ("<script type=\"text/javascript\">");
668 writer.WriteLine (SCRIPT_BLOCK_START);
669 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
670 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
671 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
672 writer.WriteLine (SCRIPT_BLOCK_END);
673 writer.WriteLine ("</script>");
677 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
679 WriteScripts (writer, clientScriptBlocks);
682 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
684 WriteScripts (writer, startupScriptBlocks);
687 internal void WriteArrayDeclares (HtmlTextWriter writer)
689 if (registeredArrayDeclares != null) {
691 WriteBeginScriptBlock (writer);
692 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
693 while (arrayEnum.MoveNext()) {
694 if (page.IsMultiForm)
695 writer.Write ("\t" + page.theForm + ".");
697 writer.Write ("\tvar ");
698 writer.Write(arrayEnum.Key);
699 writer.Write(" = new Array(");
700 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
702 while (arrayListEnum.MoveNext()) {
707 writer.Write(arrayListEnum.Current);
709 writer.WriteLine(");");
711 WriteEndScriptBlock (writer);
717 internal string GetClientValidationEvent (string validationGroup) {
718 if (page.IsMultiForm)
719 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
720 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
724 internal string GetClientValidationEvent ()
726 if (page.IsMultiForm)
727 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
728 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
732 internal string WriteSubmitStatements ()
734 if (submitStatements == null) return null;
736 StringBuilder sb = new StringBuilder ();
737 ScriptEntry entry = submitStatements;
738 while (entry != null) {
740 sb.Append (EnsureEndsWithSemicolon (entry.Script));
742 sb.Append (entry.Script);
747 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
749 " + (page.IsMultiForm ? page.theForm + "." : null) + @"WebForm_OnSubmit = function () {
750 " + sb.ToString () + @"
754 if (page.IsMultiForm)
755 return "javascript:return " + page.theForm + ".WebForm_OnSubmit();";
757 return "javascript:return WebForm_OnSubmit();";
760 return sb.ToString ();
764 internal static string GetScriptLiteral (object ob)
768 else if (ob is string) {
769 string s = (string)ob;
773 for (int i = 0; i < len; i++)
774 if (s [i] == '\\' || s [i] == '\"') {
780 return string.Concat ("\"", s, "\"");
782 StringBuilder sb = new StringBuilder (len + 10);
785 for (int si = 0; si < len; si++) {
788 else if (s [si] == '\\')
795 return sb.ToString ();
796 } else if (ob is bool) {
797 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
799 return ob.ToString ();
803 sealed class ScriptEntry
805 public readonly Type Type;
806 public readonly string Key;
807 public readonly string Script;
808 public readonly ScriptEntryFormat Format;
809 public ScriptEntry Next;
811 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
819 enum ScriptEntryFormat
828 internal static string EnsureEndsWithSemicolon (string value) {
829 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')