Permalink
Browse files

xxf:script: fewer inline scripts by using hash of script bodies

Other small improvements along the way.
  • Loading branch information...
1 parent 43d562b commit d1b026087807b00438500fd64647a3e01d61b370 @ebruchez ebruchez committed Feb 14, 2012
@@ -772,22 +772,22 @@ public String getLevel() {
}
}
- public void addScriptToRun(String scriptId, XFormsEvent event, XFormsEventObserver eventObserver) {
+ public void addScriptToRun(org.orbeon.oxf.xforms.Script script, XFormsEvent event, XFormsEventObserver eventObserver) {
if (activeSubmissionFirstPass != null && StringUtils.isBlank(activeSubmissionFirstPass.getResolvedXXFormsTarget())) {
// Scripts occurring after a submission without a target takes place should not run
// TODO: Should we allow scripts anyway? Don't we allow value changes updates on the client anyway?
- indentedLogger.logWarning("", "xxforms:script will be ignored because two-pass submission started", "script id", scriptId);
+ indentedLogger.logWarning("", "xxforms:script will be ignored because two-pass submission started", "script id", script.prefixedId());
return;
}
// Warn that scripts won't run in noscript mode (duh)
if (staticState.isNoscript())
- indentedLogger.logWarning("noscript", "script won't run in noscript mode", "script id", scriptId);
+ indentedLogger.logWarning("noscript", "script won't run in noscript mode", "script id", script.prefixedId());
if (scriptsToRun == null)
scriptsToRun = new ArrayList<Script>();
- scriptsToRun.add(new Script(XFormsUtils.scriptIdToScriptName(scriptId), event, eventObserver));
+ scriptsToRun.add(new Script(script.clientName(), event, eventObserver));
}
public static class Script {
@@ -870,16 +870,6 @@ public static boolean isXPath2Expression(Configuration configuration, String xpa
return true;
}
-
- /**
- * Create a JavaScript function name based on a script id.
- *
- * @param scriptId id of the script
- * @return JavaScript function name
- */
- public static String scriptIdToScriptName(String scriptId) {
- return scriptId.replace('-', '_').replace('$', '_') + "_xforms_function";
- }
private static String[] voidElementsNames = {
// HTML 5: http://www.w3.org/TR/html5/syntax.html#void-elements
@@ -120,11 +120,11 @@ protected abstract void outputJavaScriptResources(ContentHandlerHelper helper, S
private void outputScriptDeclarations(ContentHandlerHelper helper, String xhtmlPrefix, String focusElementId, List<XFormsContainingDocument.Message> messagesToRun, List<XXFormsDialogControl> dialogsToOpen) {
- if (containingDocument.getStaticOps().scripts().size() > 0 || focusElementId != null || messagesToRun != null || dialogsToOpen.size() > 0) {
+ if (containingDocument.getStaticOps().uniqueClientScripts().size() > 0 || focusElementId != null || messagesToRun != null || dialogsToOpen.size() > 0) {
helper.startElement(xhtmlPrefix, XMLConstants.XHTML_NAMESPACE_URI, "script", new String[] {
"type", "text/javascript"});
- XHTMLHeadHandler.outputScripts(helper, containingDocument.getStaticOps().scripts().values());
+ XHTMLHeadHandler.outputScripts(helper, containingDocument.getStaticOps().uniqueClientScripts());
final List<XFormsContainingDocument.Script> scriptsToRun = containingDocument.getScriptsToRun();
@@ -271,7 +271,7 @@
<xxf:attribute id="my-tabview$fr-tabview-select-2" for="my-tabview$fr-tabview-select-2" name="style">display: none</xxf:attribute>
<xxf:attribute id="my-tabview$fr-tabview-deselect-2" for="my-tabview$fr-tabview-deselect-2" name="style">display: none</xxf:attribute>
</xxf:control-values>
- <xxf:script name="my_tabview_xf_en_xforms_function" target-id="my-tabview$fr-tabview-group" observer-id="my-tabview$fr-tabview-group"/>
+ <xxf:script name="xf_fa3c6018b6aca63d2389f350f65e272530d03568" target-id="my-tabview$fr-tabview-group" observer-id="my-tabview$fr-tabview-group"/>
</xxf:action>
</xxf:event-response>
</output>
@@ -386,7 +386,7 @@
<xxf:attribute id="my-tabview$fr-tabview-select-7" for="my-tabview$fr-tabview-select-7" name="style">display: none</xxf:attribute>
<xxf:attribute id="my-tabview$fr-tabview-deselect-7" for="my-tabview$fr-tabview-deselect-7" name="style">display: none</xxf:attribute>
</xxf:control-values>
- <xxf:script name="my_tabview_xf_en_xforms_function" target-id="my-tabview$fr-tabview-group" observer-id="my-tabview$fr-tabview-group"/>
+ <xxf:script name="xf_fa3c6018b6aca63d2389f350f65e272530d03568" target-id="my-tabview$fr-tabview-group" observer-id="my-tabview$fr-tabview-group"/>
</xxf:action>
</xxf:event-response>
</output>
@@ -648,8 +648,8 @@
<xxf:attribute id="my-currency$xf-22" for="my-currency$xf-22" name="style">display: none</xxf:attribute>
<xxf:control id="my-currency$number-input" class="xbl-fr-number-xforms-input">$ 12.34</xxf:control>
</xxf:control-values>
- <xxf:script name="my_currency_xf_6_xforms_function" target-id="my-currency$xf-5" observer-id="my-currency$xf-5"/>
- <xxf:script name="my_currency_xf_sf_xforms_function" target-id="my-currency" observer-id="my-currency"/>
+ <xxf:script name="xf_bb240fc3ea943af5cb3019cdb5d43148021201ec" target-id="my-currency$xf-5" observer-id="my-currency$xf-5"/>
+ <xxf:script name="xf_ee48e71b3ea5811ae186431b61fae15a32a302b5" target-id="my-currency" observer-id="my-currency"/>
</xxf:action>
</xxf:event-response>
</output>
@@ -732,8 +732,8 @@
<xxf:control id="my-autocomplete$value-selected" class="fr-autocomplete-value-selected"/>
<xxf:attribute id="my-autocomplete$value-selected" for="my-autocomplete$value-selected" name="style">display: none</xxf:attribute>
</xxf:control-values>
- <xxf:script name="my_autocomplete_init_xforms_function" target-id="my-autocomplete$component-inner-group" observer-id="my-autocomplete$component-inner-group"/>
- <xxf:script name="my_autocomplete_show_suggestions_button_xf_88_xforms_function" target-id="my-autocomplete$show-suggestions-button$trigger" observer-id="my-autocomplete$show-suggestions-button$trigger"/>
+ <xxf:script name="xf_586bd855f1f823eb047ce1f64fc89bcd15e36a85" target-id="my-autocomplete$component-inner-group" observer-id="my-autocomplete$component-inner-group"/>
+ <xxf:script name="xf_8ba62560f51cbde692a8536df3e54bb1c2eba121" target-id="my-autocomplete$show-suggestions-button$trigger" observer-id="my-autocomplete$show-suggestions-button$trigger"/>
<xxf:setfocus control-id="my-autocomplete$search"/>
</xxf:action>
</xxf:event-response>
@@ -60,7 +60,8 @@ trait PartGlobalOps {
def getAttributeControl(prefixedForAttribute: String, attributeName: String): AttributeControl
// Client-side resources
- def scripts: collection.Map[String, Script]
+ def scripts: Map[String, Script]
+ def uniqueClientScripts: Seq[(String, String)]
def getXBLStyles: Seq[Element]
def getXBLScripts: Seq[Element]
def baselineResources: (collection.Set[String], collection.Set[String])
@@ -13,4 +13,9 @@
*/
package org.orbeon.oxf.xforms
-class Script(val prefixedId: String, val isClient: Boolean, val scriptType: String, val body: String)
+import org.orbeon.oxf.util.SecureUtils
+
+case class Script(prefixedId: String, isClient: Boolean, scriptType: String, body: String) {
+ val digest = SecureUtils.digestString(body, "SHA1", "hex")
+ val clientName = "xf_" + digest // digest must be JavaScript-safe (e.g. a hex string)
+}
@@ -80,7 +80,8 @@ class StaticStateGlobalOps(topLevelPart: PartAnalysis) extends PartGlobalOps {
def getGlobals = collectInParts(_.getGlobals) toMap
- def scripts = parts flatMap (_.scripts) toMap
+ def scripts = collectInParts(_.scripts) toMap
+ def uniqueClientScripts = collectInParts(_.uniqueClientScripts)
def getXBLStyles = collectInParts(_.getXBLStyles)
def getXBLScripts = collectInParts(_.getXBLScripts)
@@ -16,6 +16,7 @@ package org.orbeon.oxf.xforms.action.actions;
import org.orbeon.oxf.xforms._
import action.{DynamicActionContext, XFormsAction}
import org.orbeon.oxf.common.OXFException
+import script.ServerScript
/**
* Extension xxforms:script action.
@@ -29,23 +30,26 @@ class XXFormsScriptAction extends XFormsAction {
val mediatype = actionElement.attributeValue(XFormsConstants.TYPE_QNAME)
mediatype match {
- case "javascript" | "text/javascript" | "application/javascript" | null =>
+ case "javascript" | "text/javascript" | "application/javascript" | null
// Get prefixed id of the xxforms:script element based on its location
- val actionPrefixedId = actionInterpreter.getActionPrefixedId(actionElement)
+ val script = {
+ val partAnalysis = actionInterpreter.actionXPathContext.container.getPartAnalysis
+ partAnalysis.scripts(actionInterpreter.getActionPrefixedId(actionElement))
+ }
val containingDocument = actionInterpreter.containingDocument
// Run Script on server or client
- actionElement.attributeValue("runat") match {
- case "server" =>
- containingDocument.getScriptInterpreter.runScript(actionPrefixedId)
- case _ =>
- containingDocument.addScriptToRun(actionPrefixedId, actionContext.interpreter.event, actionContext.interpreter.eventObserver)
+ script match {
+ case serverScript: ServerScript
+ containingDocument.getScriptInterpreter.runScript(serverScript)
+ case clientScript
+ containingDocument.addScriptToRun(clientScript, actionContext.interpreter.event, actionContext.interpreter.eventObserver)
}
- case "xpath" | "text/xpath" | "application/xpath" => // "unofficial" type
+ case "xpath" | "text/xpath" | "application/xpath" // "unofficial" type
// Evaluate XPath expression for its side effects only
val bindingContext = actionInterpreter.actionXPathContext.getCurrentBindingContext
actionInterpreter.evaluateExpression(actionElement, bindingContext.getNodeset, bindingContext.getPosition, actionElement.getText)
- case other =>
+ case other
throw new OXFException("Unsupported script type: " + other)
}
}
@@ -32,10 +32,10 @@ trait PartControlsAnalysis extends TransientState {
private val controlAppearances = HashMap[String, HashSet[QName]]();
// Special handling of attributes
- private var attributeControls: Map[String, Map[String, AttributeControl]] = _
+ private[PartControlsAnalysis] var attributeControls: Map[String, Map[String, AttributeControl]] = _
// Special handling of input placeholder
- private var _hasInputPlaceholder = false
+ private[PartControlsAnalysis] var _hasInputPlaceholder = false
def hasInputPlaceholder = _hasInputPlaceholder
protected def indexNewControl(elementAnalysis: ElementAnalysis, externalLHHA: Buffer[ExternalLHHAAnalysis], eventHandlers: Buffer[EventHandlerImpl]) {
@@ -15,9 +15,8 @@ package org.orbeon.oxf.xforms.analysis
import scala.collection.JavaConverters._
import org.orbeon.oxf.xforms.event.{XFormsEvents, EventHandlerImpl, EventHandler}
-import collection.mutable.LinkedHashMap
-import org.orbeon.oxf.xforms.{Script, XFormsConstants}
import org.orbeon.oxf.xforms.script.ServerScript
+import org.orbeon.oxf.xforms.{Script, XFormsConstants}
// Part analysis: event handlers information
trait PartEventHandlerAnalysis {
@@ -29,8 +28,10 @@ trait PartEventHandlerAnalysis {
private var keyHandlers: List[EventHandler] = _
// Scripts
- private var _scripts: LinkedHashMap[String, Script] = _
- def scripts = _scripts
+ private[PartEventHandlerAnalysis] var _scriptsByPrefixedId: Map[String, Script] = _
+ def scripts = _scriptsByPrefixedId
+ private[PartEventHandlerAnalysis] var _uniqueClientScripts: Seq[(String, String)] = _
+ def uniqueClientScripts = _uniqueClientScripts
// Register all event handlers
def registerEventHandlers(eventHandlers: Seq[EventHandlerImpl]) {
@@ -57,19 +58,30 @@ trait PartEventHandlerAnalysis {
// Gather all keypress handlers
keyHandlers = (eventHandlers filter (_.eventNames(XFormsEvents.KEYPRESS)) toList)
- // Gather all scripts
- def scriptMapping(eventHandlerImpl: ElementAnalysis) = {
+ // Gather all scripts in deterministic order
+ def makeScript(eventHandlerImpl: ElementAnalysis) = {
val element = eventHandlerImpl.element
val isClient = element.attributeValue("runat") != "server"
- val newScript = if (isClient) new Script(_, _, _, _) else new ServerScript(_, _, _, _)
- val script = newScript(eventHandlerImpl.prefixedId, isClient, element.attributeValue("type"), element.getStringValue)
- eventHandlerImpl.prefixedId script
+ val make = if (isClient) new Script(_, _, _, _) else new ServerScript(_, _, _, _)
+ make(eventHandlerImpl.prefixedId, isClient, element.attributeValue("type"), element.getStringValue)
+ }
+
+ val scriptMappings = {
+ val scriptHandlers = controlTypes.get("script").toSeq flatMap (_.values)
+ scriptHandlers map (makeScript(_))
}
- // Use LinkedHashMap as we want to output scripts in the HTML in a deterministic order.
- // Could use List + Map to avoid using a mutable collection?
- _scripts = LinkedHashMap(controlTypes.get("script").toSeq flatMap (_.values) map (scriptMapping(_)): _*)
+ // Index scripts by prefixed id
+ _scriptsByPrefixedId = scriptMappings map { case script script.prefixedId script } toMap
+
+ // Keep only one script body for a given digest
+ val distinctNames = scriptMappings collect
+ { case script if script.isClient script.clientName script.digest } distinct
+
+ val scriptBodiesByDigest = (scriptMappings map { case script script.digest script.body } toMap)
+
+ _uniqueClientScripts = distinctNames map { case (clientName, digest) clientName scriptBodiesByDigest(digest) } toSeq
}
def getEventHandlers(observerPrefixedId: String) =
@@ -24,9 +24,9 @@ trait PartModelAnalysis extends TransientState {
this: PartAnalysisImpl =>
- private val modelsByScope = LinkedHashMap[Scope, Buffer[Model]]()
- private val modelsByPrefixedId = LinkedHashMap[String, Model]()
- private val modelByInstancePrefixedId = LinkedHashMap[String, Model]()
+ private[PartModelAnalysis] val modelsByScope = LinkedHashMap[Scope, Buffer[Model]]()
+ private[PartModelAnalysis] val modelsByPrefixedId = LinkedHashMap[String, Model]()
+ private[PartModelAnalysis] val modelByInstancePrefixedId = LinkedHashMap[String, Model]()
def getModel(prefixedId: String) =
modelsByPrefixedId.get(prefixedId).orNull
@@ -25,8 +25,8 @@ trait PartXBLAnalysis extends TransientState {
this: PartAnalysisImpl =>
val xblBindings = new XBLBindings(getIndentedLogger, this, metadata, staticStateDocument.xblElements)
- private val scopesById = HashMap[String, Scope]()
- private val prefixedIdToXBLScopeMap = HashMap[String, Scope]()
+ private[PartXBLAnalysis] val scopesById = HashMap[String, Scope]()
+ private[PartXBLAnalysis] val prefixedIdToXBLScopeMap = HashMap[String, Scope]()
protected def initializeScopes() {
// Add existing ids to scope map
Oops, something went wrong.

0 comments on commit d1b0260

Please sign in to comment.