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;
68 bool _initCallBackRegistered;
71 internal ClientScriptManager (Page page)
77 public string GetPostBackClientEvent (Control control, string argument)
79 return GetPostBackEventReference (control, argument);
83 public string GetPostBackClientHyperlink (Control control, string argument)
85 return "javascript:" + GetPostBackEventReference (control, argument);
89 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
91 if (registerForEventValidation)
92 RegisterForEventValidation (control.UniqueID, argument);
93 return "javascript:" + GetPostBackEventReference (control, argument);
102 string GetPostBackEventReference (Control control, string argument)
105 throw new ArgumentNullException ("control");
107 page.RequiresPostBackScript ();
109 return page.theForm + ".__doPostBack('" + control.UniqueID + "','" + argument + "')";
111 return "__doPostBack('" + control.UniqueID + "','" + argument + "')";
115 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
118 throw new ArgumentNullException ("control");
120 if (registerForEventValidation)
121 RegisterForEventValidation (control.UniqueID, argument);
122 return GetPostBackEventReference (control, argument);
125 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
128 throw new ArgumentNullException ("options");
129 if (registerForEventValidation)
130 RegisterForEventValidation (options);
131 return GetPostBackEventReference (options);
134 public string GetPostBackEventReference (PostBackOptions options)
137 throw new ArgumentNullException ("options");
139 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
140 !options.AutoPostBack && !options.PerformValidation)
142 if (!options.ClientSubmit)
145 if (options.RequiresJavaScriptProtocol)
146 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
148 return GetPostBackEventReference (options.TargetControl, options.Argument);
151 RegisterWebFormClientScript ();
153 string actionUrl = options.ActionUrl;
154 if (actionUrl != null)
155 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
157 if(options.TrackFocus)
158 RegisterHiddenField (Page.LastFocusID, String.Empty);
160 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
161 if (page.IsMultiForm)
162 prefix += page.theForm + ".";
164 return prefix + "WebForm_DoPostback(" +
165 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID) + "," +
166 ClientScriptManager.GetScriptLiteral (options.Argument) + "," +
167 ClientScriptManager.GetScriptLiteral (actionUrl) + "," +
168 ClientScriptManager.GetScriptLiteral (options.AutoPostBack) + "," +
169 ClientScriptManager.GetScriptLiteral (options.PerformValidation) + "," +
170 ClientScriptManager.GetScriptLiteral (options.TrackFocus) + "," +
171 ClientScriptManager.GetScriptLiteral (options.ClientSubmit) + "," +
172 ClientScriptManager.GetScriptLiteral (options.ValidationGroup) + ")";
175 internal void RegisterWebFormClientScript ()
177 if (_webFormClientScriptRequired)
180 page.RequiresPostBackScript ();
181 _webFormClientScriptRequired = true;
184 bool _webFormClientScriptRendered;
185 bool _webFormClientScriptRequired;
187 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
188 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
190 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"), typeof (Page), "webform.js");
191 WriteBeginScriptBlock (writer);
192 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
193 WriteEndScriptBlock (writer);
194 _webFormClientScriptRendered = true;
198 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
200 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
203 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
205 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
208 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
211 throw new ArgumentNullException ("control");
212 if(!(control is ICallbackEventHandler))
213 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
215 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
218 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
220 RegisterWebFormClientScript ();
222 if (!_initCallBackRegistered) {
223 _initCallBackRegistered = true;
224 RegisterStartupScript (typeof (Page), "WebForm_InitCallback", page.WebFormScriptReference + ".WebForm_InitCallback();", true);
226 return page.WebFormScriptReference + ".WebForm_DoCallback(" +
228 (argument ?? "null") + "," +
229 clientCallback + "," +
230 (context ?? "null") + "," +
231 (clientErrorCallback ?? "null") + "," +
232 (useAsync ? "true" : "false") + ")";
241 string GetWebResourceUrl(Type type, string resourceName)
244 throw new ArgumentNullException ("type");
246 if (resourceName == null || resourceName.Length == 0)
247 throw new ArgumentNullException ("type");
249 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
253 public bool IsClientScriptBlockRegistered (string key)
255 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
258 public bool IsClientScriptBlockRegistered (Type type, string key)
260 return IsScriptRegistered (clientScriptBlocks, type, key);
263 public bool IsStartupScriptRegistered (string key)
265 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
268 public bool IsStartupScriptRegistered (Type type, string key)
270 return IsScriptRegistered (startupScriptBlocks, type, key);
273 public bool IsOnSubmitStatementRegistered (string key)
275 return IsScriptRegistered (submitStatements, GetType(), key);
278 public bool IsOnSubmitStatementRegistered (Type type, string key)
280 return IsScriptRegistered (submitStatements, type, key);
283 public bool IsClientScriptIncludeRegistered (string key)
285 return IsClientScriptIncludeRegistered (GetType (), key);
288 public bool IsClientScriptIncludeRegistered (Type type, string key)
290 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
293 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
295 while (scriptList != null) {
296 if (scriptList.Type == type && scriptList.Key == key)
298 scriptList = scriptList.Next;
303 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
305 if (registeredArrayDeclares == null)
306 registeredArrayDeclares = new Hashtable();
308 if (!registeredArrayDeclares.ContainsKey (arrayName))
309 registeredArrayDeclares.Add (arrayName, new ArrayList());
311 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
314 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
316 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
319 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
321 ScriptEntry last = null;
322 ScriptEntry entry = scriptList;
324 while (entry != null) {
325 if (entry.Type == type && entry.Key == key)
331 entry = new ScriptEntry (type, key, script, format);
333 if (last != null) last.Next = entry;
334 else scriptList = entry;
337 internal void RegisterClientScriptBlock (string key, string script)
339 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
342 public void RegisterClientScriptBlock (Type type, string key, string script)
344 RegisterClientScriptBlock (type, key, script, false);
347 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
350 throw new ArgumentNullException ("type");
352 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
355 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
357 if (hiddenFields == null)
358 hiddenFields = new Hashtable ();
360 if (!hiddenFields.ContainsKey (hiddenFieldName))
361 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
364 internal void RegisterOnSubmitStatement (string key, string script)
366 RegisterScript (ref submitStatements, GetType (), key, script, false);
369 public void RegisterOnSubmitStatement (Type type, string key, string script)
372 throw new ArgumentNullException ("type");
374 RegisterScript (ref submitStatements, type, key, script, false);
377 internal void RegisterStartupScript (string key, string script)
379 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
382 public void RegisterStartupScript (Type type, string key, string script)
384 RegisterStartupScript (type, key, script, false);
387 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
390 throw new ArgumentNullException ("type");
392 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
395 public void RegisterClientScriptInclude (string key, string url)
397 RegisterClientScriptInclude (GetType (), key, url);
400 public void RegisterClientScriptInclude (Type type, string key, string url)
403 throw new ArgumentNullException ("type");
404 if (url == null || url.Length == 0)
405 throw new ArgumentException ("url");
407 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
411 public void RegisterClientScriptResource (Type type, string resourceName)
413 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
416 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
418 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
421 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
423 if (controlId == null)
424 throw new ArgumentNullException ("controlId");
426 if (attributeName == null)
427 throw new ArgumentNullException ("attributeName");
429 if (expandoAttributes == null)
430 expandoAttributes = new Hashtable ();
432 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
434 list = new ListDictionary ();
435 expandoAttributes [controlId] = list;
438 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
441 private void EnsureEventValidationArray ()
443 if (eventValidationValues == null || eventValidationValues.Length == 0)
444 eventValidationValues = new int [64];
446 int len = eventValidationValues.Length;
448 if (eventValidationPos >= len) {
449 int [] tmp = new int [len * 2];
450 Array.Copy (eventValidationValues, tmp, len);
451 eventValidationValues = tmp;
455 internal void ResetEventValidationState ()
457 _pageInRender = true;
458 eventValidationPos = 0;
461 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
462 private int CalculateEventHash (string uniqueId, string argument)
464 int uniqueIdHash = uniqueId.GetHashCode ();
465 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
466 return (uniqueIdHash ^ argumentHash);
469 public void RegisterForEventValidation (PostBackOptions options)
471 // MS.NET does not check for options == null, so we won't too...
472 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
475 public void RegisterForEventValidation (string uniqueId)
477 RegisterForEventValidation (uniqueId, null);
480 public void RegisterForEventValidation (string uniqueId, string argument)
482 if (!page.EnableEventValidation)
484 if (uniqueId == null || uniqueId.Length == 0)
487 _hasRegisteredForEventValidationOnCallback = true;
488 else if (!_pageInRender)
489 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
491 EnsureEventValidationArray ();
493 int hash = CalculateEventHash (uniqueId, argument);
494 for (int i = 0; i < eventValidationPos; i++)
495 if (eventValidationValues [i] == hash)
497 eventValidationValues [eventValidationPos++] = hash;
500 public void ValidateEvent (string uniqueId)
502 ValidateEvent (uniqueId, null);
505 public void ValidateEvent (string uniqueId, string argument)
507 if (uniqueId == null || uniqueId.Length == 0)
508 throw new ArgumentException ("must not be null or empty", "uniqueId");
509 if (!page.EnableEventValidation)
511 if (eventValidationValues == null)
514 int hash = CalculateEventHash (uniqueId, argument);
515 for (int i = 0; i < eventValidationValues.Length; i++)
516 if (eventValidationValues [i] == hash)
520 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.");
523 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
525 if (scriptList == null)
530 while (scriptList != null) {
531 switch (scriptList.Format) {
532 case ScriptEntryFormat.AddScriptTag:
533 EnsureBeginScriptBlock (writer);
534 writer.Write (scriptList.Script);
536 case ScriptEntryFormat.Include:
537 EnsureEndScriptBlock (writer);
538 WriteClientScriptInclude (writer, scriptList.Script, scriptList.Type, scriptList.Key);
541 EnsureEndScriptBlock (writer);
542 writer.WriteLine (scriptList.Script);
545 scriptList = scriptList.Next;
547 EnsureEndScriptBlock (writer);
550 bool _scriptTagOpened;
552 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
553 if (!_scriptTagOpened) {
554 WriteBeginScriptBlock (writer);
555 _scriptTagOpened = true;
559 void EnsureEndScriptBlock (HtmlTextWriter writer) {
560 if (_scriptTagOpened) {
561 WriteEndScriptBlock (writer);
562 _scriptTagOpened = false;
567 internal void RestoreEventValidationState (string fieldValue)
569 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
571 IStateFormatter fmt = page.GetFormatter ();
572 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
573 eventValidationPos = eventValidationValues.Length;
576 internal void SaveEventValidationState ()
578 if (!page.EnableEventValidation)
581 string eventValidation = GetEventValidationStateFormatted ();
582 if (eventValidation == null)
585 RegisterHiddenField (EventStateFieldName, eventValidation);
588 internal string GetEventValidationStateFormatted ()
590 if (eventValidationValues == null || eventValidationValues.Length == 0)
593 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
596 IStateFormatter fmt = page.GetFormatter ();
597 int [] array = new int [eventValidationPos];
598 Array.Copy (eventValidationValues, array, eventValidationPos);
599 return fmt.Serialize (array);
602 internal void WriteExpandoAttributes (HtmlTextWriter writer)
604 if (expandoAttributes == null)
608 WriteBeginScriptBlock (writer);
610 foreach (string controlId in expandoAttributes.Keys) {
611 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
612 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
613 foreach (string attributeName in attrs.Keys) {
614 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
617 WriteEndScriptBlock (writer);
624 internal const string SCRIPT_BLOCK_START = "//<![CDATA[";
625 internal const string SCRIPT_BLOCK_END = "//]]>";
627 internal const string SCRIPT_BLOCK_START = "<!--";
628 internal const string SCRIPT_BLOCK_END ="// -->";
631 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
633 writer.WriteLine ("<script"+
635 " language=\"javascript\""+
637 " type=\"text/javascript\">");
638 writer.WriteLine (SCRIPT_BLOCK_START);
641 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
643 writer.WriteLine (SCRIPT_BLOCK_END);
644 writer.WriteLine ("</script>");
647 internal void WriteHiddenFields (HtmlTextWriter writer)
649 if (hiddenFields == null)
653 writer.RenderBeginTag (HtmlTextWriterTag.Div);
655 foreach (string key in hiddenFields.Keys) {
656 string value = hiddenFields [key] as string;
657 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
660 writer.RenderEndTag (); // DIV
665 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path, Type type, string key) {
666 if (!page.IsMultiForm)
667 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
669 string scriptKey = "inc_" + (type.FullName + key).GetHashCode ().ToString ("X");
670 writer.WriteLine ("<script type=\"text/javascript\">");
671 writer.WriteLine (SCRIPT_BLOCK_START);
672 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
673 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
674 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
675 writer.WriteLine (SCRIPT_BLOCK_END);
676 writer.WriteLine ("</script>");
680 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
682 WriteScripts (writer, clientScriptBlocks);
685 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
687 WriteScripts (writer, startupScriptBlocks);
690 internal void WriteArrayDeclares (HtmlTextWriter writer)
692 if (registeredArrayDeclares != null) {
694 WriteBeginScriptBlock (writer);
695 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
696 while (arrayEnum.MoveNext()) {
697 if (page.IsMultiForm)
698 writer.Write ("\t" + page.theForm + ".");
700 writer.Write ("\tvar ");
701 writer.Write(arrayEnum.Key);
702 writer.Write(" = new Array(");
703 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
705 while (arrayListEnum.MoveNext()) {
710 writer.Write(arrayListEnum.Current);
712 writer.WriteLine(");");
714 WriteEndScriptBlock (writer);
720 internal string GetClientValidationEvent (string validationGroup) {
721 if (page.IsMultiForm)
722 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
723 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
727 internal string GetClientValidationEvent ()
729 if (page.IsMultiForm)
730 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
731 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
735 internal string WriteSubmitStatements ()
737 if (submitStatements == null) return null;
739 StringBuilder sb = new StringBuilder ();
740 ScriptEntry entry = submitStatements;
741 while (entry != null) {
743 sb.Append (EnsureEndsWithSemicolon (entry.Script));
745 sb.Append (entry.Script);
750 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
752 " + page.WebFormScriptReference + @".WebForm_OnSubmit = function () {
753 " + sb.ToString () + @"
757 return "javascript:return " + page.WebFormScriptReference + ".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] != ';')