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 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 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 ArgumentException InvalidPostBackException ()
507 return 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.");
510 public void ValidateEvent (string uniqueId, string argument)
512 if (uniqueId == null || uniqueId.Length == 0)
513 throw new ArgumentException ("must not be null or empty", "uniqueId");
514 if (!page.EnableEventValidation)
516 if (eventValidationValues == null)
517 throw InvalidPostBackException ();
519 int hash = CalculateEventHash (uniqueId, argument);
520 for (int i = 0; i < eventValidationValues.Length; i++)
521 if (eventValidationValues [i] == hash)
524 throw InvalidPostBackException ();
527 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
529 if (scriptList == null)
534 while (scriptList != null) {
535 switch (scriptList.Format) {
536 case ScriptEntryFormat.AddScriptTag:
537 EnsureBeginScriptBlock (writer);
538 writer.Write (scriptList.Script);
540 case ScriptEntryFormat.Include:
541 EnsureEndScriptBlock (writer);
542 WriteClientScriptInclude (writer, scriptList.Script, scriptList.Type, scriptList.Key);
545 EnsureEndScriptBlock (writer);
546 writer.WriteLine (scriptList.Script);
549 scriptList = scriptList.Next;
551 EnsureEndScriptBlock (writer);
554 bool _scriptTagOpened;
556 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
557 if (!_scriptTagOpened) {
558 WriteBeginScriptBlock (writer);
559 _scriptTagOpened = true;
563 void EnsureEndScriptBlock (HtmlTextWriter writer) {
564 if (_scriptTagOpened) {
565 WriteEndScriptBlock (writer);
566 _scriptTagOpened = false;
571 internal void RestoreEventValidationState (string fieldValue)
573 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
575 IStateFormatter fmt = page.GetFormatter ();
576 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
577 eventValidationPos = eventValidationValues.Length;
580 internal void SaveEventValidationState ()
582 if (!page.EnableEventValidation)
585 string eventValidation = GetEventValidationStateFormatted ();
586 if (eventValidation == null)
589 RegisterHiddenField (EventStateFieldName, eventValidation);
592 internal string GetEventValidationStateFormatted ()
594 if (eventValidationValues == null || eventValidationValues.Length == 0)
597 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
600 IStateFormatter fmt = page.GetFormatter ();
601 int [] array = new int [eventValidationPos];
602 Array.Copy (eventValidationValues, array, eventValidationPos);
603 return fmt.Serialize (array);
606 internal void WriteExpandoAttributes (HtmlTextWriter writer)
608 if (expandoAttributes == null)
612 WriteBeginScriptBlock (writer);
614 foreach (string controlId in expandoAttributes.Keys) {
615 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
616 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
617 foreach (string attributeName in attrs.Keys) {
618 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
621 WriteEndScriptBlock (writer);
628 internal const string SCRIPT_BLOCK_START = "//<![CDATA[";
629 internal const string SCRIPT_BLOCK_END = "//]]>";
631 internal const string SCRIPT_BLOCK_START = "<!--";
632 internal const string SCRIPT_BLOCK_END ="// -->";
635 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
637 writer.WriteLine ("<script"+
639 " language=\"javascript\""+
641 " type=\"text/javascript\">");
642 writer.WriteLine (SCRIPT_BLOCK_START);
645 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
647 writer.WriteLine (SCRIPT_BLOCK_END);
648 writer.WriteLine ("</script>");
651 internal void WriteHiddenFields (HtmlTextWriter writer)
653 if (hiddenFields == null)
657 writer.RenderBeginTag (HtmlTextWriterTag.Div);
659 foreach (string key in hiddenFields.Keys) {
660 string value = hiddenFields [key] as string;
661 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
664 writer.RenderEndTag (); // DIV
669 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path, Type type, string key) {
670 if (!page.IsMultiForm)
671 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
673 string scriptKey = "inc_" + (type.FullName + key).GetHashCode ().ToString ("X");
674 writer.WriteLine ("<script type=\"text/javascript\">");
675 writer.WriteLine (SCRIPT_BLOCK_START);
676 writer.WriteLine ("if (!window.{0}) {{", scriptKey);
677 writer.WriteLine ("\twindow.{0} = true", scriptKey);
678 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
679 writer.WriteLine (SCRIPT_BLOCK_END);
680 writer.WriteLine ("</script>");
684 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
686 WriteScripts (writer, clientScriptBlocks);
689 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
691 WriteScripts (writer, startupScriptBlocks);
694 internal void WriteArrayDeclares (HtmlTextWriter writer)
696 if (registeredArrayDeclares != null) {
698 WriteBeginScriptBlock (writer);
699 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
700 while (arrayEnum.MoveNext()) {
701 if (page.IsMultiForm)
702 writer.Write ("\t" + page.theForm + ".");
704 writer.Write ("\tvar ");
705 writer.Write(arrayEnum.Key);
706 writer.Write(" = new Array(");
707 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
709 while (arrayListEnum.MoveNext()) {
714 writer.Write(arrayListEnum.Current);
716 writer.WriteLine(");");
718 WriteEndScriptBlock (writer);
724 internal string GetClientValidationEvent (string validationGroup) {
725 if (page.IsMultiForm)
726 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
727 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
731 internal string GetClientValidationEvent ()
733 if (page.IsMultiForm)
734 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
735 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
739 internal string WriteSubmitStatements ()
741 if (submitStatements == null) return null;
743 StringBuilder sb = new StringBuilder ();
744 ScriptEntry entry = submitStatements;
745 while (entry != null) {
747 sb.Append (EnsureEndsWithSemicolon (entry.Script));
749 sb.Append (entry.Script);
754 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
756 " + page.WebFormScriptReference + @".WebForm_OnSubmit = function () {
757 " + sb.ToString () + @"
761 return "javascript:return " + page.WebFormScriptReference + ".WebForm_OnSubmit();";
764 return sb.ToString ();
768 internal static string GetScriptLiteral (object ob)
772 else if (ob is string) {
773 string s = (string)ob;
777 for (int i = 0; i < len; i++)
778 if (s [i] == '\\' || s [i] == '\"') {
784 return string.Concat ("\"", s, "\"");
786 StringBuilder sb = new StringBuilder (len + 10);
789 for (int si = 0; si < len; si++) {
792 else if (s [si] == '\\')
799 return sb.ToString ();
800 } else if (ob is bool) {
801 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
803 return ob.ToString ();
807 sealed class ScriptEntry
809 public readonly Type Type;
810 public readonly string Key;
811 public readonly string Script;
812 public readonly ScriptEntryFormat Format;
813 public ScriptEntry Next;
815 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
823 enum ScriptEntryFormat
832 internal static string EnsureEndsWithSemicolon (string value) {
833 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')