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;
69 internal ClientScriptManager (Page page)
75 public string GetPostBackClientEvent (Control control, string argument)
77 return GetPostBackEventReference (control, argument);
81 public string GetPostBackClientHyperlink (Control control, string argument)
83 return "javascript:" + GetPostBackEventReference (control, argument);
87 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
89 if (registerForEventValidation)
90 RegisterForEventValidation (control.UniqueID, argument);
91 return "javascript:" + GetPostBackEventReference (control, argument);
100 string GetPostBackEventReference (Control control, string argument)
103 throw new ArgumentNullException ("control");
105 page.RequiresPostBackScript ();
107 return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
109 return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
113 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
116 throw new ArgumentNullException ("control");
118 if (registerForEventValidation)
119 RegisterForEventValidation (control.UniqueID, argument);
120 return GetPostBackEventReference (control, argument);
123 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
126 throw new ArgumentNullException ("options");
127 if (registerForEventValidation)
128 RegisterForEventValidation (options);
129 return GetPostBackEventReference (options);
132 public string GetPostBackEventReference (PostBackOptions options)
135 throw new ArgumentNullException ("options");
137 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
138 !options.AutoPostBack && !options.PerformValidation)
140 if (!options.ClientSubmit)
143 if (options.RequiresJavaScriptProtocol)
144 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
146 return GetPostBackEventReference (options.TargetControl, options.Argument);
149 RegisterWebFormClientScript ();
151 string actionUrl = options.ActionUrl;
152 if (actionUrl != null)
153 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
155 if(options.TrackFocus)
156 RegisterHiddenField (Page.LastFocusID, String.Empty);
158 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
159 if (page.IsMultiForm)
160 prefix += page.theForm + ".";
162 // Allow the page to transform ActionUrl to a portlet action url
163 if (actionUrl != null && page.PortletNamespace != null) {
164 actionUrl = page.CreateActionUrl(actionUrl);
169 return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8})",
171 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID),
172 ClientScriptManager.GetScriptLiteral (options.Argument),
173 ClientScriptManager.GetScriptLiteral (actionUrl),
174 ClientScriptManager.GetScriptLiteral (options.AutoPostBack),
175 ClientScriptManager.GetScriptLiteral (options.PerformValidation),
176 ClientScriptManager.GetScriptLiteral (options.TrackFocus),
177 ClientScriptManager.GetScriptLiteral (options.ClientSubmit),
178 ClientScriptManager.GetScriptLiteral (options.ValidationGroup)
182 internal void RegisterWebFormClientScript ()
184 if (_webFormClientScriptRequired)
187 page.RequiresPostBackScript ();
188 _webFormClientScriptRequired = true;
191 bool _webFormClientScriptRendered;
192 bool _webFormClientScriptRequired;
194 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
195 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
197 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"));
198 WriteBeginScriptBlock (writer);
199 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
200 WriteEndScriptBlock (writer);
201 _webFormClientScriptRendered = true;
205 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
207 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
210 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
212 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
215 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
218 throw new ArgumentNullException ("control");
219 if(!(control is ICallbackEventHandler))
220 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
222 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
225 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
227 RegisterWebFormClientScript ();
229 return string.Format ("{6}WebForm_DoCallback({0},{1},{2},{3},{4},{5})", target, ((argument == null) ? "null" : argument), clientCallback, ((context == null) ? "null" : context), ((clientErrorCallback == null) ? "null" : clientErrorCallback), (useAsync ? "true" : "false"), (page.IsMultiForm ? page.theForm + "." : null));
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 eventValidationPos = 0;
457 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
458 private int CalculateEventHash (string uniqueId, string argument)
460 int uniqueIdHash = uniqueId.GetHashCode ();
461 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
462 return (uniqueIdHash ^ argumentHash);
465 public void RegisterForEventValidation (PostBackOptions options)
467 // MS.NET does not check for options == null, so we won't too...
468 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
471 public void RegisterForEventValidation (string uniqueId)
473 RegisterForEventValidation (uniqueId, null);
476 public void RegisterForEventValidation (string uniqueId, string argument)
478 if (!page.EnableEventValidation)
480 if (uniqueId == null || uniqueId.Length == 0)
483 _hasRegisteredForEventValidationOnCallback = true;
484 else if (page.LifeCycle < PageLifeCycle.Render)
485 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
487 EnsureEventValidationArray ();
489 int hash = CalculateEventHash (uniqueId, argument);
490 for (int i = 0; i < eventValidationPos; i++)
491 if (eventValidationValues [i] == hash)
493 eventValidationValues [eventValidationPos++] = hash;
496 public void ValidateEvent (string uniqueId)
498 ValidateEvent (uniqueId, null);
501 public void ValidateEvent (string uniqueId, string argument)
503 if (uniqueId == null || uniqueId.Length == 0)
504 throw new ArgumentException ("must not be null or empty", "uniqueId");
505 if (!page.EnableEventValidation)
507 if (eventValidationValues == null)
510 int hash = CalculateEventHash (uniqueId, argument);
511 for (int i = 0; i < eventValidationValues.Length; i++)
512 if (eventValidationValues [i] == hash)
516 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.");
519 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
521 if (scriptList == null)
526 while (scriptList != null) {
527 switch (scriptList.Format) {
528 case ScriptEntryFormat.AddScriptTag:
529 EnsureBeginScriptBlock (writer);
530 writer.Write (scriptList.Script);
532 case ScriptEntryFormat.Include:
533 EnsureEndScriptBlock (writer);
534 WriteClientScriptInclude (writer, scriptList.Script);
537 EnsureEndScriptBlock (writer);
538 writer.WriteLine (scriptList.Script);
541 scriptList = scriptList.Next;
543 EnsureEndScriptBlock (writer);
546 bool _scriptTagOpened;
548 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
549 if (!_scriptTagOpened) {
550 WriteBeginScriptBlock (writer);
551 _scriptTagOpened = true;
555 void EnsureEndScriptBlock (HtmlTextWriter writer) {
556 if (_scriptTagOpened) {
557 WriteEndScriptBlock (writer);
558 _scriptTagOpened = false;
563 internal void RestoreEventValidationState (string fieldValue)
565 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
567 IStateFormatter fmt = page.GetFormatter ();
568 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
569 eventValidationPos = eventValidationValues.Length;
572 internal void SaveEventValidationState ()
574 if (!page.EnableEventValidation)
577 string eventValidation = GetEventValidationStateFormatted ();
578 if (eventValidation == null)
581 RegisterHiddenField (EventStateFieldName, eventValidation);
584 internal string GetEventValidationStateFormatted ()
586 if (eventValidationValues == null || eventValidationValues.Length == 0)
589 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
592 IStateFormatter fmt = page.GetFormatter ();
593 int [] array = new int [eventValidationPos];
594 Array.Copy (eventValidationValues, array, eventValidationPos);
595 return fmt.Serialize (array);
598 internal void WriteExpandoAttributes (HtmlTextWriter writer)
600 if (expandoAttributes == null)
604 WriteBeginScriptBlock (writer);
606 foreach (string controlId in expandoAttributes.Keys) {
607 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
608 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
609 foreach (string attributeName in attrs.Keys) {
610 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
613 WriteEndScriptBlock (writer);
620 internal const string SCRIPT_BLOCK_START = "//<![CDATA[";
621 internal const string SCRIPT_BLOCK_END = "//]]>";
623 internal const string SCRIPT_BLOCK_START = "<!--";
624 internal const string SCRIPT_BLOCK_END ="// -->";
627 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
629 writer.WriteLine ("<script"+
631 " language=\"javascript\""+
633 " type=\"text/javascript\">");
634 writer.WriteLine (SCRIPT_BLOCK_START);
637 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
639 writer.WriteLine (SCRIPT_BLOCK_END);
640 writer.WriteLine ("</script>");
643 internal void WriteHiddenFields (HtmlTextWriter writer)
645 if (hiddenFields == null)
648 writer.RenderBeginTag (HtmlTextWriterTag.Div);
649 foreach (string key in hiddenFields.Keys) {
650 string value = hiddenFields [key] as string;
651 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
653 writer.RenderEndTag (); // DIV
657 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path) {
658 if (!page.IsMultiForm)
659 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
661 string scriptKey = "inc_" + path.GetHashCode ().ToString ("X");
662 writer.WriteLine ("<script type=\"text/javascript\">");
663 writer.WriteLine (SCRIPT_BLOCK_START);
664 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
665 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
666 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
667 writer.WriteLine (SCRIPT_BLOCK_END);
668 writer.WriteLine ("</script>");
672 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
674 WriteScripts (writer, clientScriptBlocks);
677 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
679 WriteScripts (writer, startupScriptBlocks);
682 internal void WriteArrayDeclares (HtmlTextWriter writer)
684 if (registeredArrayDeclares != null) {
686 WriteBeginScriptBlock (writer);
687 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
688 while (arrayEnum.MoveNext()) {
689 if (page.IsMultiForm)
690 writer.Write ("\t" + page.theForm + ".");
692 writer.Write ("\tvar ");
693 writer.Write(arrayEnum.Key);
694 writer.Write(" = new Array(");
695 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
697 while (arrayListEnum.MoveNext()) {
702 writer.Write(arrayListEnum.Current);
704 writer.WriteLine(");");
706 WriteEndScriptBlock (writer);
712 internal string GetClientValidationEvent (string validationGroup) {
713 if (page.IsMultiForm)
714 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
715 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
719 internal string GetClientValidationEvent ()
721 if (page.IsMultiForm)
722 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
723 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
727 internal string WriteSubmitStatements ()
729 if (submitStatements == null) return null;
731 StringBuilder sb = new StringBuilder ();
732 ScriptEntry entry = submitStatements;
733 while (entry != null) {
735 sb.Append (EnsureEndsWithSemicolon (entry.Script));
737 sb.Append (entry.Script);
742 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
744 " + (page.IsMultiForm ? page.theForm + "." : null) + @"WebForm_OnSubmit = function () {
745 " + sb.ToString () + @"
749 if (page.IsMultiForm)
750 return "javascript:return " + page.theForm + ".WebForm_OnSubmit();";
752 return "javascript:return WebForm_OnSubmit();";
755 return sb.ToString ();
759 internal static string GetScriptLiteral (object ob)
763 else if (ob is string) {
764 string s = (string)ob;
768 for (int i = 0; i < len; i++)
769 if (s [i] == '\\' || s [i] == '\"') {
775 return string.Concat ("\"", s, "\"");
777 StringBuilder sb = new StringBuilder (len + 10);
780 for (int si = 0; si < len; si++) {
783 else if (s [si] == '\\')
790 return sb.ToString ();
791 } else if (ob is bool) {
792 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
794 return ob.ToString ();
798 sealed class ScriptEntry
800 public readonly Type Type;
801 public readonly string Key;
802 public readonly string Script;
803 public readonly ScriptEntryFormat Format;
804 public ScriptEntry Next;
806 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
814 enum ScriptEntryFormat
823 internal static string EnsureEndsWithSemicolon (string value) {
824 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')