Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[feature] core/xhtml: enable inlining of event handler via runtime se…

…rver option

--xhtml-enable-inlined-event to test it
  • Loading branch information...
commit eea542f3222a3f65964c651b76090a05e6b5d3ff 1 parent ca0f2c5
@OpaOnWindowsNow OpaOnWindowsNow authored
View
3  opa/opa_Roots.ml
@@ -1,5 +1,5 @@
(*
- Copyright © 2011 MLstate
+ Copyright © 2011, 2012 MLstate
This file is part of OPA.
@@ -83,6 +83,7 @@ let roots_for_s3
let pass_JavascriptCompilation = [
Opacapi.callFA; (* because used in a @js_ident *)
+ Opacapi.dom_event_to_opa_event;
Opacapi.Client_code.register_js_code ;
Opacapi.Core_server_code.register_server_code;
Opacapi.Client_code.register_js_code_elt ;
View
4 opabsl/jsbsl/bslClientOnly.js
@@ -1,5 +1,5 @@
/*
- Copyright © 2011 MLstate
+ Copyright © 2011, 2012 MLstate
This file is part of OPA.
@@ -22,6 +22,7 @@
##extern-type dom_element
##extern-type Client.location
##extern-type Dom.event
+##extern-type native_event
##extern-type Dom.event_propagation
##extern-type dom
##extern-type Xhtml.private_dom_element_list
@@ -51,6 +52,7 @@
return default_opa_event
}
+##register dom_event_to_opa_event \ `dom_event_to_opa_event` : Dom.event -> native_event
/**
* {1 Dynamic loading of JavaScript}
View
1  opacapi/opacapi.ml
@@ -49,6 +49,7 @@ let magicToXml = !! "magicToXml"
let never_do_anything = !! "never_do_anything"
let none = !! "none"
let callFA = !! "callFA"
+let dom_event_to_opa_event = !! "dom_event_to_opa_event"
let some = !! "some"
let unary_minus = !! "unary_minus"
let unary_minus_dot = !! "unary_minus_dot"
View
16 stdlib/core/funaction/funaction.opa
@@ -1,5 +1,5 @@
/*
- Copyright © 2011 MLstate
+ Copyright © 2011, 2012 MLstate
This file is part of OPA.
@@ -58,8 +58,16 @@ callFA(json : string, event : Dom.event): void =
@opacapi FunActionServer_serialize_argument = FunActionServer.serialize_argument
@opacapi FunActionServer_serialize_ty_argument = FunActionServer.serialize_ty_argument
+/** Internal function, don't use */
+@opacapi
+@client
+dom_event_to_opa_event(e) = (%% bslClientOnly.dom_event_to_opa_event %%)(e)
+
+type native_event = external
+
@both
FunAction = {{
+
/**
* Serialize a fun action on a javascript code.
*/
@@ -205,9 +213,9 @@ serialize_call(fun_action_name:string, ty_ser_args:list(FunActionServer.serializ
// dropping the server value for the serialized one
fun_action_call(fun_action,tyargs,args) =
ty_call = if tyargs == "" then "" else "({tyargs})"
- ty_call = "({fun_action}){ty_call}"
- arg_call = "{ty_call}({args})"
- event_apply = "{arg_call}(event)"
+ event_conv = @js_ident("dom_event_to_opa_event")
+ // fun_action and event_conv are always named values, so we don't need to surround by parenthesis
+ event_apply = "{fun_action}{ty_call}({args})({event_conv}(event))"
event_apply
get_serialized_args(l)=
View
109 stdlib/core/xhtml/xhtml.opa
@@ -684,6 +684,60 @@ Xhtml =
to_string = serialize_to_string
+ @private
+ JsEvent = {{
+
+ filter_unsafe_inline(f)(v) =
+ if is_safe_inline_event(v.name)
+ then
+ str = inline_content(v.value)
+ if is_safe_inline_content(str) then
+ do f(Dom.Event.get_name(v.name),str):void
+ false
+ else
+ true
+ else
+ true
+
+ inline_handler(eventname,content) = " on{eventname}='{content}'"
+
+ inline_content =
+ | ~{value} -> value
+ | ~{expr} -> FunAction.serialize(expr)
+
+ /* stay sync with jquery.(mlstate)bind */
+ is_safe_inline_event =
+ | {click} -> true
+ | {keydown} -> false // jquery normalisation
+ | _ -> false
+
+ /*
+ false = {}
+ true = {v}
+ none = {}
+ some(a) = {v:a}
+ */
+ is_safe_inline_content(s) =
+ match Parser.try_parse(check,s)
+ {some} ->
+ // do println("is_safe_inline_content ACCEPTED")
+ true
+ {none} ->
+ // do println("is_safe_inline_content REJECTED {s} at {Parser.partial_parse(debug_check,s)}\n")
+ false
+
+ // CAUTION: the check accept only a very restricted set of chars, take care when extending to not introduce security problem
+ // the checked string is delimited by ', hence ' is not in the accepted chars set
+ // e.g. the string f("\"'onready=\"alert(\"toto\")\"") could cause a js injection on some browser
+ // < and > should not be in the set
+ // typical event handler is a function call with args that are only datatype (no code)
+ // * / are here in case of comments
+ check = parser
+ | [-_A-Za-z()\"0-9:.,\n\t{}*/]* -> void
+ debug_check = parser s=(check) -> s
+
+ }}
+
@publish new_server_id =
nid = (%% bslIdentGen.create %%)("I8")
nid
@@ -728,7 +782,6 @@ Xhtml =
| { ~value } -> value
| { ~expr } -> FunAction.serialize(expr)
Buf.add(js_buffer,code)
-
/**
* @param depth The current depth in the tree. Used both for pretty-printing and to insert scripts at the correct place
*/
@@ -797,8 +850,15 @@ Xhtml =
| _ -> void
)
- (load_events, other_events) = List.partition((a -> match a.name:Dom.event.kind {ready} -> true | _ -> false), events)
- (_, other_events_options) = List.partition((a -> match a.name:Dom.event.kind {ready} -> true | _ -> false), events_options)
+
+ other_events = // take care of event handler that can be inlined in html attribute
+ if XhtmlOptions.options.enable_inlined_event then
+ safe_inline(eventname,content) =
+ Buf.add(html_buffer,JsEvent.inline_handler(eventname,content))
+ List.filter(JsEvent.filter_unsafe_inline(safe_inline), other_events)
+ else
+ other_events
+
//Handle events and style: start
do (if other_events == [] && other_events_options == [] && style == [] then void
else //We need an ID for this node, to be able to attach event handlers
@@ -812,7 +872,7 @@ Xhtml =
//Now, generate jQuery-specific code in the jsbuffer, as a chain of JS dot calls on the item
do Buf.add(js_buffer,"\n$('#")
do Buf.add(js_buffer,Dom.escape_selector(id))
- do Buf.add(js_buffer,"')")
+ do Buf.add(js_buffer,"')\n")
//Handle style -- generate a call to jQuery function [css]
do if style == [] then void
@@ -833,40 +893,37 @@ Xhtml =
)
else
- do Buf.add(js_buffer,"\n.css(\{ ")
+ do Buf.add(js_buffer,".css(\{ ")
iter_tell_me_if_i_am_last((~{name value}, last ->
do Buf.add(js_buffer,"'")
do Buf.add(js_buffer,name)
do Buf.add(js_buffer,"': '")
do Buf.add(js_buffer,value)
- if last then Buf.add(js_buffer,"'})")
+ if last then Buf.add(js_buffer,"'})\n")
else Buf.add(js_buffer,"', ")),
css_as_list
)
- //Handle non-load events
- do List.iter(~{name value} -> //Generate [.name(function(event({<<serialize_event_handler(value)>>})))]
- do Buf.add(js_buffer,"\n.opachbind('")
- do Buf.add(js_buffer,Dom.Event.get_name(name))
- do Buf.add(js_buffer,"', (function(event)\{")
- do jsappend_event_handler(value)
- do Buf.add(js_buffer,"\}))")
- void,
- other_events)
-
- //Handle non-load events options
- do List.iter({name=handle value=options} -> //Generate [.name(function(event({<<serialize_event_handler(value)>>})))]
+ //Handle non-inlined, non-ready events
+ do (List.iter(_,other_events)){
+ ~{name value} -> //Generate [.name(function(event({<<serialize_event_handler(value)>>})))]
+ name = Dom.Event.get_name(name)
+ content = JsEvent.inline_content(value)
+ add = ".bind('{name}',(function(event)\{{content}\}))\n"
+ Buf.add(js_buffer,add)
+ }
+ //Handle non-inlined non-ready events options
+ do (List.iter(_,other_events_options)){
+ {name=handle value=options} -> //Generate [.name(function(event({<<serialize_event_handler(value)>>})))]
stop_propagation = List.exists(_ == {stop_propagation}, options)
prevent_default = List.exists(_ == {prevent_default}, options)
if(stop_propagation || prevent_default) then
- do Buf.add(js_buffer,"\n\t.bind('")//Note: here, using [bind], which is faster than [opabind] because it doesn't perform JS -> OPA event conversion
- do Buf.add(js_buffer,Dom.Event.get_name(handle))
- do Buf.add(js_buffer,"', (function(event)\{")
- do if stop_propagation then Buf.add(js_buffer,"event.stopPropagation();")
- do if prevent_default then Buf.add(js_buffer,"event.preventDefault();")
- do Buf.add(js_buffer,"\}))")
- void,
- other_events_options)
+ name = Dom.Event.get_name(handle)
+ stop_propagation = if stop_propagation then "event.stopPropagation();" else ""
+ prevent_default = if prevent_default then "event.preventDefault();" else ""
+ add = ".bind('{name}',(function(event)\{{stop_propagation}{prevent_default}\}))\n"
+ Buf.add(js_buffer,add)
+ }
//Finally, return args with id
void
View
65 stdlib/core/xhtml/xhtml_options.opa
@@ -0,0 +1,65 @@
+/*
+ Copyright © 2011, 2012 MLstate
+
+ This file is part of OPA.
+
+ OPA is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License, version 3, as published by
+ the Free Software Foundation.
+
+ OPA is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with OPA. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import stdlib.core.args
+
+/**
+ * Type defining the state of options
+ * enable_inlined_event : Enable inlined event handler in xhtml when it is possible
+ */
+type Xhtml.serialization.options = {
+ enable_inlined_event : bool
+}
+
+/**
+ * {1 About this module}
+ *
+ * Command line options related to xhtml
+ * the field options contains the record of options
+ *
+ */
+@private
+XhtmlOptions = {{
+
+ /** default value for options */
+ default = { enable_inlined_event = true } : Xhtml.serialization.options
+
+ /** Contains command line options */
+ @both
+ options = p_options
+
+ @private
+ commandline : CommandLine.family(Xhtml.serialization.options) =
+ {
+ title = "Xhtml"
+ init = default
+ parsers = [
+ CommandLine.switch(
+ ["--xhtml-enable-inlined-event"],
+ "Enable inlined event handler in xhtml when it is possible"
+ ){
+ options -> {options with enable_inlined_event = true}
+ }
+ ]
+ anonymous = []
+ }
+
+ @private @server @publish // i.e. published for options (see above)
+ p_options = CommandLine.filter(commandline)
+
+}}
Please sign in to comment.
Something went wrong with that request. Please try again.