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 // Allow the page to transform ActionUrl to a portlet action url
164 if (actionUrl != null) {
165 actionUrl = page.CreateActionUrl(actionUrl);
170 return prefix + "WebForm_DoPostback(" +
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) + ")";
181 internal void RegisterWebFormClientScript ()
183 if (_webFormClientScriptRequired)
186 page.RequiresPostBackScript ();
187 _webFormClientScriptRequired = true;
190 bool _webFormClientScriptRendered;
191 bool _webFormClientScriptRequired;
193 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
194 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
196 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"), typeof (Page), "webform.js");
197 WriteBeginScriptBlock (writer);
198 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
199 WriteEndScriptBlock (writer);
200 _webFormClientScriptRendered = true;
204 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
206 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
209 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
211 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
214 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
217 throw new ArgumentNullException ("control");
218 if(!(control is ICallbackEventHandler))
219 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
221 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
224 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
226 RegisterWebFormClientScript ();
228 string formReference = page.IsMultiForm ? page.theForm + "." : String.Empty;
230 return formReference + "WebForm_DoCallback(" +
232 ((argument == null) ? "null" : argument) + "," +
233 clientCallback + "," +
234 ((context == null) ? "null" : context) + "," +
235 ((clientErrorCallback == null) ? "null" : clientErrorCallback) + "," +
236 (useAsync ? "true" : "false") + ")";
245 string GetWebResourceUrl(Type type, string resourceName)
248 throw new ArgumentNullException ("type");
250 if (resourceName == null || resourceName.Length == 0)
251 throw new ArgumentNullException ("type");
253 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
257 public bool IsClientScriptBlockRegistered (string key)
259 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
262 public bool IsClientScriptBlockRegistered (Type type, string key)
264 return IsScriptRegistered (clientScriptBlocks, type, key);
267 public bool IsStartupScriptRegistered (string key)
269 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
272 public bool IsStartupScriptRegistered (Type type, string key)
274 return IsScriptRegistered (startupScriptBlocks, type, key);
277 public bool IsOnSubmitStatementRegistered (string key)
279 return IsScriptRegistered (submitStatements, GetType(), key);
282 public bool IsOnSubmitStatementRegistered (Type type, string key)
284 return IsScriptRegistered (submitStatements, type, key);
287 public bool IsClientScriptIncludeRegistered (string key)
289 return IsClientScriptIncludeRegistered (GetType (), key);
292 public bool IsClientScriptIncludeRegistered (Type type, string key)
294 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
297 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
299 while (scriptList != null) {
300 if (scriptList.Type == type && scriptList.Key == key)
302 scriptList = scriptList.Next;
307 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
309 if (registeredArrayDeclares == null)
310 registeredArrayDeclares = new Hashtable();
312 if (!registeredArrayDeclares.ContainsKey (arrayName))
313 registeredArrayDeclares.Add (arrayName, new ArrayList());
315 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
318 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
320 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
323 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
325 ScriptEntry last = null;
326 ScriptEntry entry = scriptList;
328 while (entry != null) {
329 if (entry.Type == type && entry.Key == key)
335 entry = new ScriptEntry (type, key, script, format);
337 if (last != null) last.Next = entry;
338 else scriptList = entry;
341 internal void RegisterClientScriptBlock (string key, string script)
343 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
346 public void RegisterClientScriptBlock (Type type, string key, string script)
348 RegisterClientScriptBlock (type, key, script, false);
351 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
354 throw new ArgumentNullException ("type");
356 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
359 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
361 if (hiddenFields == null)
362 hiddenFields = new Hashtable ();
364 if (!hiddenFields.ContainsKey (hiddenFieldName))
365 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
368 internal void RegisterOnSubmitStatement (string key, string script)
370 RegisterScript (ref submitStatements, GetType (), key, script, false);
373 public void RegisterOnSubmitStatement (Type type, string key, string script)
376 throw new ArgumentNullException ("type");
378 RegisterScript (ref submitStatements, type, key, script, false);
381 internal void RegisterStartupScript (string key, string script)
383 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
386 public void RegisterStartupScript (Type type, string key, string script)
388 RegisterStartupScript (type, key, script, false);
391 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
394 throw new ArgumentNullException ("type");
396 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
399 public void RegisterClientScriptInclude (string key, string url)
401 RegisterClientScriptInclude (GetType (), key, url);
404 public void RegisterClientScriptInclude (Type type, string key, string url)
407 throw new ArgumentNullException ("type");
408 if (url == null || url.Length == 0)
409 throw new ArgumentException ("url");
411 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
415 public void RegisterClientScriptResource (Type type, string resourceName)
417 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
420 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
422 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
425 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
427 if (controlId == null)
428 throw new ArgumentNullException ("controlId");
430 if (attributeName == null)
431 throw new ArgumentNullException ("attributeName");
433 if (expandoAttributes == null)
434 expandoAttributes = new Hashtable ();
436 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
438 list = new ListDictionary ();
439 expandoAttributes [controlId] = list;
442 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
445 private void EnsureEventValidationArray ()
447 if (eventValidationValues == null || eventValidationValues.Length == 0)
448 eventValidationValues = new int [64];
450 int len = eventValidationValues.Length;
452 if (eventValidationPos >= len) {
453 int [] tmp = new int [len * 2];
454 Array.Copy (eventValidationValues, tmp, len);
455 eventValidationValues = tmp;
459 internal void ResetEventValidationState ()
461 _pageInRender = true;
462 eventValidationPos = 0;
465 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
466 private int CalculateEventHash (string uniqueId, string argument)
468 int uniqueIdHash = uniqueId.GetHashCode ();
469 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
470 return (uniqueIdHash ^ argumentHash);
473 public void RegisterForEventValidation (PostBackOptions options)
475 // MS.NET does not check for options == null, so we won't too...
476 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
479 public void RegisterForEventValidation (string uniqueId)
481 RegisterForEventValidation (uniqueId, null);
484 public void RegisterForEventValidation (string uniqueId, string argument)
486 if (!page.EnableEventValidation)
488 if (uniqueId == null || uniqueId.Length == 0)
491 _hasRegisteredForEventValidationOnCallback = true;
492 else if (!_pageInRender)
493 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
495 EnsureEventValidationArray ();
497 int hash = CalculateEventHash (uniqueId, argument);
498 for (int i = 0; i < eventValidationPos; i++)
499 if (eventValidationValues [i] == hash)
501 eventValidationValues [eventValidationPos++] = hash;
504 public void ValidateEvent (string uniqueId)
506 ValidateEvent (uniqueId, null);
509 public void ValidateEvent (string uniqueId, string argument)
511 if (uniqueId == null || uniqueId.Length == 0)
512 throw new ArgumentException ("must not be null or empty", "uniqueId");
513 if (!page.EnableEventValidation)
515 if (eventValidationValues == null)
518 int hash = CalculateEventHash (uniqueId, argument);
519 for (int i = 0; i < eventValidationValues.Length; i++)
520 if (eventValidationValues [i] == hash)
524 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.");
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 (document.{0} == null) {{", scriptKey);
677 writer.WriteLine ("\tdocument.{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.IsMultiForm ? page.theForm + "." : null) + @"WebForm_OnSubmit = function () {
757 " + sb.ToString () + @"
761 if (page.IsMultiForm)
762 return "javascript:return " + page.theForm + ".WebForm_OnSubmit();";
764 return "javascript:return WebForm_OnSubmit();";
767 return sb.ToString ();
771 internal static string GetScriptLiteral (object ob)
775 else if (ob is string) {
776 string s = (string)ob;
780 for (int i = 0; i < len; i++)
781 if (s [i] == '\\' || s [i] == '\"') {
787 return string.Concat ("\"", s, "\"");
789 StringBuilder sb = new StringBuilder (len + 10);
792 for (int si = 0; si < len; si++) {
795 else if (s [si] == '\\')
802 return sb.ToString ();
803 } else if (ob is bool) {
804 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
806 return ob.ToString ();
810 sealed class ScriptEntry
812 public readonly Type Type;
813 public readonly string Key;
814 public readonly string Script;
815 public readonly ScriptEntryFormat Format;
816 public ScriptEntry Next;
818 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
826 enum ScriptEntryFormat
835 internal static string EnsureEndsWithSemicolon (string value) {
836 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')