From 164c9cfda44c71efa4f3a762da10967b8f48fd09 Mon Sep 17 00:00:00 2001
From: Peter Krautzberger
Date: Wed, 1 Apr 2015 09:52:36 +0200
Subject: [PATCH] re-add the Safe and MathEvents extensions
---
unpacked/extensions/MathEvents.js | 571 ++++++++++++++++++++++++++++++
unpacked/extensions/Safe.js | 348 ++++++++++++++++++
2 files changed, 919 insertions(+)
create mode 100644 unpacked/extensions/MathEvents.js
create mode 100644 unpacked/extensions/Safe.js
diff --git a/unpacked/extensions/MathEvents.js b/unpacked/extensions/MathEvents.js
new file mode 100644
index 000000000..ff6d316c0
--- /dev/null
+++ b/unpacked/extensions/MathEvents.js
@@ -0,0 +1,571 @@
+/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+
+/*************************************************************
+ *
+ * MathJax/extensions/MathEvents.js
+ *
+ * Implements the event handlers needed by the output jax to perform
+ * menu, hover, and other events.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * Copyright (c) 2011-2015 The MathJax Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function (HUB,HTML,AJAX,CALLBACK,LOCALE,OUTPUT,INPUT) {
+ var VERSION = "2.5.0";
+
+ var EXTENSION = MathJax.Extension;
+ var ME = EXTENSION.MathEvents = {version: VERSION};
+
+ var SETTINGS = HUB.config.menuSettings;
+
+ var CONFIG = {
+ hover: 500, // time required to be considered a hover
+ frame: {
+ x: 3.5, y: 5, // frame padding and
+ bwidth: 1, // frame border width (in pixels)
+ bcolor: "#A6D", // frame border color
+ hwidth: "15px", // haze width
+ hcolor: "#83A" // haze color
+ },
+ button: {
+ x: -4, y: -3, // menu button offsets
+ wx: -2, // button offset for full-width equations
+ src: AJAX.urlRev(OUTPUT.imageDir+"/MenuArrow-15.png") // button image
+ },
+ fadeinInc: .2, // increment for fade-in
+ fadeoutInc: .05, // increment for fade-out
+ fadeDelay: 50, // delay between fade-in or fade-out steps
+ fadeoutStart: 400, // delay before fade-out after mouseout
+ fadeoutDelay: 15*1000, // delay before automatic fade-out
+
+ styles: {
+ ".MathJax_Hover_Frame": {
+ "border-radius": ".25em", // Opera 10.5 and IE9
+ "-webkit-border-radius": ".25em", // Safari and Chrome
+ "-moz-border-radius": ".25em", // Firefox
+ "-khtml-border-radius": ".25em", // Konqueror
+
+ "box-shadow": "0px 0px 15px #83A", // Opera 10.5 and IE9
+ "-webkit-box-shadow": "0px 0px 15px #83A", // Safari and Chrome
+ "-moz-box-shadow": "0px 0px 15px #83A", // Forefox
+ "-khtml-box-shadow": "0px 0px 15px #83A", // Konqueror
+
+ border: "1px solid #A6D ! important",
+ display: "inline-block", position:"absolute"
+ },
+
+ ".MathJax_Hover_Arrow": {
+ position:"absolute",
+ width:"15px", height:"11px",
+ cursor:"pointer"
+ }
+ }
+ };
+
+
+ //
+ // Common event-handling code
+ //
+ var EVENT = ME.Event = {
+
+ LEFTBUTTON: 0, // the event.button value for left button
+ RIGHTBUTTON: 2, // the event.button value for right button
+ MENUKEY: "altKey", // the event value for alternate context menu
+
+ Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)},
+ Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)},
+ Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)},
+ Mouseover: function (event) {return EVENT.Handler(event,"Mouseover",this)},
+ Mouseout: function (event) {return EVENT.Handler(event,"Mouseout",this)},
+ Click: function (event) {return EVENT.Handler(event,"Click",this)},
+ DblClick: function (event) {return EVENT.Handler(event,"DblClick",this)},
+ Menu: function (event) {return EVENT.Handler(event,"ContextMenu",this)},
+
+ //
+ // Call the output jax's event handler or the zoom handler
+ //
+ Handler: function (event,type,math) {
+ if (AJAX.loadingMathMenu) {return EVENT.False(event)}
+ var jax = OUTPUT[math.jaxID];
+ if (!event) {event = window.event}
+ event.isContextMenu = (type === "ContextMenu");
+ if (jax[type]) {return jax[type](event,math)}
+ if (EXTENSION.MathZoom) {return EXTENSION.MathZoom.HandleEvent(event,type,math)}
+ },
+
+ //
+ // Try to cancel the event in every way we can
+ //
+ False: function (event) {
+ if (!event) {event = window.event}
+ if (event) {
+ if (event.preventDefault) {event.preventDefault()} else {event.returnValue = false}
+ if (event.stopPropagation) {event.stopPropagation()}
+ event.cancelBubble = true;
+ }
+ return false;
+ },
+
+ //
+ // Load the contextual menu code, if needed, and post the menu
+ //
+ ContextMenu: function (event,math,force) {
+ //
+ // Check if we are showing menus
+ //
+ var JAX = OUTPUT[math.jaxID], jax = JAX.getJaxFromMath(math);
+ var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
+ if (!show || (SETTINGS.context !== "MathJax" && !force)) return;
+
+ //
+ // Remove selections, remove hover fades
+ //
+ if (ME.msieEventBug) {event = window.event || event}
+ EVENT.ClearSelection(); HOVER.ClearHoverTimer();
+ if (jax.hover) {
+ if (jax.hover.remove) {clearTimeout(jax.hover.remove); delete jax.hover.remove}
+ jax.hover.nofade = true;
+ }
+
+ //
+ // If the menu code is loaded,
+ // Check if localization needs loading;
+ // If not, post the menu, and return.
+ // Otherwise wait for the localization to load
+ // Otherwse load the menu code.
+ // Try again after the file is loaded.
+ //
+ var MENU = MathJax.Menu; var load, fn;
+ if (MENU) {
+ if (MENU.loadingDomain) {return EVENT.False(event)}
+ load = LOCALE.loadDomain("MathMenu");
+ if (!load) {
+ MENU.jax = jax;
+ var source = MENU.menu.Find("Show Math As").menu;
+ source.items[0].name = jax.sourceMenuTitle;
+ source.items[0].format = (jax.sourceMenuFormat||"MathML");
+ source.items[1].name = INPUT[jax.inputJax].sourceMenuTitle;
+ source.items[5].disabled = !INPUT[jax.inputJax].annotationEncoding;
+
+ //
+ // Try and find each known annotation format and enable the menu
+ // items accordingly.
+ //
+ var annotations = source.items[2]; annotations.disabled = true;
+ var annotationItems = annotations.menu.items;
+ annotationList = MathJax.Hub.Config.semanticsAnnotations;
+ for (var i = 0, m = annotationItems.length; i < m; i++) {
+ var name = annotationItems[i].name[1]
+ if (jax.root && jax.root.getAnnotation(name) !== null) {
+ annotations.disabled = false;
+ annotationItems[i].hidden = false;
+ } else {
+ annotationItems[i].hidden = true;
+ }
+ }
+
+ var MathPlayer = MENU.menu.Find("Math Settings","MathPlayer");
+ MathPlayer.hidden = !(jax.outputJax === "NativeMML" && HUB.Browser.hasMathPlayer);
+ return MENU.menu.Post(event);
+ }
+ MENU.loadingDomain = true;
+ fn = function () {delete MENU.loadingDomain};
+ } else {
+ if (AJAX.loadingMathMenu) {return EVENT.False(event)}
+ AJAX.loadingMathMenu = true;
+ load = AJAX.Require("[MathJax]/extensions/MathMenu.js");
+ fn = function () {
+ delete AJAX.loadingMathMenu;
+ if (!MathJax.Menu) {MathJax.Menu = {}}
+ }
+ }
+ var ev = {
+ pageX:event.pageX, pageY:event.pageY,
+ clientX:event.clientX, clientY:event.clientY
+ };
+ CALLBACK.Queue(
+ load, fn, // load the file and delete the marker when done
+ ["ContextMenu",EVENT,ev,math,force] // call this function again
+ );
+ return EVENT.False(event);
+ },
+
+ //
+ // Mousedown handler for alternate means of accessing menu
+ //
+ AltContextMenu: function (event,math) {
+ var JAX = OUTPUT[math.jaxID];
+ var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
+ if (show) {
+ show = (JAX.config.showMathMenuMSIE != null ? JAX : HUB).config.showMathMenuMSIE;
+ if (SETTINGS.context === "MathJax" && !SETTINGS.mpContext && show) {
+ if (!ME.noContextMenuBug || event.button !== EVENT.RIGHTBUTTON) return;
+ } else {
+ if (!event[EVENT.MENUKEY] || event.button !== EVENT.LEFTBUTTON) return;
+ }
+ return JAX.ContextMenu(event,math,true);
+ }
+ },
+
+ ClearSelection: function () {
+ if (ME.safariContextMenuBug) {setTimeout("window.getSelection().empty()",0)}
+ if (document.selection) {setTimeout("document.selection.empty()",0)}
+ },
+
+ getBBox: function (span) {
+ span.appendChild(ME.topImg);
+ var h = ME.topImg.offsetTop, d = span.offsetHeight-h, w = span.offsetWidth;
+ span.removeChild(ME.topImg);
+ return {w:w, h:h, d:d};
+ }
+
+ };
+
+ //
+ // Handle hover "discoverability"
+ //
+ var HOVER = ME.Hover = {
+
+ //
+ // Check if we are moving from a non-MathJax element to a MathJax one
+ // and either start fading in again (if it is fading out) or start the
+ // timer for the hover
+ //
+ Mouseover: function (event,math) {
+ if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
+ var from = event.fromElement || event.relatedTarget,
+ to = event.toElement || event.target;
+ if (from && to && (from.isMathJax != to.isMathJax ||
+ HUB.getJaxFor(from) !== HUB.getJaxFor(to))) {
+ var jax = this.getJaxFromMath(math);
+ if (jax.hover) {HOVER.ReHover(jax)} else {HOVER.HoverTimer(jax,math)}
+ return EVENT.False(event);
+ }
+ }
+ },
+ //
+ // Check if we are moving from a MathJax element to a non-MathJax one
+ // and either start fading out, or clear the timer if we haven't
+ // hovered yet
+ //
+ Mouseout: function (event,math) {
+ if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
+ var from = event.fromElement || event.relatedTarget,
+ to = event.toElement || event.target;
+ if (from && to && (from.isMathJax != to.isMathJax ||
+ HUB.getJaxFor(from) !== HUB.getJaxFor(to))) {
+ var jax = this.getJaxFromMath(math);
+ if (jax.hover) {HOVER.UnHover(jax)} else {HOVER.ClearHoverTimer()}
+ return EVENT.False(event);
+ }
+ }
+ },
+ //
+ // Restart hover timer if the mouse moves
+ //
+ Mousemove: function (event,math) {
+ if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") {
+ var jax = this.getJaxFromMath(math); if (jax.hover) return;
+ if (HOVER.lastX == event.clientX && HOVER.lastY == event.clientY) return;
+ HOVER.lastX = event.clientX; HOVER.lastY = event.clientY;
+ HOVER.HoverTimer(jax,math);
+ return EVENT.False(event);
+ }
+ },
+
+ //
+ // Clear the old timer and start a new one
+ //
+ HoverTimer: function (jax,math) {
+ this.ClearHoverTimer();
+ this.hoverTimer = setTimeout(CALLBACK(["Hover",this,jax,math]),CONFIG.hover);
+ },
+ ClearHoverTimer: function () {
+ if (this.hoverTimer) {clearTimeout(this.hoverTimer); delete this.hoverTimer}
+ },
+
+ //
+ // Handle putting up the hover frame
+ //
+ Hover: function (jax,math) {
+ //
+ // Check if Zoom handles the hover event
+ //
+ if (EXTENSION.MathZoom && EXTENSION.MathZoom.Hover({},math)) return;
+ //
+ // Get the hover data
+ //
+ var JAX = OUTPUT[jax.outputJax],
+ span = JAX.getHoverSpan(jax,math),
+ bbox = JAX.getHoverBBox(jax,span,math),
+ show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu;
+ var dx = CONFIG.frame.x, dy = CONFIG.frame.y, dd = CONFIG.frame.bwidth; // frame size
+ if (ME.msieBorderWidthBug) {dd = 0}
+ jax.hover = {opacity:0, id:jax.inputID+"-Hover"};
+ //
+ // The frame and menu button
+ //
+ var frame = HTML.Element("span",{
+ id:jax.hover.id, isMathJax: true,
+ style:{display:"inline-block", width:0, height:0, position:"relative"}
+ },[["span",{
+ className:"MathJax_Hover_Frame", isMathJax: true,
+ style:{
+ display:"inline-block", position:"absolute",
+ top:this.Px(-bbox.h-dy-dd-(bbox.y||0)), left:this.Px(-dx-dd+(bbox.x||0)),
+ width:this.Px(bbox.w+2*dx), height:this.Px(bbox.h+bbox.d+2*dy),
+ opacity:0, filter:"alpha(opacity=0)"
+ }}
+ ]]
+ );
+ var button = HTML.Element("span",{
+ isMathJax: true, id:jax.hover.id+"Menu",
+ style:{display:"inline-block", "z-index": 1, width:0, height:0, position:"relative"}
+ },[["img",{
+ className: "MathJax_Hover_Arrow", isMathJax: true, math: math,
+ src: CONFIG.button.src, onclick: this.HoverMenu, jax:JAX.id,
+ style: {
+ left:this.Px(bbox.w+dx+dd+(bbox.x||0)+CONFIG.button.x),
+ top:this.Px(-bbox.h-dy-dd-(bbox.y||0)-CONFIG.button.y),
+ opacity:0, filter:"alpha(opacity=0)"
+ }
+ }]]
+ );
+ if (bbox.width) {
+ frame.style.width = button.style.width = bbox.width;
+ frame.style.marginRight = button.style.marginRight = "-"+bbox.width;
+ frame.firstChild.style.width = bbox.width;
+ button.firstChild.style.left = "";
+ button.firstChild.style.right = this.Px(CONFIG.button.wx);
+ }
+ //
+ // Add the frame and button
+ //
+ span.parentNode.insertBefore(frame,span);
+ if (show) {span.parentNode.insertBefore(button,span)}
+ if (span.style) {span.style.position = "relative"} // so math is on top of hover frame
+ //
+ // Start the hover fade-in
+ //
+ this.ReHover(jax);
+ },
+ //
+ // Restart the hover fade in and fade-out timers
+ //
+ ReHover: function (jax) {
+ if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
+ jax.hover.remove = setTimeout(CALLBACK(["UnHover",this,jax]),CONFIG.fadeoutDelay);
+ this.HoverFadeTimer(jax,CONFIG.fadeinInc);
+ },
+ //
+ // Start the fade-out
+ //
+ UnHover: function (jax) {
+ if (!jax.hover.nofade) {this.HoverFadeTimer(jax,-CONFIG.fadeoutInc,CONFIG.fadeoutStart)}
+ },
+ //
+ // Handle the fade-in and fade-out
+ //
+ HoverFade: function (jax) {
+ delete jax.hover.timer;
+ jax.hover.opacity = Math.max(0,Math.min(1,jax.hover.opacity + jax.hover.inc));
+ jax.hover.opacity = Math.floor(1000*jax.hover.opacity)/1000;
+ var frame = document.getElementById(jax.hover.id),
+ button = document.getElementById(jax.hover.id+"Menu");
+ frame.firstChild.style.opacity = jax.hover.opacity;
+ frame.firstChild.style.filter = "alpha(opacity="+Math.floor(100*jax.hover.opacity)+")";
+ if (button) {
+ button.firstChild.style.opacity = jax.hover.opacity;
+ button.firstChild.style.filter = frame.style.filter;
+ }
+ if (jax.hover.opacity === 1) {return}
+ if (jax.hover.opacity > 0) {this.HoverFadeTimer(jax,jax.hover.inc); return}
+ frame.parentNode.removeChild(frame);
+ if (button) {button.parentNode.removeChild(button)}
+ if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
+ delete jax.hover;
+ },
+ //
+ // Set the fade to in or out (via inc) and start the timer, if needed
+ //
+ HoverFadeTimer: function (jax,inc,delay) {
+ jax.hover.inc = inc;
+ if (!jax.hover.timer) {
+ jax.hover.timer = setTimeout(CALLBACK(["HoverFade",this,jax]),(delay||CONFIG.fadeDelay));
+ }
+ },
+
+ //
+ // Handle a click on the menu button
+ //
+ HoverMenu: function (event) {
+ if (!event) {event = window.event}
+ return OUTPUT[this.jax].ContextMenu(event,this.math,true);
+ },
+
+ //
+ // Clear all hover timers
+ //
+ ClearHover: function (jax) {
+ if (jax.hover.remove) {clearTimeout(jax.hover.remove)}
+ if (jax.hover.timer) {clearTimeout(jax.hover.timer)}
+ HOVER.ClearHoverTimer();
+ delete jax.hover;
+ },
+
+ //
+ // Make a measurement in pixels
+ //
+ Px: function (m) {
+ if (Math.abs(m) < .006) {return "0px"}
+ return m.toFixed(2).replace(/\.?0+$/,"") + "px";
+ },
+
+ //
+ // Preload images so they show up with the menu
+ //
+ getImages: function () {
+ if (SETTINGS.discoverable) {
+ var menu = new Image();
+ menu.src = CONFIG.button.src;
+ }
+ }
+
+ };
+
+ //
+ // Handle touch events.
+ //
+ // Use double-tap-and-hold as a replacement for context menu event.
+ // Use double-tap as a replacement for double click.
+ //
+ var TOUCH = ME.Touch = {
+
+ last: 0, // time of last tap event
+ delay: 500, // delay time for double-click
+
+ //
+ // Check if this is a double-tap, and if so, start the timer
+ // for the double-tap and hold (to trigger the contextual menu)
+ //
+ start: function (event) {
+ var now = new Date().getTime();
+ var dblTap = (now - TOUCH.last < TOUCH.delay && TOUCH.up);
+ TOUCH.last = now; TOUCH.up = false;
+ if (dblTap) {
+ TOUCH.timeout = setTimeout(TOUCH.menu,TOUCH.delay,event,this);
+ event.preventDefault();
+ }
+ },
+
+ //
+ // Check if there is a timeout pending, i.e., we have a
+ // double-tap and were waiting to see if it is held long
+ // enough for the menu. Since we got the end before the
+ // timeout, it is a double-click, not a double-tap-and-hold.
+ // Prevent the default action and issue a double click.
+ //
+ end: function (event) {
+ var now = new Date().getTime();
+ TOUCH.up = (now - TOUCH.last < TOUCH.delay);
+ if (TOUCH.timeout) {
+ clearTimeout(TOUCH.timeout);
+ delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false;
+ event.preventDefault();
+ return EVENT.Handler((event.touches[0]||event.touch),"DblClick",this);
+ }
+ },
+
+ //
+ // If the timeout passes without an end event, we issue
+ // the contextual menu event.
+ //
+ menu: function (event,math) {
+ delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false;
+ return EVENT.Handler((event.touches[0]||event.touch),"ContextMenu",math);
+ }
+
+ };
+
+ //
+ // Mobile screens are small, so use larger version of arrow
+ //
+ if (HUB.Browser.isMobile) {
+ var arrow = CONFIG.styles[".MathJax_Hover_Arrow"];
+ arrow.width = "25px"; arrow.height = "18px";
+ CONFIG.button.x = -6;
+ }
+
+ //
+ // Set up browser-specific values
+ //
+ HUB.Browser.Select({
+ MSIE: function (browser) {
+ var mode = (document.documentMode || 0);
+ var isIE8 = browser.versionAtLeast("8.0");
+ ME.msieBorderWidthBug = (document.compatMode === "BackCompat"); // borders are inside offsetWidth/Height
+ ME.msieEventBug = browser.isIE9; // must get event from window even though event is passed
+ ME.msieAlignBug = (!isIE8 || mode < 8); // inline-block spans don't rest on baseline
+ if (mode < 9) {EVENT.LEFTBUTTON = 1} // IE < 9 has wrong event.button values
+ },
+ Safari: function (browser) {
+ ME.safariContextMenuBug = true; // selection can be started by contextmenu event
+ },
+ Opera: function (browser) {
+ ME.operaPositionBug = true; // position is wrong unless border is used
+ },
+ Konqueror: function (browser) {
+ ME.noContextMenuBug = true; // doesn't produce contextmenu event
+ }
+ });
+
+ //
+ // Used in measuring zoom and hover positions
+ //
+ ME.topImg = (ME.msieAlignBug ?
+ HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) :
+ HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}})
+ );
+ if (ME.operaPositionBug) {ME.topImg.style.border="1px solid"}
+
+ //
+ // Get configuration from user
+ //
+ ME.config = CONFIG = HUB.CombineConfig("MathEvents",CONFIG);
+ var SETFRAME = function () {
+ var haze = CONFIG.styles[".MathJax_Hover_Frame"];
+ haze.border = CONFIG.frame.bwidth+"px solid "+CONFIG.frame.bcolor+" ! important";
+ haze["box-shadow"] = haze["-webkit-box-shadow"] =
+ haze["-moz-box-shadow"] = haze["-khtml-box-shadow"] =
+ "0px 0px "+CONFIG.frame.hwidth+" "+CONFIG.frame.hcolor;
+ };
+
+ //
+ // Queue the events needed for startup
+ //
+ CALLBACK.Queue(
+ HUB.Register.StartupHook("End Config",{}), // wait until config is complete
+ [SETFRAME],
+ ["getImages",HOVER],
+ ["Styles",AJAX,CONFIG.styles],
+ ["Post",HUB.Startup.signal,"MathEvents Ready"],
+ ["loadComplete",AJAX,"[MathJax]/extensions/MathEvents.js"]
+ );
+
+})(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.Callback,
+ MathJax.Localization,MathJax.OutputJax,MathJax.InputJax);
diff --git a/unpacked/extensions/Safe.js b/unpacked/extensions/Safe.js
new file mode 100644
index 000000000..47a60ef50
--- /dev/null
+++ b/unpacked/extensions/Safe.js
@@ -0,0 +1,348 @@
+/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+
+/*************************************************************
+ *
+ * MathJax/extensions/Safe.js
+ *
+ * Implements a "Safe" mode that disables features that could be
+ * misused in a shared environment (such as href's to javascript URL's).
+ * See the CONFIG variable below for configuration options.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * Copyright (c) 2013-2015 The MathJax Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function (HUB,AJAX) {
+ var VERSION = "2.5.0";
+
+ var CONFIG = MathJax.Hub.CombineConfig("Safe",{
+ allow: {
+ //
+ // Values can be "all", "safe", or "none"
+ //
+ URLs: "safe", // safe are in safeProtocols below
+ classes: "safe", // safe start with MJX-
+ cssIDs: "safe", // safe start with MJX-
+ styles: "safe", // safe are in safeStyles below
+ fontsize: "all", // safe are between sizeMin and sizeMax em's
+ require: "safe" // safe are in safeRequire below
+ },
+ sizeMin: .7, // \scriptsize
+ sizeMax: 1.44, // \large
+ safeProtocols: {
+ http: true,
+ https: true,
+ file: true,
+ javascript: false
+ },
+ safeStyles: {
+ color: true,
+ backgroundColor: true,
+ border: true,
+ cursor: true,
+ margin: true,
+ padding: true,
+ textShadow: true,
+ fontFamily: true,
+ fontSize: true,
+ fontStyle: true,
+ fontWeight: true,
+ opacity: true,
+ outline: true
+ },
+ safeRequire: {
+ action: true,
+ amscd: true,
+ amsmath: true,
+ amssymbols: true,
+ autobold: false,
+ "autoload-all": false,
+ bbox: true,
+ begingroup: true,
+ boldsymbol: true,
+ cancel: true,
+ color: true,
+ enclose: true,
+ extpfeil: true,
+ HTML: true,
+ mathchoice: true,
+ mhchem: true,
+ newcommand: true,
+ noErrors: false,
+ noUndefined: false,
+ unicode: true,
+ verb: true
+ }
+ });
+
+ var ALLOW = CONFIG.allow;
+ if (ALLOW.fontsize !== "all") {CONFIG.safeStyles.fontSize = false}
+
+ var SAFE = MathJax.Extension.Safe = {
+ version: VERSION,
+ config: CONFIG,
+ div1: document.createElement("div"), // for CSS processing
+ div2: document.createElement("div"),
+
+ //
+ // Methods called for MathML attribute processing
+ //
+ filter: {
+ href: "filterURL",
+ src: "filterURL",
+ altimg: "filterURL",
+ "class": "filterClass",
+ style: "filterStyles",
+ id: "filterID",
+ fontsize: "filterFontSize",
+ mathsize: "filterFontSize",
+ scriptminsize: "filterFontSize",
+ scriptsizemultiplier: "filterSizeMultiplier",
+ scriptlevel: "filterScriptLevel"
+ },
+
+ //
+ // Filter HREF URL's
+ //
+ filterURL: function (url) {
+ var protocol = (url.match(/^\s*([a-z]+):/i)||[null,""])[1].toLowerCase();
+ if (ALLOW.URLs === "none" ||
+ (ALLOW.URLs !== "all" && !CONFIG.safeProtocols[protocol])) {url = null}
+ return url;
+ },
+
+ //
+ // Filter class names and css ID's
+ //
+ filterClass: function (CLASS) {
+ if (ALLOW.classes === "none" ||
+ (ALLOW.classes !== "all" && !CLASS.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {CLASS = null}
+ return CLASS;
+ },
+ filterID: function (id) {
+ if (ALLOW.cssIDs === "none" ||
+ (ALLOW.cssIDs !== "all" && !id.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {id = null}
+ return id;
+ },
+
+ //
+ // Filter style strings
+ //
+ filterStyles: function (styles) {
+ if (ALLOW.styles === "all") {return styles}
+ if (ALLOW.styles === "none") {return null}
+ try {
+ //
+ // Set the div1 styles to the given styles, and clear div2
+ //
+ var STYLE1 = this.div1.style, STYLE2 = this.div2.style;
+ STYLE1.cssText = styles; STYLE2.cssText = "";
+ //
+ // Check each allowed style and transfer OK ones to div2
+ //
+ for (var name in CONFIG.safeStyles) {if (CONFIG.safeStyles.hasOwnProperty(name)) {
+ var value = this.filterStyle(name,STYLE1[name]);
+ if (value != null) {STYLE2[name] = value}
+ }}
+ //
+ // Return the div2 style string
+ //
+ styles = STYLE2.cssText;
+ } catch (e) {styles = null}
+ return styles;
+ },
+ //
+ // Filter an individual name:value style pair
+ //
+ filterStyle: function (name,value) {
+ if (typeof value !== "string") {return null}
+ if (value.match(/^\s*expression/)) {return null}
+ if (value.match(/javascript:/)) {return null}
+ return (CONFIG.safeStyles[name] ? value : null);
+ },
+
+ //
+ // Filter TeX font size values (in em's)
+ //
+ filterSize: function (size) {
+ if (ALLOW.fontsize === "none") {return null}
+ if (ALLOW.fontsize !== "all")
+ {size = Math.min(Math.max(size,CONFIG.sizeMin),CONFIG.sizeMax)}
+ return size;
+ },
+ filterFontSize: function (size) {
+ return (ALLOW.fontsize === "all" ? size: null);
+ },
+
+ //
+ // Filter scriptsizemultiplier
+ //
+ filterSizeMultiplier: function (size) {
+ if (ALLOW.fontsize === "none") {size = null}
+ else if (ALLOW.fontsize !== "all") {size = Math.min(1,Math.max(.6,size)).toString()}
+ return size;
+ },
+ //
+ // Filter scriptLevel
+ //
+ filterScriptLevel: function (level) {
+ if (ALLOW.fontsize === "none") {level = null}
+ else if (ALLOW.fontsize !== "all") {level = Math.max(0,level).toString()}
+ return level;
+ },
+
+ //
+ // Filter TeX extension names
+ //
+ filterRequire: function (name) {
+ if (ALLOW.require === "none" ||
+ (ALLOW.require !== "all" && !CONFIG.safeRequire[name.toLowerCase()]))
+ {name = null}
+ return name;
+ }
+
+ };
+
+ HUB.Register.StartupHook("TeX HTML Ready",function () {
+ var TEX = MathJax.InputJax.TeX;
+
+ TEX.Parse.Augment({
+
+ //
+ // Implements \href{url}{math} with URL filter
+ //
+ HREF_attribute: function (name) {
+ var url = SAFE.filterURL(this.GetArgument(name)),
+ arg = this.GetArgumentMML(name);
+ if (url) {arg.With({href:url})}
+ this.Push(arg);
+ },
+
+ //
+ // Implements \class{name}{math} with class-name filter
+ //
+ CLASS_attribute: function (name) {
+ var CLASS = SAFE.filterClass(this.GetArgument(name)),
+ arg = this.GetArgumentMML(name);
+ if (CLASS) {
+ if (arg["class"] != null) {CLASS = arg["class"] + " " + CLASS}
+ arg.With({"class":CLASS});
+ }
+ this.Push(arg);
+ },
+
+ //
+ // Implements \style{style-string}{math} with style filter
+ //
+ STYLE_attribute: function (name) {
+ var style = SAFE.filterStyles(this.GetArgument(name)),
+ arg = this.GetArgumentMML(name);
+ if (style) {
+ if (arg.style != null) {
+ if (style.charAt(style.length-1) !== ";") {style += ";"}
+ style = arg.style + " " + style;
+ }
+ arg.With({style: style});
+ }
+ this.Push(arg);
+ },
+
+ //
+ // Implements \cssId{id}{math} with ID filter
+ //
+ ID_attribute: function (name) {
+ var ID = SAFE.filterID(this.GetArgument(name)),
+ arg = this.GetArgumentMML(name);
+ if (ID) {arg.With({id:ID})}
+ this.Push(arg);
+ }
+
+ });
+
+ });
+
+ HUB.Register.StartupHook("TeX Jax Ready",function () {
+ var TEX = MathJax.InputJax.TeX,
+ PARSE = TEX.Parse, METHOD = SAFE.filter;
+
+ PARSE.Augment({
+
+ //
+ // Implements \require{name} with filtering
+ //
+ Require: function (name) {
+ var file = this.GetArgument(name).replace(/.*\//,"").replace(/[^a-z0-9_.-]/ig,"");
+ file = SAFE.filterRequire(file);
+ if (file) {this.Extension(null,file)}
+ },
+
+ //
+ // Controls \mmlToken attributes
+ //
+ MmlFilterAttribute: function (name,value) {
+ if (METHOD[name]) {value = SAFE[METHOD[name]](value)}
+ return value;
+ },
+
+ //
+ // Handles font size macros with filtering
+ //
+ SetSize: function (name,size) {
+ size = SAFE.filterSize(size);
+ if (size) {
+ this.stack.env.size = size;
+ this.Push(TEX.Stack.Item.style().With({styles: {mathsize: size+"em"}}));
+ }
+ }
+
+ });
+ });
+
+ HUB.Register.StartupHook("TeX bbox Ready",function () {
+ var TEX = MathJax.InputJax.TeX;
+
+ //
+ // Filter the styles for \bbox
+ //
+ TEX.Parse.Augment({
+ BBoxStyle: function (styles) {return SAFE.filterStyles(styles)}
+ });
+
+ });
+
+ HUB.Register.StartupHook("MathML Jax Ready",function () {
+ var PARSE = MathJax.InputJax.MathML.Parse,
+ METHOD = SAFE.filter;
+
+ //
+ // Filter MathML attributes
+ //
+ PARSE.Augment({
+ filterAttribute: function (name,value) {
+ if (METHOD[name]) {value = SAFE[METHOD[name]](value)}
+ return value;
+ }
+ });
+
+ });
+
+ // MathML input (href, style, fontsize, class, id)
+
+ HUB.Startup.signal.Post("Safe Extension Ready");
+ AJAX.loadComplete("[MathJax]/extensions/Safe.js");
+
+})(MathJax.Hub,MathJax.Ajax);