Commit 5126d1e118f42e14ff1608e7328c91278f5732d2

Michael Schmidt 2021-08-12T15:10:40

JSONP Highlight: Refactored JSONP logic (#3018)

diff --git a/plugins/jsonp-highlight/prism-jsonp-highlight.js b/plugins/jsonp-highlight/prism-jsonp-highlight.js
index ba07b58..28541a3 100644
--- a/plugins/jsonp-highlight/prism-jsonp-highlight.js
+++ b/plugins/jsonp-highlight/prism-jsonp-highlight.js
@@ -126,6 +126,49 @@
 
 
 	var jsonpCallbackCounter = 0;
+	/**
+	 * Makes a JSONP request.
+	 *
+	 * @param {string} src The URL of the resource to request.
+	 * @param {string | undefined | null} callbackParameter The name of the callback parameter. If falsy, `"callback"`
+	 * will be used.
+	 * @param {(data: unknown) => void} onSuccess
+	 * @param {(reason: "timeout" | "network") => void} onError
+	 * @returns {void}
+	 */
+	function jsonp(src, callbackParameter, onSuccess, onError) {
+		var callbackName = 'prismjsonp' + jsonpCallbackCounter++;
+
+		var uri = document.createElement('a');
+		uri.href = src;
+		uri.href += (uri.search ? '&' : '?') + (callbackParameter || 'callback') + '=' + callbackName;
+
+		var script = document.createElement('script');
+		script.src = uri.href;
+		script.onerror = function () {
+			cleanup();
+			onError('network');
+		};
+
+		var timeoutId = setTimeout(function () {
+			cleanup();
+			onError('timeout');
+		}, Prism.plugins.jsonphighlight.timeout);
+
+		function cleanup() {
+			clearTimeout(timeoutId);
+			document.head.removeChild(script);
+			delete window[callbackName];
+		}
+
+		// the JSONP callback function
+		window[callbackName] = function (response) {
+			cleanup();
+			onSuccess(response);
+		};
+
+		document.head.appendChild(script);
+	}
 
 	var LOADING_MESSAGE = 'Loading…';
 	var MISSING_ADAPTER_MESSAGE = function (name) {
@@ -185,61 +228,45 @@
 				}
 			}
 
-			var callbackName = 'prismjsonp' + jsonpCallbackCounter++;
-
-			var uri = document.createElement('a');
-			var src = uri.href = pre.getAttribute('data-jsonp');
-			uri.href += (uri.search ? '&' : '?') + (pre.getAttribute('data-callback') || 'callback') + '=' + callbackName;
-
-
-			var timeout = setTimeout(function () {
-				// we could clean up window[cb], but if the request finally succeeds, keeping it around is a good thing
-
-				// mark as failed
-				pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
-
-				code.textContent = TIMEOUT_MESSAGE(src);
-			}, Prism.plugins.jsonphighlight.timeout);
-
+			var src = pre.getAttribute('data-jsonp');
+
+			jsonp(
+				src,
+				pre.getAttribute('data-callback'),
+				function (response) {
+					// interpret the received data using the adapter(s)
+					var data = null;
+					if (adapter) {
+						data = adapter(response, pre);
+					} else {
+						for (var i = 0, l = adapters.length; i < l; i++) {
+							data = adapters[i].adapter(response, pre);
+							if (data !== null) {
+								break;
+							}
+						}
+					}
 
-			var script = document.createElement('script');
-			script.src = uri.href;
+					if (data === null) {
+						// mark as failed
+						pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
 
-			// the JSONP callback function
-			window[callbackName] = function (response) {
-				// clean up
-				document.head.removeChild(script);
-				clearTimeout(timeout);
-				delete window[callbackName];
+						code.textContent = UNKNOWN_FAILURE_MESSAGE;
+					} else {
+						// mark as loaded
+						pre.setAttribute(STATUS_ATTR, STATUS_LOADED);
 
-				// interpret the received data using the adapter(s)
-				var data = null;
-				if (adapter) {
-					data = adapter(response, pre);
-				} else {
-					for (var i = 0, l = adapters.length; i < l; i++) {
-						data = adapters[i].adapter(response, pre);
-						if (data !== null) {
-							break;
-						}
+						code.textContent = data;
+						Prism.highlightElement(code);
 					}
-				}
-
-				if (data === null) {
+				},
+				function () {
 					// mark as failed
 					pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
 
-					code.textContent = UNKNOWN_FAILURE_MESSAGE;
-				} else {
-					// mark as loaded
-					pre.setAttribute(STATUS_ATTR, STATUS_LOADED);
-
-					code.textContent = data;
-					Prism.highlightElement(code);
+					code.textContent = TIMEOUT_MESSAGE(src);
 				}
-			};
-
-			document.head.appendChild(script);
+			);
 		}
 	});
 
