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 Hashtable registeredArrayDeclares;
55 ScriptEntry clientScriptBlocks;
56 ScriptEntry startupScriptBlocks;
57 internal Hashtable hiddenFields;
58 ScriptEntry submitStatements;
61 int [] eventValidationValues;
62 int eventValidationPos = 0;
63 Hashtable expandoAttributes;
64 bool _hasRegisteredForEventValidationOnCallback;
67 internal ClientScriptManager (Page page)
73 public string GetPostBackClientEvent (Control control, string argument)
75 return GetPostBackEventReference (control, argument);
79 public string GetPostBackClientHyperlink (Control control, string argument)
81 return "javascript:" + GetPostBackEventReference (control, argument);
85 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
87 if (registerForEventValidation)
88 RegisterForEventValidation (control.UniqueID, argument);
89 return "javascript:" + GetPostBackEventReference (control, argument);
98 string GetPostBackEventReference (Control control, string argument)
101 throw new ArgumentNullException ("control");
103 page.RequiresPostBackScript ();
105 return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
107 return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
111 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
114 throw new ArgumentNullException ("control");
116 if (registerForEventValidation)
117 RegisterForEventValidation (control.UniqueID, argument);
118 return GetPostBackEventReference (control, argument);
121 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
124 throw new ArgumentNullException ("options");
125 if (registerForEventValidation)
126 RegisterForEventValidation (options);
127 return GetPostBackEventReference (options);
130 public string GetPostBackEventReference (PostBackOptions options)
133 throw new ArgumentNullException ("options");
135 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
136 !options.AutoPostBack && !options.PerformValidation)
138 if (!options.ClientSubmit)
141 if (options.RequiresJavaScriptProtocol)
142 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
144 return GetPostBackEventReference (options.TargetControl, options.Argument);
147 RegisterWebFormClientScript ();
149 string actionUrl = options.ActionUrl;
150 if (actionUrl != null)
151 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
153 if(options.TrackFocus)
154 RegisterHiddenField (Page.LastFocusID, String.Empty);
156 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
157 if (page.IsMultiForm)
158 prefix += page.theForm + ".";
160 // Allow the page to transform ActionUrl to a portlet action url
161 if (actionUrl != null && page.PortletNamespace != null) {
162 actionUrl = page.CreateActionUrl(actionUrl);
167 return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8})",
169 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID),
170 ClientScriptManager.GetScriptLiteral (options.Argument),
171 ClientScriptManager.GetScriptLiteral (actionUrl),
172 ClientScriptManager.GetScriptLiteral (options.AutoPostBack),
173 ClientScriptManager.GetScriptLiteral (options.PerformValidation),
174 ClientScriptManager.GetScriptLiteral (options.TrackFocus),
175 ClientScriptManager.GetScriptLiteral (options.ClientSubmit),
176 ClientScriptManager.GetScriptLiteral (options.ValidationGroup)
180 internal void RegisterWebFormClientScript ()
182 if (_webFormClientScriptRequired)
185 page.RequiresPostBackScript ();
186 _webFormClientScriptRequired = true;
189 bool _webFormClientScriptRendered;
190 bool _webFormClientScriptRequired;
192 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
193 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
195 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"));
196 WriteBeginScriptBlock (writer);
197 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
198 WriteEndScriptBlock (writer);
199 _webFormClientScriptRendered = true;
203 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
205 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
208 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
210 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
213 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
216 throw new ArgumentNullException ("control");
217 if(!(control is ICallbackEventHandler))
218 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
220 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
223 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
225 RegisterWebFormClientScript ();
227 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));
236 string GetWebResourceUrl(Type type, string resourceName)
239 throw new ArgumentNullException ("type");
241 if (resourceName == null || resourceName.Length == 0)
242 throw new ArgumentNullException ("type");
244 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
248 public bool IsClientScriptBlockRegistered (string key)
250 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
253 public bool IsClientScriptBlockRegistered (Type type, string key)
255 return IsScriptRegistered (clientScriptBlocks, type, key);
258 public bool IsStartupScriptRegistered (string key)
260 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
263 public bool IsStartupScriptRegistered (Type type, string key)
265 return IsScriptRegistered (startupScriptBlocks, type, key);
268 public bool IsOnSubmitStatementRegistered (string key)
270 return IsScriptRegistered (submitStatements, GetType(), key);
273 public bool IsOnSubmitStatementRegistered (Type type, string key)
275 return IsScriptRegistered (submitStatements, type, key);
278 public bool IsClientScriptIncludeRegistered (string key)
280 return IsClientScriptIncludeRegistered (GetType (), key);
283 public bool IsClientScriptIncludeRegistered (Type type, string key)
285 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
288 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
290 while (scriptList != null) {
291 if (scriptList.Type == type && scriptList.Key == key)
293 scriptList = scriptList.Next;
298 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
300 if (registeredArrayDeclares == null)
301 registeredArrayDeclares = new Hashtable();
303 if (!registeredArrayDeclares.ContainsKey (arrayName))
304 registeredArrayDeclares.Add (arrayName, new ArrayList());
306 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
309 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
311 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
314 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
316 ScriptEntry last = null;
317 ScriptEntry entry = scriptList;
319 while (entry != null) {
320 if (entry.Type == type && entry.Key == key)
326 entry = new ScriptEntry (type, key, script, format);
328 if (last != null) last.Next = entry;
329 else scriptList = entry;
332 internal void RegisterClientScriptBlock (string key, string script)
334 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
337 public void RegisterClientScriptBlock (Type type, string key, string script)
339 RegisterClientScriptBlock (type, key, script, false);
342 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
345 throw new ArgumentNullException ("type");
347 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
350 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
352 if (hiddenFields == null)
353 hiddenFields = new Hashtable ();
355 if (!hiddenFields.ContainsKey (hiddenFieldName))
356 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
359 internal void RegisterOnSubmitStatement (string key, string script)
361 RegisterScript (ref submitStatements, GetType (), key, script, false);
364 public void RegisterOnSubmitStatement (Type type, string key, string script)
367 throw new ArgumentNullException ("type");
369 RegisterScript (ref submitStatements, type, key, script, false);
372 internal void RegisterStartupScript (string key, string script)
374 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
377 public void RegisterStartupScript (Type type, string key, string script)
379 RegisterStartupScript (type, key, script, false);
382 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
385 throw new ArgumentNullException ("type");
387 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
390 public void RegisterClientScriptInclude (string key, string url)
392 RegisterClientScriptInclude (GetType (), key, url);
395 public void RegisterClientScriptInclude (Type type, string key, string url)
398 throw new ArgumentNullException ("type");
399 if (url == null || url.Length == 0)
400 throw new ArgumentException ("url");
402 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
406 public void RegisterClientScriptResource (Type type, string resourceName)
408 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
411 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
413 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
416 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
418 if (controlId == null)
419 throw new ArgumentNullException ("controlId");
421 if (attributeName == null)
422 throw new ArgumentNullException ("attributeName");
424 if (expandoAttributes == null)
425 expandoAttributes = new Hashtable ();
427 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
429 list = new ListDictionary ();
430 expandoAttributes [controlId] = list;
433 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
436 private void EnsureEventValidationArray ()
438 if (eventValidationValues == null)
439 eventValidationValues = new int [64];
441 int len = eventValidationValues.Length;
443 if (eventValidationPos >= len) {
444 int [] tmp = new int [len * 2];
445 Array.Copy (eventValidationValues, tmp, len);
446 eventValidationValues = tmp;
450 internal void ResetEventValidationState ()
452 eventValidationPos = 0;
455 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
456 private int CalculateEventHash (string uniqueId, string argument)
458 int uniqueIdHash = uniqueId.GetHashCode ();
459 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
460 return (uniqueIdHash ^ argumentHash);
463 public void RegisterForEventValidation (PostBackOptions options)
465 // MS.NET does not check for options == null, so we won't too...
466 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
469 public void RegisterForEventValidation (string uniqueId)
471 RegisterForEventValidation (uniqueId, null);
474 public void RegisterForEventValidation (string uniqueId, string argument)
476 if (!page.EnableEventValidation)
478 if (uniqueId == null || uniqueId.Length == 0)
481 _hasRegisteredForEventValidationOnCallback = true;
482 else if (page.LifeCycle < PageLifeCycle.Render)
483 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
485 EnsureEventValidationArray ();
487 int hash = CalculateEventHash (uniqueId, argument);
488 for (int i = 0; i < eventValidationPos; i++)
489 if (eventValidationValues [i] == hash)
491 eventValidationValues [eventValidationPos++] = hash;
494 public void ValidateEvent (string uniqueId)
496 ValidateEvent (uniqueId, null);
499 public void ValidateEvent (string uniqueId, string argument)
501 if (uniqueId == null || uniqueId.Length == 0)
502 throw new ArgumentException ("must not be null or empty", "uniqueId");
503 if (!page.EnableEventValidation)
505 if (eventValidationValues == null)
508 int hash = CalculateEventHash (uniqueId, argument);
509 for (int i = 0; i < eventValidationValues.Length; i++)
510 if (eventValidationValues [i] == hash)
514 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.");
517 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
519 if (scriptList == null)
524 while (scriptList != null) {
525 switch (scriptList.Format) {
526 case ScriptEntryFormat.AddScriptTag:
527 EnsureBeginScriptBlock (writer);
528 writer.Write (scriptList.Script);
530 case ScriptEntryFormat.Include:
531 EnsureEndScriptBlock (writer);
532 WriteClientScriptInclude (writer, scriptList.Script);
535 EnsureEndScriptBlock (writer);
536 writer.WriteLine (scriptList.Script);
539 scriptList = scriptList.Next;
541 EnsureEndScriptBlock (writer);
544 bool _scriptTagOpened;
546 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
547 if (!_scriptTagOpened) {
548 WriteBeginScriptBlock (writer);
549 _scriptTagOpened = true;
553 void EnsureEndScriptBlock (HtmlTextWriter writer) {
554 if (_scriptTagOpened) {
555 WriteEndScriptBlock (writer);
556 _scriptTagOpened = false;
561 internal void RestoreEventValidationState (string fieldValue)
563 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
565 IStateFormatter fmt = page.GetFormatter ();
566 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
567 eventValidationPos = eventValidationValues.Length;
570 internal void SaveEventValidationState ()
572 if (!page.EnableEventValidation)
575 string eventValidation = GetEventValidationStateFormatted ();
576 if (eventValidation == null)
579 RegisterHiddenField (EventStateFieldName, eventValidation);
582 internal string GetEventValidationStateFormatted ()
584 if (eventValidationValues == null || eventValidationValues.Length == 0)
587 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
590 IStateFormatter fmt = page.GetFormatter ();
591 int [] array = new int [eventValidationPos];
592 Array.Copy (eventValidationValues, array, eventValidationPos);
593 return fmt.Serialize (array);
596 internal string EventStateFieldName
598 get { return "__EVENTVALIDATION"; }
601 internal void WriteExpandoAttributes (HtmlTextWriter writer)
603 if (expandoAttributes == null)
607 WriteBeginScriptBlock (writer);
609 foreach (string controlId in expandoAttributes.Keys) {
610 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
611 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
612 foreach (string attributeName in attrs.Keys) {
613 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
616 WriteEndScriptBlock (writer);
621 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
623 writer.WriteLine ("<script"+
625 " language=\"javascript\""+
627 " type=\"text/javascript\">");
628 writer.WriteLine ("<!--");
631 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
633 writer.WriteLine ("// -->");
634 writer.WriteLine ("</script>");
637 internal void WriteHiddenFields (HtmlTextWriter writer)
639 if (hiddenFields == null)
642 writer.RenderBeginTag (HtmlTextWriterTag.Div);
643 foreach (string key in hiddenFields.Keys) {
644 string value = hiddenFields [key] as string;
645 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
647 writer.RenderEndTag (); // DIV
651 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path) {
652 if (!page.IsMultiForm)
653 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
655 string scriptKey = "inc_" + path.GetHashCode ().ToString ("X");
656 writer.WriteLine ("<script type=\"text/javascript\">");
657 writer.WriteLine ("<!--");
658 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
659 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
660 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
661 writer.WriteLine ("// -->");
662 writer.WriteLine ("</script>");
666 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
668 WriteScripts (writer, clientScriptBlocks);
671 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
673 WriteScripts (writer, startupScriptBlocks);
676 internal void WriteArrayDeclares (HtmlTextWriter writer)
678 if (registeredArrayDeclares != null) {
680 WriteBeginScriptBlock (writer);
681 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
682 while (arrayEnum.MoveNext()) {
683 if (page.IsMultiForm)
684 writer.Write ("\t" + page.theForm + ".");
686 writer.Write ("\tvar ");
687 writer.Write(arrayEnum.Key);
688 writer.Write(" = new Array(");
689 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
691 while (arrayListEnum.MoveNext()) {
696 writer.Write(arrayListEnum.Current);
698 writer.WriteLine(");");
700 WriteEndScriptBlock (writer);
706 internal string GetClientValidationEvent (string validationGroup) {
707 if (page.IsMultiForm)
708 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
709 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
713 internal string GetClientValidationEvent ()
715 if (page.IsMultiForm)
716 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
717 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
721 internal string WriteSubmitStatements ()
723 if (submitStatements == null) return null;
725 StringBuilder sb = new StringBuilder ();
726 ScriptEntry entry = submitStatements;
727 while (entry != null) {
729 sb.Append (EnsureEndsWithSemicolon (entry.Script));
731 sb.Append (entry.Script);
736 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
738 " + (page.IsMultiForm ? page.theForm + "." : null) + @"WebForm_OnSubmit = function () {
739 " + sb.ToString () + @"
743 if (page.IsMultiForm)
744 return "javascript:return " + page.theForm + ".WebForm_OnSubmit();";
746 return "javascript:return WebForm_OnSubmit();";
749 return sb.ToString ();
753 internal static string GetScriptLiteral (object ob)
757 else if (ob is string) {
758 string s = (string)ob;
762 for (int i = 0; i < len; i++)
763 if (s [i] == '\\' || s [i] == '\"') {
769 return string.Concat ("\"", s, "\"");
771 StringBuilder sb = new StringBuilder (len + 10);
774 for (int si = 0; si < len; si++) {
777 else if (s [si] == '\\')
784 return sb.ToString ();
785 } else if (ob is bool) {
786 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
788 return ob.ToString ();
792 sealed class ScriptEntry
794 public readonly Type Type;
795 public readonly string Key;
796 public readonly string Script;
797 public readonly ScriptEntryFormat Format;
798 public ScriptEntry Next;
800 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
808 enum ScriptEntryFormat
817 internal static string EnsureEndsWithSemicolon (string value) {
818 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')