diff --git a/plugins/jsonp-highlight/prism-jsonp-highlight.min.js b/plugins/jsonp-highlight/prism-jsonp-highlight.min.js
index dde26c0..b6303a8 100644
--- a/plugins/jsonp-highlight/prism-jsonp-highlight.min.js
+++ b/plugins/jsonp-highlight/prism-jsonp-highlight.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var c=[];t(function(t){if(t&&t.meta&&t.data){if(t.meta.status&&400<=t.meta.status)return"Error: "+(t.data.message||t.meta.status);if("string"==typeof t.data.content)return"function"==typeof atob?atob(t.data.content.replace(/\s/g,"")):"Your browser cannot decode base64"}return null},"github"),t(function(t,e){if(t&&t.meta&&t.data&&t.data.files){if(t.meta.status&&400<=t.meta.status)return"Error: "+(t.data.message||t.meta.status);var n=t.data.files,a=e.getAttribute("data-filename");if(null==a)for(var r in n)if(n.hasOwnProperty(r)){a=r;break}return void 0!==n[a]?n[a].content:"Error: unknown or missing gist file "+a}return null},"gist"),t(function(t){return t&&t.node&&"string"==typeof t.data?t.data:null},"bitbucket");var m=0,p="data-jsonp-status",g="loading",h="loaded",v="failed",b="pre[data-jsonp]:not(["+p+'="'+h+'"]):not(['+p+'="'+g+'"])';Prism.hooks.add("before-highlightall",function(t){t.selector+=", "+b}),Prism.hooks.add("before-sanity-check",function(t){var r=t.element;if(r.matches(b)){t.code="",r.setAttribute(p,g);var i=r.appendChild(document.createElement("CODE"));i.textContent="Loading…";var e=t.language;i.className="language-"+e;var n=Prism.plugins.autoloader;n&&n.loadLanguages(e);var a=r.getAttribute("data-adapter"),o=null;if(a){if("function"!=typeof window[a])return r.setAttribute(p,v),void(i.textContent=function(t){return'✖ Error: JSONP adapter function "'+t+"\" doesn't exist"}(a));o=window[a]}var u="prismjsonp"+m++,s=document.createElement("a"),d=s.href=r.getAttribute("data-jsonp");s.href+=(s.search?"&":"?")+(r.getAttribute("data-callback")||"callback")+"="+u;var f=setTimeout(function(){r.setAttribute(p,v),i.textContent=function(t){return"✖ Error: Timeout loading "+t}(d)},Prism.plugins.jsonphighlight.timeout),l=document.createElement("script");l.src=s.href,window[u]=function(t){document.head.removeChild(l),clearTimeout(f),delete window[u];var e=null;if(o)e=o(t,r);else for(var n=0,a=c.length;n<a&&null===(e=c[n].adapter(t,r));n++);null===e?(r.setAttribute(p,v),i.textContent="✖ Error: Cannot parse response (perhaps you need an adapter function?)"):(r.setAttribute(p,h),i.textContent=e,Prism.highlightElement(i))},document.head.appendChild(l)}}),Prism.plugins.jsonphighlight={timeout:5e3,registerAdapter:t,removeAdapter:function(e){if("string"==typeof e&&(e=n(e)),"function"==typeof e){var t=c.findIndex(function(t){return t.adapter===e});0<=t&&c.splice(t,1)}},highlight:function(t){for(var e,n=(t||document).querySelectorAll(b),a=0;e=n[a++];)Prism.highlightElement(e)}}}function t(t,e){e=e||t.name,"function"!=typeof t||n(t)||n(e)||c.push({adapter:t,name:e})}function n(t){if("function"==typeof t){for(var e=0;n=c[e++];)if(n.adapter.valueOf()===t.valueOf())return n.adapter}else if("string"==typeof t){var n;for(e=0;n=c[e++];)if(n.name===t)return n.adapter}return null}}();
\ No newline at end of file
+!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var s=[];t(function(t){if(t&&t.meta&&t.data){if(t.meta.status&&400<=t.meta.status)return"Error: "+(t.data.message||t.meta.status);if("string"==typeof t.data.content)return"function"==typeof atob?atob(t.data.content.replace(/\s/g,"")):"Your browser cannot decode base64"}return null},"github"),t(function(t,e){if(t&&t.meta&&t.data&&t.data.files){if(t.meta.status&&400<=t.meta.status)return"Error: "+(t.data.message||t.meta.status);var n=t.data.files,a=e.getAttribute("data-filename");if(null==a)for(var r in n)if(n.hasOwnProperty(r)){a=r;break}return void 0!==n[a]?n[a].content:"Error: unknown or missing gist file "+a}return null},"gist"),t(function(t){return t&&t.node&&"string"==typeof t.data?t.data:null},"bitbucket");var f=0,d="data-jsonp-status",l="loading",c="loaded",m="failed",p="pre[data-jsonp]:not(["+d+'="'+c+'"]):not(['+d+'="'+l+'"])';Prism.hooks.add("before-highlightall",function(t){t.selector+=", "+p}),Prism.hooks.add("before-sanity-check",function(t){var r=t.element;if(r.matches(p)){t.code="",r.setAttribute(d,l);var i=r.appendChild(document.createElement("CODE"));i.textContent="Loading…";var e=t.language;i.className="language-"+e;var n=Prism.plugins.autoloader;n&&n.loadLanguages(e);var a=r.getAttribute("data-adapter"),o=null;if(a){if("function"!=typeof window[a])return r.setAttribute(d,m),void(i.textContent=function(t){return'✖ Error: JSONP adapter function "'+t+"\" doesn't exist"}(a));o=window[a]}var u=r.getAttribute("data-jsonp");!function(t,e,n,a){var r="prismjsonp"+f++,i=document.createElement("a");i.href=t,i.href+=(i.search?"&":"?")+(e||"callback")+"="+r;var o=document.createElement("script");o.src=i.href,o.onerror=function(){s(),a("network")};var u=setTimeout(function(){s(),a("timeout")},Prism.plugins.jsonphighlight.timeout);function s(){clearTimeout(u),document.head.removeChild(o),delete window[r]}window[r]=function(t){s(),n(t)},document.head.appendChild(o)}(u,r.getAttribute("data-callback"),function(t){var e=null;if(o)e=o(t,r);else for(var n=0,a=s.length;n<a&&null===(e=s[n].adapter(t,r));n++);null===e?(r.setAttribute(d,m),i.textContent="✖ Error: Cannot parse response (perhaps you need an adapter function?)"):(r.setAttribute(d,c),i.textContent=e,Prism.highlightElement(i))},function(){r.setAttribute(d,m),i.textContent=function(t){return"✖ Error: Timeout loading "+t}(u)})}}),Prism.plugins.jsonphighlight={timeout:5e3,registerAdapter:t,removeAdapter:function(e){if("string"==typeof e&&(e=n(e)),"function"==typeof e){var t=s.findIndex(function(t){return t.adapter===e});0<=t&&s.splice(t,1)}},highlight:function(t){for(var e,n=(t||document).querySelectorAll(p),a=0;e=n[a++];)Prism.highlightElement(e)}}}function t(t,e){e=e||t.name,"function"!=typeof t||n(t)||n(e)||s.push({adapter:t,name:e})}function n(t){if("function"==typeof t){for(var e=0;n=s[e++];)if(n.adapter.valueOf()===t.valueOf())return n.adapter}else if("string"==typeof t){var n;for(e=0;n=s[e++];)if(n.name===t)return n.adapter}return null}}();
\ No newline at end of file