From d39f1d39863dc284b5873867a86498edd06d8e22 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 12:50:26 +0000 Subject: [PATCH] fix(frontend): no regex literal ends in an escaped slash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LiteSpeed Cache minifies and combines scripts for guests through matthiasmullie/minify. That minifier reads a regex literal ending in an escaped slash, followed by `]` or a line break, as closing early and drops the rest of the line. `['LINE', /Line\//]` (#2053) came out as `['LINE',/Line\];`, the combined file stopped parsing, and the device test stayed at "Checking…" for every visitor who is not logged in. The three such literals become `\/\d` and `\/{2}`. Every readable and minified script in assets/js and libs/js was run through the same minifier and still parses. MinifierSafeRegexTest refuses the shape, with a canary and an independent recount of the scanned files. Closes #2056 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018srx5hc431EYmG4MHUr2KA --- CHANGELOG.md | 1 + assets/js/ffc-core.js | 2 +- assets/js/ffc-core.min.js | 2 +- assets/js/ffc-device-check.js | 2 +- assets/js/ffc-device-check.min.js | 2 +- assets/js/ffc-preflight.js | 2 +- assets/js/ffc-preflight.min.js | 2 +- tests/Unit/MinifierSafeRegexTest.php | 117 +++++++++++++++++++++++++++ 8 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 tests/Unit/MinifierSafeRegexTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 63be741c6..e32f336e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ The format follows [Keep a Changelog] (https://keepachangelog.com/en/1.1.0/). - **Certificate forms: no silent failure when the browser cannot run them** (#2030). The form posts with `method="post"`, so without JavaScript the name, e-mail and CPF/RF stay out of the URL and the logs, and a notice says JavaScript is off. A form whose scripts, PDF libraries or captcha and nonce refresh did not load in 10 s shows "The form did not load completely" with a Reload button instead of a form that fails on submit. - **GPS forms no longer show "Location blocked" twice** (#2030). On a cached page, refreshing the captcha and security codes re-ran the checks while the browser was still answering the permission query, and both answers drew a banner. A stale answer is now dropped, and a banner replaces one with the same purpose. - **"Location blocked" only when the browser really blocks it** (#2048). Opera for Android reports the location as blocked while it gives the position, so GPS forms showed a false block, the check-in a false error and the device test "Blocked". A reported block is now confirmed by one quick read; the position is discarded. +- **Scripts run again for visitors on sites that minify with LiteSpeed Cache** (#2056). Its minifier cut a regex literal ending in an escaped slash, which #2053 added, so the combined file stopped parsing and the device test stayed at "Checking…"; the three such literals are rewritten and a test refuses the shape. ### Changed diff --git a/assets/js/ffc-core.js b/assets/js/ffc-core.js index 08af8e0a4..7ce1ca18a 100644 --- a/assets/js/ffc-core.js +++ b/assets/js/ffc-core.js @@ -151,7 +151,7 @@ || /WhatsApp/i.test(ua) || /LinkedInApp/i.test(ua) || /BytedanceWebView|musical_ly/i.test(ua) - || /Line\//i.test(ua); + || /Line\/\d/i.test(ua); }, /** @returns {boolean} Samsung Internet. */ diff --git a/assets/js/ffc-core.min.js b/assets/js/ffc-core.min.js index fd03056a8..ca27fe1b1 100644 --- a/assets/js/ffc-core.min.js +++ b/assets/js/ffc-core.min.js @@ -1,2 +1,2 @@ -!function(e){"use strict";var t={error:{message:"is-error",icon:"ffc-icon-error",control:"is-invalid",role:"alert",invalid:!0},warning:{message:"is-warning",icon:"ffc-icon-warning",control:"is-warning",role:"status",invalid:!1}},n=0;function i(e){return"ffc-msg-"+String(e).replace(/[^A-Za-z0-9_-]/g,"-")}var r={Platform:{ua:function(){return"undefined"!=typeof navigator&&navigator.userAgent||""},ios:function(){var e=this.ua();return/iPad|iPhone|iPod/.test(e)||/Macintosh/.test(e)&&"undefined"!=typeof navigator&&navigator.maxTouchPoints>1},os:function(){return this.ios()?"ios":/Android/i.test(this.ua())?"android":"desktop"},webkit:function(){return this.ios()||/^((?!chrome|android).)*safari/i.test(this.ua())},androidWebView:function(){var e=this.ua();return/\bwv\)/.test(e)||/; wv;/.test(e)},inApp:function(){var e=this.ua();return this.androidWebView()||/(FBAN|FBAV|FBIOS)/i.test(e)||/Instagram/i.test(e)||/TwitterIOS\/|Twitter for iPhone/i.test(e)||/WhatsApp/i.test(e)||/LinkedInApp/i.test(e)||/BytedanceWebView|musical_ly/i.test(e)||/Line\//i.test(e)},samsung:function(){return/SamsungBrowser/i.test(this.ua())},mobile:function(){return/Mobi|Android|iPhone|iPad|iPod/i.test(this.ua())}},version:e.ffcCoreConfig&&e.ffcCoreConfig.version||"0.0.0",config:{debug:!1,ajaxUrl:e.ffc_ajax?.ajax_url||"/wp-admin/admin-ajax.php",get nonce(){return e.ffc_ajax&&e.ffc_ajax.nonce||""},get strings(){return e.ffc_ajax&&e.ffc_ajax.strings||{}}},isModuleLoaded:function(e){return void 0!==this[e]&&null!==this[e]},log:function(e,t){this.config.debug&&(void 0!==t?console.log("[FFC Debug]",e,t):console.log("[FFC Debug]",e))},error:function(e,t){void 0!==t?console.error("[FFC Error]",e,t):console.error("[FFC Error]",e)},warn:function(e){console.warn("[FFC Warning]",e)},getAjaxUrl:function(){return this.config.ajaxUrl},getNonce:function(){return this.config.nonce},getString:function(e,t){return this.config.strings[e]||t||e},ajax:function(e){if(e.action){var t=e.data||{};t.action=e.action,!1!==e.includeNonce&&this.config.nonce&&(t.nonce=this.config.nonce);var n={url:this.config.ajaxUrl,type:e.method||"POST",data:t,success:e.success||function(){},error:e.error||function(t,n,i){FFC.error("AJAX request failed",{action:e.action,status:n,error:i})}};return this.log("AJAX request",{action:e.action,data:t}),jQuery.ajax(n)}this.error("FFC.ajax: action parameter is required")},request:function(t,n,i){var r=this;var a=(i=i||{}).ajaxUrl||this.config.ajaxUrl||"/wp-admin/admin-ajax.php";return new Promise(function(o,s){!function c(){var d,u=(d=i.nonce||n&&"object"==typeof n&&n.nonce||r.config.nonce||"","string"==typeof n?n+"&action="+encodeURIComponent(t)+"&nonce="+encodeURIComponent(d):jQuery.extend({},n,{action:t,nonce:d}));(i.timeout?jQuery.ajax({url:a,type:"POST",data:u,timeout:i.timeout}):jQuery.post(a,u)).done(function(n){if(!n||!n.success){var r=n&&"string"==typeof n.data?n.data:n&&n.data&&n.data.message,a=r||FFC.config.strings&&FFC.config.strings.error||"Request failed",d=new Error(a);return d.fromServer=!!r,d.data=n&&n.data,!i._ffcNonceRetried&&d.data&&d.data.refresh_nonce&&d.data.new_nonce?(void 0!==e.ffc_ajax&&e.ffc_ajax&&(e.ffc_ajax.nonce=d.data.new_nonce),"undefined"!=typeof console&&console.warn&&console.warn("[FFC] Stale nonce auto-recovered; retrying "+t),i._ffcNonceRetried=!0,void c()):("undefined"!=typeof document&&"function"==typeof e.CustomEvent&&document.dispatchEvent(new CustomEvent("ffc:request-rejected",{detail:{action:t,data:d.data}})),void s(d))}o(n.data)}).fail(function(e){var t=FFC.config.strings&&FFC.config.strings.connectionError||"Connection error",n=new Error(t);n.fromServer=!1,n.xhr=e,e&&e.responseJSON&&e.responseJSON.data&&(n.data=e.responseJSON.data),s(n)})}()})},rest:function(e,t){var n=((t=t||{}).method||"GET").toUpperCase(),i=t.nonce||this.config&&this.config.restNonce||"",r={url:e,method:n,beforeSend:function(e){i&&e.setRequestHeader("X-WP-Nonce",i)}};return"number"==typeof t.timeout&&(r.timeout=t.timeout),void 0!==t.data&&null!==t.data&&("GET"===n||"HEAD"===n?r.data=t.data:(r.contentType="application/json",r.data=JSON.stringify(t.data))),new Promise(function(e,t){r.success=function(t){e(t)},r.error=function(e){var n=e&&e.responseJSON&&e.responseJSON.message||FFC.config.strings&&FFC.config.strings.connectionError||"Request failed",i=new Error(n);i.xhr=e,t(i)},jQuery.ajax(r)})},setRequiredWithin:function(e,t){jQuery(e).find("[required], [data-ffc-required-off]").each(function(){var e=jQuery(this);t?void 0!==e.attr("data-ffc-required-off")&&e.prop("required",!0).removeAttr("data-ffc-required-off"):this.required&&e.prop("required",!1).attr("data-ffc-required-off","")})},fieldMessage:function(e,r,a,o){var s=jQuery(e);if(!s.length)return null;var c=t[r];if(!c)return this.clearFieldMessage(s),"valid"===r&&s.addClass("is-valid"),null;o=o||{};var d,u,f=s[0],l=o.id?i(o.id):f.getAttribute("data-ffc-message-id")||((u=(d=f).id||d.getAttribute("name")||"")||(u="field-"+(n+=1)),i(u)),g=document.getElementById(l);return g&&g.classList.contains("ffc-field-message")||((g=document.createElement("span")).id=l,function(e,t,n){var i=n.within&&jQuery(n.within)[0];if(i)i.appendChild(e);else{var r=n.after&&jQuery(n.after)[0];r||(r=t,"checkbox"!==t.type&&"radio"!==t.type||(r=t.closest('.ffc-radio-group, .ffc-checkbox-group, [role="group"], [role="radiogroup"]')||t.closest("label")||t)),r.parentNode&&r.parentNode.insertBefore(e,r.nextSibling)}}(g,f,o)),g.className="ffc-field-message "+c.message+" "+c.icon,g.setAttribute("role",c.role),g.removeAttribute("hidden"),g.textContent=null==a?"":String(a),s.each(function(){this.classList.remove("is-invalid","is-warning","is-valid"),this.classList.add(c.control),c.invalid?this.setAttribute("aria-invalid","true"):this.removeAttribute("aria-invalid"),this.setAttribute("data-ffc-message-id",l),function(e,t,n){var i=(e.getAttribute(t)||"").split(/\s+/).filter(Boolean);-1===i.indexOf(n)&&i.push(n),e.setAttribute(t,i.join(" "))}(this,"aria-describedby",l)}),g},clearFieldMessage:function(e){jQuery(e).each(function(){var e=this.getAttribute("data-ffc-message-id");if(e){!function(e,t,n){var i=(e.getAttribute(t)||"").split(/\s+/).filter(function(e){return e&&e!==n});i.length?e.setAttribute(t,i.join(" ")):e.removeAttribute(t)}(this,"aria-describedby",e),this.removeAttribute("data-ffc-message-id");var t=document.getElementById(e);t&&t.classList.contains("ffc-field-message")&&t.parentNode&&!document.querySelector('[data-ffc-message-id="'+e+'"]')&&t.parentNode.removeChild(t)}this.classList.remove("is-invalid","is-warning","is-valid"),this.removeAttribute("aria-invalid")})},requiredMessage:function(){return(e.ffcCoreConfig&&e.ffcCoreConfig.strings||{}).fieldRequired||"This field is required."},buttonCategories:["primary","neutral","create","caution","danger"],button:function(e){var t=e||{};if(null===t.label||void 0===t.label||""===t.label)return null;var n="string"==typeof t.icon&&/^[a-z0-9-]+$/.test(t.icon);if(t.iconOnly&&!n)return null;var i=-1!==this.buttonCategories.indexOf(t.category)?t.category:"neutral",r="string"==typeof t.href,a=document.createElement(r?"a":"button"),o=["ffc-btn","ffc-btn-"+i];t.iconOnly&&o.push("ffc-btn-icon"),n&&o.push("ffc-icon-"+t.icon),String(t.className||"").split(/\s+/).forEach(function(e){e&&-1===o.indexOf(e)&&o.push(e)}),r?(a.setAttribute("href",t.href),t.disabled&&(o.push("disabled"),a.setAttribute("aria-disabled","true"))):(a.type=-1!==["button","submit","reset"].indexOf(t.type)?t.type:"button",t.disabled&&(a.disabled=!0)),a.className=o.join(" "),t.id&&(a.id=String(t.id)),t.iconOnly&&(a.setAttribute("aria-label",String(t.label)),a.setAttribute("title",String(t.label)));var s=t.attrs||{};return Object.keys(s).forEach(function(e){var t=s[e],n=String(e).toLowerCase();/^[a-z][a-z0-9_:.-]*$/.test(n)&&0!==n.indexOf("on")&&!a.hasAttribute(n)&&(!0===t?a.setAttribute(n,""):!1!==t&&null!=t&&a.setAttribute(n,String(t)))}),t.iconOnly||(a.textContent=String(t.label)),a},buttonHtml:function(e){var t=this.button(e);return t?t.outerHTML:""},notice:function(e,t,n,i,r){var a={info:"info",warning:"warning",danger:"error",success:"success",neutral:"info"},o=r||{};Object.prototype.hasOwnProperty.call(a,e)||(e="info"),"string"==typeof t&&/^[a-z0-9-]+$/.test(t)||(t=a[e]);var s=document.createElement("div");s.className="ffc-notice ffc-notice-"+e,s.setAttribute("role",-1!==["status","alert","note"].indexOf(o.role)?o.role:"status"),"polite"!==o.live&&"assertive"!==o.live||s.setAttribute("aria-live",o.live),"string"==typeof o.hook&&""!==o.hook&&s.setAttribute("data-ffc-notice",o.hook);var c=document.createElement("span");c.className="ffc-notice-icon ffc-icon-"+t,c.setAttribute("aria-hidden","true");var d=document.createElement("div");if(d.className="ffc-notice-body",i){var u=document.createElement("strong");u.className="ffc-notice-title",u.textContent=String(i),d.appendChild(u)}return(Array.isArray(n)?n:[n]).forEach(function(e){if(null!=e&&""!==e){var t=document.createElement("p");t.className="ffc-notice-text",t.textContent=String(e),d.appendChild(t)}}),(o.children||[]).forEach(function(e){e&&d.appendChild(e)}),s.appendChild(c),s.appendChild(d),s},confirmRangeCancel:function(e){var t=e||{},n=t.strings||{},i=parseInt(t.count,10)||1,a=String(t.label||""),o=i>1?String(n.range||"").replace("%1$d",i).replace("%2$s",a):String(n.single||"").replace("%s",a);return r.overrideConfirm({reasons:[o],strings:n,agreeCategory:"danger"})},overrideConfirm:function(e){var t=e||{},n=t.strings||{},i=parseInt(t.seconds||n.seconds,10);i>=0||(i=15);var a={title:n.title||"Book outside the rules?",intro:n.intro||"This booking breaks the following rules:",logged:n.logged||"",agree:n.agree||"I agree",agreeIn:n.agreeIn||"I agree (%d)",cancel:n.cancel||"Cancel",countdownStart:n.countdownStart||"You can confirm in %d seconds.",countdownEnd:n.countdownEnd||"You can confirm now."},o=(t.reasons||[]).map(function(e){return String(e&&"object"==typeof e?e.message||e.code||"":e)}).filter(function(e){return""!==e}),s="ffc-override-"+Date.now().toString(36),c=document.activeElement,d=document.createElement("div");d.className="ffc-shortcode ffc-modal ffc-override-dialog",d.setAttribute("role","alertdialog"),d.setAttribute("aria-modal","true"),d.setAttribute("aria-labelledby",s+"-title"),d.setAttribute("aria-describedby",s+"-body");var u=document.createElement("div");u.className="ffc-modal-backdrop";var f=document.createElement("div");f.className="ffc-modal-content";var l=document.createElement("div");l.className="ffc-modal-header";var g=document.createElement("h3");g.id=s+"-title",g.textContent=a.title,l.appendChild(g);var m=document.createElement("div");m.className="ffc-modal-body",m.id=s+"-body";var p=document.createElement("ul");p.className="ffc-override-reasons",o.forEach(function(e){var t=document.createElement("li");t.textContent=e,p.appendChild(t)}),m.appendChild(this.notice("warning","warning",a.logged,a.intro,{role:"note",hook:"override-reasons",children:[p]}));var v=document.createElement("p");v.className="ffc-override-countdown",v.setAttribute("role","status"),v.setAttribute("aria-live","polite"),m.appendChild(v);var h=document.createElement("div");h.className="ffc-modal-footer";var b=r.button({label:a.cancel,category:"neutral",icon:"cross",attrs:{"data-ffc-override":"cancel"}}),y=r.button({label:a.agree,category:t.agreeCategory||"primary",icon:"checkmark",attrs:{"data-ffc-override":"agree"}});return h.appendChild(b),h.appendChild(y),f.appendChild(l),f.appendChild(m),f.appendChild(h),d.appendChild(u),d.appendChild(f),document.body.appendChild(d),new Promise(function(e){var t=i,n=null,r=!1;function o(){y.textContent=t>0?a.agreeIn.replace("%d",t):a.agree,y.disabled=t>0,y.setAttribute("aria-disabled",t>0?"true":"false")}function s(t){r||(r=!0,n&&clearInterval(n),document.removeEventListener("keydown",f,!0),d.parentNode&&d.parentNode.removeChild(d),c&&"function"==typeof c.focus&&c.focus(),e(t))}function f(e){if("Escape"===e.key)return e.preventDefault(),e.stopPropagation(),void s(!1);if("Tab"===e.key){var t=[b,y].filter(function(e){return!e.disabled}),n=t[0],i=t[t.length-1];-1===t.indexOf(document.activeElement)?(e.preventDefault(),n.focus()):e.shiftKey&&document.activeElement===n?(e.preventDefault(),i.focus()):e.shiftKey||document.activeElement!==i||(e.preventDefault(),n.focus())}}b.addEventListener("click",function(){s(!1)}),u.addEventListener("click",function(){s(!1)}),y.addEventListener("click",function(){y.disabled||s(!0)}),document.addEventListener("keydown",f,!0),o(),t>0?(v.textContent=a.countdownStart.replace("%d",t),n=setInterval(function(){t-=1,o(),t<=0&&(clearInterval(n),n=null,v.textContent=a.countdownEnd)},1e3)):v.textContent=a.countdownEnd,b.focus()})},status:function(e,t,n){var i=e&&e.jquery?e[0]:"string"==typeof e?document.querySelector(e):e;if(!i||!i.classList)return null;var r={busy:"sync",success:"success",error:"error",warning:"warning"},a=Object.prototype.hasOwnProperty.call(r,t),o=null==n?"":String(n);for(i.classList.add("ffc-status"),i.classList.remove("is-busy","is-success","is-error","is-warning"),i.hasAttribute("role")||i.setAttribute("role","status"),i.hasAttribute("aria-live")||i.setAttribute("aria-live","polite");i.firstChild;)i.removeChild(i.firstChild);if(!a&&""===o)return i.setAttribute("hidden","hidden"),i;if(a){i.classList.add("is-"+t);var s=document.createElement("span");s.className="ffc-status-icon ffc-icon-"+r[t]+("busy"===t?" ffc-icon-spin":""),s.setAttribute("aria-hidden","true"),i.appendChild(s)}var c=document.createElement("span");return c.className="ffc-status-text",c.textContent=o,i.appendChild(c),i.removeAttribute("hidden"),i},toggleFields:function(e,t,n,i){if(e=jQuery(e),t=jQuery(t),i=i||{},0!==e.length&&0!==t.length){var r=this,a=i.useSlide||!1,o=i.duration||200,s=i.invertLogic||!1;void 0===n&&(e.is(":checkbox")?n=!0:e.is("select")&&(n=e.find("option:first").val()));var c=function(){var i,c=(i=(e.is(":checkbox")?e.is(":checked"):e.is(":radio")?e.filter(":checked").val():e.val())===n,s?!i:i);a?c?t.slideDown(o):t.slideUp(o):t.toggle(c),r.log("toggleFields: visibility updated",{show:c})};e.on("change",c),c(),this.log("toggleFields: initialized",{trigger:e.length+" element(s)",target:t.length+" element(s)",showValue:n})}else this.warn("toggleFields: trigger or target not found")},enableDebug:function(){this.config.debug=!0},disableDebug:function(){this.config.debug=!1}},a=e.FFC;a&&"object"==typeof a&&Object.keys(a).forEach(function(e){Object.prototype.hasOwnProperty.call(r,e)||(r[e]=a[e])}),e.FFC=r,e.FFC._modules=Array.isArray(e.FFC._modules)?e.FFC._modules:[],e.FFC.registerModule=function(e,t){this._modules.push({name:e,version:t,loadedAt:new Date})},e.FFC.getModules=function(){return this._modules},"undefined"!=typeof jQuery&&jQuery(document).on("click",".ffc-copy-link[data-ffc-copy-target]",function(t){t.preventDefault();var n=jQuery(this),i=jQuery(n.data("ffc-copy-target"));if(i.length){var r=i.val(),a="undefined"!=typeof ffc_ajax&&ffc_ajax.strings?ffc_ajax.strings:{},o=a.copied||"Copied!",s=a.copyFailed||"Copy failed",c=function(e){var t=n.data("ffc-copy-original")||n.text();n.data("ffc-copy-original",t),n.text(e?o:s),setTimeout(function(){n.text(t)},1500)};if(e.navigator&&e.navigator.clipboard&&e.navigator.clipboard.writeText)e.navigator.clipboard.writeText(r).then(function(){c(!0)},function(){c(!1)});else try{i[0].select(),document.execCommand("copy"),c(!0)}catch(e){c(!1)}}}),"undefined"==typeof jQuery?console.warn("[FFC Core] jQuery not found. Some features may not work."):jQuery(function(e){e(document).on("click","[data-confirm]",function(t){confirm(e(this).attr("data-confirm"))||t.preventDefault()})})}(window); +!function(e){"use strict";var t={error:{message:"is-error",icon:"ffc-icon-error",control:"is-invalid",role:"alert",invalid:!0},warning:{message:"is-warning",icon:"ffc-icon-warning",control:"is-warning",role:"status",invalid:!1}},n=0;function i(e){return"ffc-msg-"+String(e).replace(/[^A-Za-z0-9_-]/g,"-")}var r={Platform:{ua:function(){return"undefined"!=typeof navigator&&navigator.userAgent||""},ios:function(){var e=this.ua();return/iPad|iPhone|iPod/.test(e)||/Macintosh/.test(e)&&"undefined"!=typeof navigator&&navigator.maxTouchPoints>1},os:function(){return this.ios()?"ios":/Android/i.test(this.ua())?"android":"desktop"},webkit:function(){return this.ios()||/^((?!chrome|android).)*safari/i.test(this.ua())},androidWebView:function(){var e=this.ua();return/\bwv\)/.test(e)||/; wv;/.test(e)},inApp:function(){var e=this.ua();return this.androidWebView()||/(FBAN|FBAV|FBIOS)/i.test(e)||/Instagram/i.test(e)||/TwitterIOS\/|Twitter for iPhone/i.test(e)||/WhatsApp/i.test(e)||/LinkedInApp/i.test(e)||/BytedanceWebView|musical_ly/i.test(e)||/Line\/\d/i.test(e)},samsung:function(){return/SamsungBrowser/i.test(this.ua())},mobile:function(){return/Mobi|Android|iPhone|iPad|iPod/i.test(this.ua())}},version:e.ffcCoreConfig&&e.ffcCoreConfig.version||"0.0.0",config:{debug:!1,ajaxUrl:e.ffc_ajax?.ajax_url||"/wp-admin/admin-ajax.php",get nonce(){return e.ffc_ajax&&e.ffc_ajax.nonce||""},get strings(){return e.ffc_ajax&&e.ffc_ajax.strings||{}}},isModuleLoaded:function(e){return void 0!==this[e]&&null!==this[e]},log:function(e,t){this.config.debug&&(void 0!==t?console.log("[FFC Debug]",e,t):console.log("[FFC Debug]",e))},error:function(e,t){void 0!==t?console.error("[FFC Error]",e,t):console.error("[FFC Error]",e)},warn:function(e){console.warn("[FFC Warning]",e)},getAjaxUrl:function(){return this.config.ajaxUrl},getNonce:function(){return this.config.nonce},getString:function(e,t){return this.config.strings[e]||t||e},ajax:function(e){if(e.action){var t=e.data||{};t.action=e.action,!1!==e.includeNonce&&this.config.nonce&&(t.nonce=this.config.nonce);var n={url:this.config.ajaxUrl,type:e.method||"POST",data:t,success:e.success||function(){},error:e.error||function(t,n,i){FFC.error("AJAX request failed",{action:e.action,status:n,error:i})}};return this.log("AJAX request",{action:e.action,data:t}),jQuery.ajax(n)}this.error("FFC.ajax: action parameter is required")},request:function(t,n,i){var r=this;var a=(i=i||{}).ajaxUrl||this.config.ajaxUrl||"/wp-admin/admin-ajax.php";return new Promise(function(o,s){!function c(){var d,u=(d=i.nonce||n&&"object"==typeof n&&n.nonce||r.config.nonce||"","string"==typeof n?n+"&action="+encodeURIComponent(t)+"&nonce="+encodeURIComponent(d):jQuery.extend({},n,{action:t,nonce:d}));(i.timeout?jQuery.ajax({url:a,type:"POST",data:u,timeout:i.timeout}):jQuery.post(a,u)).done(function(n){if(!n||!n.success){var r=n&&"string"==typeof n.data?n.data:n&&n.data&&n.data.message,a=r||FFC.config.strings&&FFC.config.strings.error||"Request failed",d=new Error(a);return d.fromServer=!!r,d.data=n&&n.data,!i._ffcNonceRetried&&d.data&&d.data.refresh_nonce&&d.data.new_nonce?(void 0!==e.ffc_ajax&&e.ffc_ajax&&(e.ffc_ajax.nonce=d.data.new_nonce),"undefined"!=typeof console&&console.warn&&console.warn("[FFC] Stale nonce auto-recovered; retrying "+t),i._ffcNonceRetried=!0,void c()):("undefined"!=typeof document&&"function"==typeof e.CustomEvent&&document.dispatchEvent(new CustomEvent("ffc:request-rejected",{detail:{action:t,data:d.data}})),void s(d))}o(n.data)}).fail(function(e){var t=FFC.config.strings&&FFC.config.strings.connectionError||"Connection error",n=new Error(t);n.fromServer=!1,n.xhr=e,e&&e.responseJSON&&e.responseJSON.data&&(n.data=e.responseJSON.data),s(n)})}()})},rest:function(e,t){var n=((t=t||{}).method||"GET").toUpperCase(),i=t.nonce||this.config&&this.config.restNonce||"",r={url:e,method:n,beforeSend:function(e){i&&e.setRequestHeader("X-WP-Nonce",i)}};return"number"==typeof t.timeout&&(r.timeout=t.timeout),void 0!==t.data&&null!==t.data&&("GET"===n||"HEAD"===n?r.data=t.data:(r.contentType="application/json",r.data=JSON.stringify(t.data))),new Promise(function(e,t){r.success=function(t){e(t)},r.error=function(e){var n=e&&e.responseJSON&&e.responseJSON.message||FFC.config.strings&&FFC.config.strings.connectionError||"Request failed",i=new Error(n);i.xhr=e,t(i)},jQuery.ajax(r)})},setRequiredWithin:function(e,t){jQuery(e).find("[required], [data-ffc-required-off]").each(function(){var e=jQuery(this);t?void 0!==e.attr("data-ffc-required-off")&&e.prop("required",!0).removeAttr("data-ffc-required-off"):this.required&&e.prop("required",!1).attr("data-ffc-required-off","")})},fieldMessage:function(e,r,a,o){var s=jQuery(e);if(!s.length)return null;var c=t[r];if(!c)return this.clearFieldMessage(s),"valid"===r&&s.addClass("is-valid"),null;o=o||{};var d,u,f=s[0],l=o.id?i(o.id):f.getAttribute("data-ffc-message-id")||((u=(d=f).id||d.getAttribute("name")||"")||(u="field-"+(n+=1)),i(u)),g=document.getElementById(l);return g&&g.classList.contains("ffc-field-message")||((g=document.createElement("span")).id=l,function(e,t,n){var i=n.within&&jQuery(n.within)[0];if(i)i.appendChild(e);else{var r=n.after&&jQuery(n.after)[0];r||(r=t,"checkbox"!==t.type&&"radio"!==t.type||(r=t.closest('.ffc-radio-group, .ffc-checkbox-group, [role="group"], [role="radiogroup"]')||t.closest("label")||t)),r.parentNode&&r.parentNode.insertBefore(e,r.nextSibling)}}(g,f,o)),g.className="ffc-field-message "+c.message+" "+c.icon,g.setAttribute("role",c.role),g.removeAttribute("hidden"),g.textContent=null==a?"":String(a),s.each(function(){this.classList.remove("is-invalid","is-warning","is-valid"),this.classList.add(c.control),c.invalid?this.setAttribute("aria-invalid","true"):this.removeAttribute("aria-invalid"),this.setAttribute("data-ffc-message-id",l),function(e,t,n){var i=(e.getAttribute(t)||"").split(/\s+/).filter(Boolean);-1===i.indexOf(n)&&i.push(n),e.setAttribute(t,i.join(" "))}(this,"aria-describedby",l)}),g},clearFieldMessage:function(e){jQuery(e).each(function(){var e=this.getAttribute("data-ffc-message-id");if(e){!function(e,t,n){var i=(e.getAttribute(t)||"").split(/\s+/).filter(function(e){return e&&e!==n});i.length?e.setAttribute(t,i.join(" ")):e.removeAttribute(t)}(this,"aria-describedby",e),this.removeAttribute("data-ffc-message-id");var t=document.getElementById(e);t&&t.classList.contains("ffc-field-message")&&t.parentNode&&!document.querySelector('[data-ffc-message-id="'+e+'"]')&&t.parentNode.removeChild(t)}this.classList.remove("is-invalid","is-warning","is-valid"),this.removeAttribute("aria-invalid")})},requiredMessage:function(){return(e.ffcCoreConfig&&e.ffcCoreConfig.strings||{}).fieldRequired||"This field is required."},buttonCategories:["primary","neutral","create","caution","danger"],button:function(e){var t=e||{};if(null===t.label||void 0===t.label||""===t.label)return null;var n="string"==typeof t.icon&&/^[a-z0-9-]+$/.test(t.icon);if(t.iconOnly&&!n)return null;var i=-1!==this.buttonCategories.indexOf(t.category)?t.category:"neutral",r="string"==typeof t.href,a=document.createElement(r?"a":"button"),o=["ffc-btn","ffc-btn-"+i];t.iconOnly&&o.push("ffc-btn-icon"),n&&o.push("ffc-icon-"+t.icon),String(t.className||"").split(/\s+/).forEach(function(e){e&&-1===o.indexOf(e)&&o.push(e)}),r?(a.setAttribute("href",t.href),t.disabled&&(o.push("disabled"),a.setAttribute("aria-disabled","true"))):(a.type=-1!==["button","submit","reset"].indexOf(t.type)?t.type:"button",t.disabled&&(a.disabled=!0)),a.className=o.join(" "),t.id&&(a.id=String(t.id)),t.iconOnly&&(a.setAttribute("aria-label",String(t.label)),a.setAttribute("title",String(t.label)));var s=t.attrs||{};return Object.keys(s).forEach(function(e){var t=s[e],n=String(e).toLowerCase();/^[a-z][a-z0-9_:.-]*$/.test(n)&&0!==n.indexOf("on")&&!a.hasAttribute(n)&&(!0===t?a.setAttribute(n,""):!1!==t&&null!=t&&a.setAttribute(n,String(t)))}),t.iconOnly||(a.textContent=String(t.label)),a},buttonHtml:function(e){var t=this.button(e);return t?t.outerHTML:""},notice:function(e,t,n,i,r){var a={info:"info",warning:"warning",danger:"error",success:"success",neutral:"info"},o=r||{};Object.prototype.hasOwnProperty.call(a,e)||(e="info"),"string"==typeof t&&/^[a-z0-9-]+$/.test(t)||(t=a[e]);var s=document.createElement("div");s.className="ffc-notice ffc-notice-"+e,s.setAttribute("role",-1!==["status","alert","note"].indexOf(o.role)?o.role:"status"),"polite"!==o.live&&"assertive"!==o.live||s.setAttribute("aria-live",o.live),"string"==typeof o.hook&&""!==o.hook&&s.setAttribute("data-ffc-notice",o.hook);var c=document.createElement("span");c.className="ffc-notice-icon ffc-icon-"+t,c.setAttribute("aria-hidden","true");var d=document.createElement("div");if(d.className="ffc-notice-body",i){var u=document.createElement("strong");u.className="ffc-notice-title",u.textContent=String(i),d.appendChild(u)}return(Array.isArray(n)?n:[n]).forEach(function(e){if(null!=e&&""!==e){var t=document.createElement("p");t.className="ffc-notice-text",t.textContent=String(e),d.appendChild(t)}}),(o.children||[]).forEach(function(e){e&&d.appendChild(e)}),s.appendChild(c),s.appendChild(d),s},confirmRangeCancel:function(e){var t=e||{},n=t.strings||{},i=parseInt(t.count,10)||1,a=String(t.label||""),o=i>1?String(n.range||"").replace("%1$d",i).replace("%2$s",a):String(n.single||"").replace("%s",a);return r.overrideConfirm({reasons:[o],strings:n,agreeCategory:"danger"})},overrideConfirm:function(e){var t=e||{},n=t.strings||{},i=parseInt(t.seconds||n.seconds,10);i>=0||(i=15);var a={title:n.title||"Book outside the rules?",intro:n.intro||"This booking breaks the following rules:",logged:n.logged||"",agree:n.agree||"I agree",agreeIn:n.agreeIn||"I agree (%d)",cancel:n.cancel||"Cancel",countdownStart:n.countdownStart||"You can confirm in %d seconds.",countdownEnd:n.countdownEnd||"You can confirm now."},o=(t.reasons||[]).map(function(e){return String(e&&"object"==typeof e?e.message||e.code||"":e)}).filter(function(e){return""!==e}),s="ffc-override-"+Date.now().toString(36),c=document.activeElement,d=document.createElement("div");d.className="ffc-shortcode ffc-modal ffc-override-dialog",d.setAttribute("role","alertdialog"),d.setAttribute("aria-modal","true"),d.setAttribute("aria-labelledby",s+"-title"),d.setAttribute("aria-describedby",s+"-body");var u=document.createElement("div");u.className="ffc-modal-backdrop";var f=document.createElement("div");f.className="ffc-modal-content";var l=document.createElement("div");l.className="ffc-modal-header";var g=document.createElement("h3");g.id=s+"-title",g.textContent=a.title,l.appendChild(g);var m=document.createElement("div");m.className="ffc-modal-body",m.id=s+"-body";var p=document.createElement("ul");p.className="ffc-override-reasons",o.forEach(function(e){var t=document.createElement("li");t.textContent=e,p.appendChild(t)}),m.appendChild(this.notice("warning","warning",a.logged,a.intro,{role:"note",hook:"override-reasons",children:[p]}));var v=document.createElement("p");v.className="ffc-override-countdown",v.setAttribute("role","status"),v.setAttribute("aria-live","polite"),m.appendChild(v);var h=document.createElement("div");h.className="ffc-modal-footer";var b=r.button({label:a.cancel,category:"neutral",icon:"cross",attrs:{"data-ffc-override":"cancel"}}),y=r.button({label:a.agree,category:t.agreeCategory||"primary",icon:"checkmark",attrs:{"data-ffc-override":"agree"}});return h.appendChild(b),h.appendChild(y),f.appendChild(l),f.appendChild(m),f.appendChild(h),d.appendChild(u),d.appendChild(f),document.body.appendChild(d),new Promise(function(e){var t=i,n=null,r=!1;function o(){y.textContent=t>0?a.agreeIn.replace("%d",t):a.agree,y.disabled=t>0,y.setAttribute("aria-disabled",t>0?"true":"false")}function s(t){r||(r=!0,n&&clearInterval(n),document.removeEventListener("keydown",f,!0),d.parentNode&&d.parentNode.removeChild(d),c&&"function"==typeof c.focus&&c.focus(),e(t))}function f(e){if("Escape"===e.key)return e.preventDefault(),e.stopPropagation(),void s(!1);if("Tab"===e.key){var t=[b,y].filter(function(e){return!e.disabled}),n=t[0],i=t[t.length-1];-1===t.indexOf(document.activeElement)?(e.preventDefault(),n.focus()):e.shiftKey&&document.activeElement===n?(e.preventDefault(),i.focus()):e.shiftKey||document.activeElement!==i||(e.preventDefault(),n.focus())}}b.addEventListener("click",function(){s(!1)}),u.addEventListener("click",function(){s(!1)}),y.addEventListener("click",function(){y.disabled||s(!0)}),document.addEventListener("keydown",f,!0),o(),t>0?(v.textContent=a.countdownStart.replace("%d",t),n=setInterval(function(){t-=1,o(),t<=0&&(clearInterval(n),n=null,v.textContent=a.countdownEnd)},1e3)):v.textContent=a.countdownEnd,b.focus()})},status:function(e,t,n){var i=e&&e.jquery?e[0]:"string"==typeof e?document.querySelector(e):e;if(!i||!i.classList)return null;var r={busy:"sync",success:"success",error:"error",warning:"warning"},a=Object.prototype.hasOwnProperty.call(r,t),o=null==n?"":String(n);for(i.classList.add("ffc-status"),i.classList.remove("is-busy","is-success","is-error","is-warning"),i.hasAttribute("role")||i.setAttribute("role","status"),i.hasAttribute("aria-live")||i.setAttribute("aria-live","polite");i.firstChild;)i.removeChild(i.firstChild);if(!a&&""===o)return i.setAttribute("hidden","hidden"),i;if(a){i.classList.add("is-"+t);var s=document.createElement("span");s.className="ffc-status-icon ffc-icon-"+r[t]+("busy"===t?" ffc-icon-spin":""),s.setAttribute("aria-hidden","true"),i.appendChild(s)}var c=document.createElement("span");return c.className="ffc-status-text",c.textContent=o,i.appendChild(c),i.removeAttribute("hidden"),i},toggleFields:function(e,t,n,i){if(e=jQuery(e),t=jQuery(t),i=i||{},0!==e.length&&0!==t.length){var r=this,a=i.useSlide||!1,o=i.duration||200,s=i.invertLogic||!1;void 0===n&&(e.is(":checkbox")?n=!0:e.is("select")&&(n=e.find("option:first").val()));var c=function(){var i,c=(i=(e.is(":checkbox")?e.is(":checked"):e.is(":radio")?e.filter(":checked").val():e.val())===n,s?!i:i);a?c?t.slideDown(o):t.slideUp(o):t.toggle(c),r.log("toggleFields: visibility updated",{show:c})};e.on("change",c),c(),this.log("toggleFields: initialized",{trigger:e.length+" element(s)",target:t.length+" element(s)",showValue:n})}else this.warn("toggleFields: trigger or target not found")},enableDebug:function(){this.config.debug=!0},disableDebug:function(){this.config.debug=!1}},a=e.FFC;a&&"object"==typeof a&&Object.keys(a).forEach(function(e){Object.prototype.hasOwnProperty.call(r,e)||(r[e]=a[e])}),e.FFC=r,e.FFC._modules=Array.isArray(e.FFC._modules)?e.FFC._modules:[],e.FFC.registerModule=function(e,t){this._modules.push({name:e,version:t,loadedAt:new Date})},e.FFC.getModules=function(){return this._modules},"undefined"!=typeof jQuery&&jQuery(document).on("click",".ffc-copy-link[data-ffc-copy-target]",function(t){t.preventDefault();var n=jQuery(this),i=jQuery(n.data("ffc-copy-target"));if(i.length){var r=i.val(),a="undefined"!=typeof ffc_ajax&&ffc_ajax.strings?ffc_ajax.strings:{},o=a.copied||"Copied!",s=a.copyFailed||"Copy failed",c=function(e){var t=n.data("ffc-copy-original")||n.text();n.data("ffc-copy-original",t),n.text(e?o:s),setTimeout(function(){n.text(t)},1500)};if(e.navigator&&e.navigator.clipboard&&e.navigator.clipboard.writeText)e.navigator.clipboard.writeText(r).then(function(){c(!0)},function(){c(!1)});else try{i[0].select(),document.execCommand("copy"),c(!0)}catch(e){c(!1)}}}),"undefined"==typeof jQuery?console.warn("[FFC Core] jQuery not found. Some features may not work."):jQuery(function(e){e(document).on("click","[data-confirm]",function(t){confirm(e(this).attr("data-confirm"))||t.preventDefault()})})}(window); //# sourceMappingURL=ffc-core.min.js.map \ No newline at end of file diff --git a/assets/js/ffc-device-check.js b/assets/js/ffc-device-check.js index 715459c0e..18b498785 100644 --- a/assets/js/ffc-device-check.js +++ b/assets/js/ffc-device-check.js @@ -160,7 +160,7 @@ ['WhatsApp', /WhatsApp/i], ['TikTok', /BytedanceWebView|musical_ly/i], ['LinkedIn', /LinkedInApp/i], - ['LINE', /Line\//] + ['LINE', /Line\/\d/] ]; /** diff --git a/assets/js/ffc-device-check.min.js b/assets/js/ffc-device-check.min.js index 1b53e0bcb..530673962 100644 --- a/assets/js/ffc-device-check.min.js +++ b/assets/js/ffc-device-check.min.js @@ -1,2 +1,2 @@ -!function(){"use strict";var e=(window.ffcDeviceCheck||{}).strings||{};function n(n,o){return e[n]||o}function o(e){return document.querySelector('[data-ffc-check="'+e+'"]')}function t(e,t,r,a){var c=o(e);c&&(function(e){i[e]&&(clearInterval(i[e]),delete i[e])}(e),c.classList.remove("is-ok","is-warn","is-fail","is-info"),c.classList.add("is-"+t),c.querySelector(".ffc-device-check-result").textContent=r,function(e,o,t){var i=e.querySelector(".ffc-device-check-tip"),r=e.querySelector(".ffc-device-check-fix");if(!t)return i&&i.remove(),void(r&&r.remove());r||((r=document.createElement("p")).className="ffc-device-check-fix",r.id="ffc-device-check-fix-"+o,r.hidden=!0,e.appendChild(r));if(r.textContent=t,i)return;var a=window.FFC||{},c="function"==typeof a.button?a.button({label:n("helpLabel","How to fix it"),icon:"info",iconOnly:!0,className:"ffc-device-check-tip",attrs:{"aria-expanded":"false","aria-controls":r.id}}):null;if(!c)return void(r.hidden=!1);c.addEventListener("click",function(){r.hidden=!r.hidden,c.setAttribute("aria-expanded",r.hidden?"false":"true")}),e.querySelector(".ffc-device-check-label").appendChild(c)}(c,e,"warn"!==t&&"fail"!==t||!a?"":n(a,"")))}var i={};function r(e,r){var a=o(e);if(a){var c=20,s=function(){a.classList.remove("is-ok","is-warn","is-fail"),a.classList.add("is-info"),a.querySelector(".ffc-device-check-result").textContent=w(n("pendingCountdown","%1$s (%2$s s)"),[r,c])};t(e,"info",r),s(),i[e]=setInterval(function(){--c>0?s():t(e,"warn",n("pendingTimeout","No answer after %s s: reload the page to test again.").replace("%s",String(20)))},1e3)}}function a(){return{ios:"helpLocationIos",android:"helpLocationAndroid"}[window.FFC&&window.FFC.Platform?window.FFC.Platform.os():"desktop"]||"helpLocationDesktop"}var c=[["Instagram",/Instagram/i],["Facebook",/FBAN|FBAV|FBIOS/i],["WhatsApp",/WhatsApp/i],["TikTok",/BytedanceWebView|musical_ly/i],["LinkedIn",/LinkedInApp/i],["LINE",/Line\//]],s=[["Brave","Brave"],["Microsoft Edge","Edge"],["Opera","Opera"],["Samsung Internet","Samsung Internet"],["Google Chrome","Chrome"]];function l(e,n){var o=n.exec(e);return o?o[1]:""}function d(e){return String(e||"").replace(/(\.0)+$/,"")}function f(e,n){return n?e+" (v. "+d(n)+")":e}function u(e,n){for(var o=function(n){for(var o=0;o0?n("clockAhead","Ahead by %s: date windows and the captcha may fail. Set the clock to automatic.").replace("%s",y(a)):n("clockBehind","Behind by %s: date windows and the captcha may fail. Set the clock to automatic.").replace("%s",y(a)),"helpClock")}},e)}else e()}}(),function(){var e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(n){e=""}var n=-(new Date).getTimezoneOffset(),o="UTC"+(n<0?"-":"+")+Math.floor(Math.abs(n)/60)+(n%60?":"+("0"+Math.abs(n%60)).slice(-2):"");t("locale","info",[e||o,e?o:"",navigator.language||""].filter(Boolean).join(" · "))}(),s=window.screen||{},t("screen","info",w(n("screenResult","Window %1$s · screen %2$s · zoom %3$s×"),[window.innerWidth+" × "+window.innerHeight,(s.width||"?")+" × "+(s.height||"?"),Math.round(100*(window.devicePixelRatio||1))/100])),function(){if(!1!==navigator.onLine){var e=navigator.connection;if(e&&e.effectiveType){var o=n("connectionType","Online · %s").replace("%s",String(e.effectiveType).toUpperCase());e.saveData&&(o+=" · "+n("connectionSaver","data saver on: some scripts may not load")),t("connection",e.saveData?"warn":"info",o,"helpDataSaver")}else t("connection","info",n("connectionOnline","Online · the browser does not say the type"))}else t("connection","fail",n("connectionOffline","Offline"),"helpOffline")}(),function(){if(o("theme")){var e=document.documentElement.classList.contains("ffc-dark-mode"),i=window.ffcDarkMode&&window.ffcDarkMode.mode||"off",r={on:n("themeSiteOn","always dark"),auto:n("themeSiteAuto","follows the system")}[i]||n("themeSiteOff","always light"),a=n("themeSystemNone","not stated");window.matchMedia&&(window.matchMedia("(prefers-color-scheme: dark)").matches?a=n("themeSystemDark","dark"):window.matchMedia("(prefers-color-scheme: light)").matches&&(a=n("themeSystemLight","light"))),t("theme","info",w(n("themeResult","%1$s (site: %2$s; system: %3$s)"),[e?n("themeDark","Dark"):n("themeLight","Light"),r,a]))}}(),k(),t("accuracy","info",n("accuracyIdle",'Press "Test my location".'));var d=document.querySelector(".ffc-device-check-locate");d&&d.addEventListener("click",b);var f=document.querySelector(".ffc-device-check-copy");f&&f.addEventListener("click",function(){var e,o;e=f,o=function(o){var t=e.querySelector(".ffc-btn-label")||e,i=t.textContent;t.textContent=o?n("copied","Copied"):n("copyFailed","Could not copy: take a screenshot instead."),setTimeout(function(){t.textContent=i},2500)},navigator.clipboard&&"function"==typeof navigator.clipboard.writeText?navigator.clipboard.writeText(C()).then(function(){o(!0)},function(){o(!1)}):o(!1)})}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S(),window.FFCDeviceCheck={summary:C,browserName:h}}(); +!function(){"use strict";var e=(window.ffcDeviceCheck||{}).strings||{};function n(n,o){return e[n]||o}function o(e){return document.querySelector('[data-ffc-check="'+e+'"]')}function t(e,t,r,a){var c=o(e);c&&(function(e){i[e]&&(clearInterval(i[e]),delete i[e])}(e),c.classList.remove("is-ok","is-warn","is-fail","is-info"),c.classList.add("is-"+t),c.querySelector(".ffc-device-check-result").textContent=r,function(e,o,t){var i=e.querySelector(".ffc-device-check-tip"),r=e.querySelector(".ffc-device-check-fix");if(!t)return i&&i.remove(),void(r&&r.remove());r||((r=document.createElement("p")).className="ffc-device-check-fix",r.id="ffc-device-check-fix-"+o,r.hidden=!0,e.appendChild(r));if(r.textContent=t,i)return;var a=window.FFC||{},c="function"==typeof a.button?a.button({label:n("helpLabel","How to fix it"),icon:"info",iconOnly:!0,className:"ffc-device-check-tip",attrs:{"aria-expanded":"false","aria-controls":r.id}}):null;if(!c)return void(r.hidden=!1);c.addEventListener("click",function(){r.hidden=!r.hidden,c.setAttribute("aria-expanded",r.hidden?"false":"true")}),e.querySelector(".ffc-device-check-label").appendChild(c)}(c,e,"warn"!==t&&"fail"!==t||!a?"":n(a,"")))}var i={};function r(e,r){var a=o(e);if(a){var c=20,s=function(){a.classList.remove("is-ok","is-warn","is-fail"),a.classList.add("is-info"),a.querySelector(".ffc-device-check-result").textContent=w(n("pendingCountdown","%1$s (%2$s s)"),[r,c])};t(e,"info",r),s(),i[e]=setInterval(function(){--c>0?s():t(e,"warn",n("pendingTimeout","No answer after %s s: reload the page to test again.").replace("%s",String(20)))},1e3)}}function a(){return{ios:"helpLocationIos",android:"helpLocationAndroid"}[window.FFC&&window.FFC.Platform?window.FFC.Platform.os():"desktop"]||"helpLocationDesktop"}var c=[["Instagram",/Instagram/i],["Facebook",/FBAN|FBAV|FBIOS/i],["WhatsApp",/WhatsApp/i],["TikTok",/BytedanceWebView|musical_ly/i],["LinkedIn",/LinkedInApp/i],["LINE",/Line\/\d/]],s=[["Brave","Brave"],["Microsoft Edge","Edge"],["Opera","Opera"],["Samsung Internet","Samsung Internet"],["Google Chrome","Chrome"]];function l(e,n){var o=n.exec(e);return o?o[1]:""}function d(e){return String(e||"").replace(/(\.0)+$/,"")}function f(e,n){return n?e+" (v. "+d(n)+")":e}function u(e,n){for(var o=function(n){for(var o=0;o0?n("clockAhead","Ahead by %s: date windows and the captcha may fail. Set the clock to automatic.").replace("%s",y(a)):n("clockBehind","Behind by %s: date windows and the captcha may fail. Set the clock to automatic.").replace("%s",y(a)),"helpClock")}},e)}else e()}}(),function(){var e="";try{e=Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch(n){e=""}var n=-(new Date).getTimezoneOffset(),o="UTC"+(n<0?"-":"+")+Math.floor(Math.abs(n)/60)+(n%60?":"+("0"+Math.abs(n%60)).slice(-2):"");t("locale","info",[e||o,e?o:"",navigator.language||""].filter(Boolean).join(" · "))}(),s=window.screen||{},t("screen","info",w(n("screenResult","Window %1$s · screen %2$s · zoom %3$s×"),[window.innerWidth+" × "+window.innerHeight,(s.width||"?")+" × "+(s.height||"?"),Math.round(100*(window.devicePixelRatio||1))/100])),function(){if(!1!==navigator.onLine){var e=navigator.connection;if(e&&e.effectiveType){var o=n("connectionType","Online · %s").replace("%s",String(e.effectiveType).toUpperCase());e.saveData&&(o+=" · "+n("connectionSaver","data saver on: some scripts may not load")),t("connection",e.saveData?"warn":"info",o,"helpDataSaver")}else t("connection","info",n("connectionOnline","Online · the browser does not say the type"))}else t("connection","fail",n("connectionOffline","Offline"),"helpOffline")}(),function(){if(o("theme")){var e=document.documentElement.classList.contains("ffc-dark-mode"),i=window.ffcDarkMode&&window.ffcDarkMode.mode||"off",r={on:n("themeSiteOn","always dark"),auto:n("themeSiteAuto","follows the system")}[i]||n("themeSiteOff","always light"),a=n("themeSystemNone","not stated");window.matchMedia&&(window.matchMedia("(prefers-color-scheme: dark)").matches?a=n("themeSystemDark","dark"):window.matchMedia("(prefers-color-scheme: light)").matches&&(a=n("themeSystemLight","light"))),t("theme","info",w(n("themeResult","%1$s (site: %2$s; system: %3$s)"),[e?n("themeDark","Dark"):n("themeLight","Light"),r,a]))}}(),k(),t("accuracy","info",n("accuracyIdle",'Press "Test my location".'));var d=document.querySelector(".ffc-device-check-locate");d&&d.addEventListener("click",b);var f=document.querySelector(".ffc-device-check-copy");f&&f.addEventListener("click",function(){var e,o;e=f,o=function(o){var t=e.querySelector(".ffc-btn-label")||e,i=t.textContent;t.textContent=o?n("copied","Copied"):n("copyFailed","Could not copy: take a screenshot instead."),setTimeout(function(){t.textContent=i},2500)},navigator.clipboard&&"function"==typeof navigator.clipboard.writeText?navigator.clipboard.writeText(C()).then(function(){o(!0)},function(){o(!1)}):o(!1)})}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S(),window.FFCDeviceCheck={summary:C,browserName:h}}(); //# sourceMappingURL=ffc-device-check.min.js.map \ No newline at end of file diff --git a/assets/js/ffc-preflight.js b/assets/js/ffc-preflight.js index c93d8b0c6..7c77fbf7f 100644 --- a/assets/js/ffc-preflight.js +++ b/assets/js/ffc-preflight.js @@ -632,7 +632,7 @@ * they are, with the menu instructions still on screen. */ function openInChrome() { - var url = window.location.href.replace(/^https?:\/\//, ''); + var url = window.location.href.replace(/^https?:\/{2}/, ''); try { window.location.href = 'intent://' + url + '#Intent;scheme=https;package=com.android.chrome;end'; } catch (e) { diff --git a/assets/js/ffc-preflight.min.js b/assets/js/ffc-preflight.min.js index ea86215de..0feaf1fa7 100644 --- a/assets/js/ffc-preflight.min.js +++ b/assets/js/ffc-preflight.min.js @@ -1,2 +1,2 @@ -!function(){"use strict";var e=window.FFC=window.FFC||{};if(!e.Preflight){var t={},o=!1,n=!1,r=null,i={},a=1e4;w("secure",{applies:function(e){return"function"==typeof e.needsSecure?!!e.needsSecure(e):!0===e.needsSecure},run:function(e,t){b()?t():(y(e,"secure"),"function"!=typeof e.onInsecure?x(e,window.FFC.notice("danger","lock",e.text("secureRequired","This form requires a secure connection (HTTPS). Please contact the site administrator."),"",{role:"alert",live:"assertive",hook:"preflight-secure"})):e.onInsecure(e))}}),w("cookies",{applies:function(e){return!(e.config&&!0===e.config.adminBypass)},run:function(e,t){if(v())t();else{y(e,"cookies");var o=window.FFC.Platform.os();g(e.wrapper,"preflight-cookies",e.text("cookieBlockedTitle","Cookies blocked"),e.text("cookieBlockedBody","Cookies are blocked in this browser. Without cookies, the form submission may fail. Enable cookies for this site, or try a different browser."),e.text("ios"===o?"cookieBlockedHowIos":"android"===o?"cookieBlockedHowAndroid":"cookieBlockedHowDesktop",""),e.text("cookieTryAnyway","Try anyway"),t)}}});var c="ffc_inapp_continue";w("browser",{applies:function(e){return!(!1===e.browserCheck||e.config&&!0===e.config.adminBypass)&&(!e.content||!!(e.config&&e.config.geo&&e.config.geo.enabled&&e.config.geo.gpsEnabled))},run:function(e,t){var o=function(e){var t=window.FFC.Platform.os();return"android"===t?e.text("browsersAndroid","Chrome, Firefox, Edge, Opera or Samsung Internet"):"ios"===t?e.text("browsersIos","Safari, Chrome, Firefox or Edge"):e.text("browsersOther","Chrome, Firefox, Edge or Safari")}(e);if(!1!==e.featureCheck&&k().length)return y(e,"browser_features"),void x(e,window.FFC.notice("warning","monitor",e.text("browserFeaturesBody","This browser is missing features the form needs to issue your certificate. Open this page in %s.").replace("%s",o),e.text("browserFeaturesTitle","This browser cannot run the form"),{role:"alert",live:"assertive",hook:"preflight-browser"}));if(window.FFC.Platform.inApp()&&!function(){try{return"1"===window.sessionStorage.getItem(c)}catch(e){return!1}}()){y(e,"browser_inapp");var n=window.FFC.Platform.os(),r=document.createElement("div");if(r.className="ffc-notice-actions","android"===n){var i=window.FFC.button({label:e.text("openInChrome","Open in Chrome"),category:"primary",icon:"external",className:"ffc-preflight-open-browser"});i.addEventListener("click",C),r.appendChild(i)}var a=document.createElement("button");a.type="button",a.className="ffc-preflight-continue",a.textContent=e.text("continueAnyway","Continue anyway"),r.appendChild(a);var f="ios"===n?e.text("inAppHowIos",'Tap the menu (••• or the share icon) and choose "Open in Safari" or "Open in browser".'):e.text("inAppHowAndroid",'Or tap the menu (⋮) and choose "Open in browser".'),s=window.FFC.notice("warning","phone",[e.text("inAppBody","You are viewing this page inside an app, where your location and the certificate download often fail. Open it in %s.").replace("%s",o),f],e.text("inAppTitle","Open this page in your browser"),{role:"alert",live:"assertive",hook:"preflight-browser",children:[r]});a.addEventListener("click",function(){y(e,"browser_inapp_continue"),function(){try{window.sessionStorage.setItem(c,"1")}catch(e){}}(),s.parentNode&&s.parentNode.removeChild(s),t()}),x(e,s)}else t()}}),w("continue",{run:function(e){"function"==typeof e.onPass&&e.onPass(e)}}),e.Preflight={ready:function(e){t[e]=!0,l()},expired:function(){return n},DEADLINE_MS:1e4,register:w,run:h,log:y,beforeDownload:function(e){var t=e.strings||{};e.wrapper&&"function"==typeof e.wrapper.find?h(["browser","continue"],{wrapper:e.wrapper,config:{},browserCheck:!1!==e.browserCheck,formSelector:e.trigger,telemetry:e.telemetry,text:function(e,o){return t[e]||o},onPass:function(t){t.wrapper.find(e.trigger).show(),e.go()}}):e.go()},secureContext:b,geoPermission:function(){var e,t={state:"unknown",status:null},o=navigator.permissions;if(!o||"function"!=typeof o.query)return Promise.resolve(t);try{e=o.query({name:"geolocation"})}catch(e){return Promise.resolve(t)}return Promise.resolve(e).then(function(e){var t=e&&e.state||"unknown";return"denied"!==t?{state:t,status:e||null}:new Promise(function(e){var t=navigator.geolocation;if(t&&"function"==typeof t.getCurrentPosition){var o=!1,n=function(t){o||(o=!0,e(t))},r=setTimeout(function(){n("prompt")},11e3);try{t.getCurrentPosition(function(){clearTimeout(r),n("granted")},function(e){clearTimeout(r),n(e&&1===e.code?"denied":"prompt")},{enableHighAccuracy:!1,maximumAge:1/0,timeout:a})}catch(e){clearTimeout(r),n("denied")}}else e("denied")}).then(function(t){return{state:t,status:e}})},function(){return t})},banner:g,cookiesWork:v,missingFeatures:k},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",m):m()}function f(){for(var e=[],t=document.querySelectorAll(".ffc-form-wrapper"),o=0;o [data-ffc-notice="boot-failed"]');t&&t.parentNode.removeChild(t);for(var o=e.querySelectorAll("[data-ffc-boot-disabled]"),n=0;n0?{type:String(e.telemetry.type),id:t}:null}e=e.wrapper}var o=e&&e.jquery?e.attr("id"):e&&e.id,n=parseInt((o||"").replace("ffc-form-",""),10);return n>0?{type:"form",id:n}:null}(e);if(!o)return;"function"==typeof window.FFC.request&&window.FFC.request("ffc_log_preflight_bail",{type:o.type,id:o.id,reason:t}).catch(function(){})}catch(e){}}function g(e,t,o,n,r,i,a){var c=document.createElement("button");c.type="button",c.className="ffc-preflight-try-anyway",c.textContent=i;var f=window.FFC.notice("warning","warning",[n,r],o,{role:"alert",live:"assertive",hook:t,children:[c]});c.addEventListener("click",function(){f.parentNode&&f.parentNode.removeChild(f),a()}),e.find(".ffc-submission-form, .ffc-form").hide(),e.find('[data-ffc-notice="'+t+'"]').remove(),e.prepend(f)}function v(){try{if("undefined"!=typeof navigator&&!1===navigator.cookieEnabled)return!1;if("undefined"==typeof document)return!1;var e="ffc_cookie_probe=1";document.cookie=e+"; path=/; SameSite=Lax; max-age=10";var t=document.cookie.split("; ").some(function(t){return 0===t.indexOf(e)});return document.cookie="ffc_cookie_probe=; path=/; SameSite=Lax; max-age=0",t}catch(e){return!1}}function b(){if("boolean"==typeof window.isSecureContext)return window.isSecureContext;var e=window.location;return"https:"===e.protocol||"localhost"===e.hostname||"127.0.0.1"===e.hostname}function k(){var e=[];"function"!=typeof window.Promise&&e.push("Promise"),"function"!=typeof window.FormData&&e.push("FormData"),"function"!=typeof window.fetch&&"function"!=typeof window.XMLHttpRequest&&e.push("fetch"),"function"!=typeof window.Blob&&e.push("Blob");try{var t=document.createElement("canvas");"function"==typeof t.getContext&&"function"==typeof t.toDataURL||e.push("canvas")}catch(t){e.push("canvas")}return e}function C(){var e=window.location.href.replace(/^https?:\/\//,"");try{window.location.href="intent://"+e+"#Intent;scheme=https;package=com.android.chrome;end"}catch(e){}}function x(e,t){e.wrapper.find(e.formSelector||".ffc-submission-form, .ffc-form").hide();var o=t.getAttribute("data-ffc-notice");o&&e.wrapper.find('[data-ffc-notice="'+o+'"]').remove(),e.wrapper.prepend(t)}}(); +!function(){"use strict";var e=window.FFC=window.FFC||{};if(!e.Preflight){var t={},o=!1,n=!1,r=null,i={},a=1e4;w("secure",{applies:function(e){return"function"==typeof e.needsSecure?!!e.needsSecure(e):!0===e.needsSecure},run:function(e,t){b()?t():(y(e,"secure"),"function"!=typeof e.onInsecure?x(e,window.FFC.notice("danger","lock",e.text("secureRequired","This form requires a secure connection (HTTPS). Please contact the site administrator."),"",{role:"alert",live:"assertive",hook:"preflight-secure"})):e.onInsecure(e))}}),w("cookies",{applies:function(e){return!(e.config&&!0===e.config.adminBypass)},run:function(e,t){if(v())t();else{y(e,"cookies");var o=window.FFC.Platform.os();g(e.wrapper,"preflight-cookies",e.text("cookieBlockedTitle","Cookies blocked"),e.text("cookieBlockedBody","Cookies are blocked in this browser. Without cookies, the form submission may fail. Enable cookies for this site, or try a different browser."),e.text("ios"===o?"cookieBlockedHowIos":"android"===o?"cookieBlockedHowAndroid":"cookieBlockedHowDesktop",""),e.text("cookieTryAnyway","Try anyway"),t)}}});var c="ffc_inapp_continue";w("browser",{applies:function(e){return!(!1===e.browserCheck||e.config&&!0===e.config.adminBypass)&&(!e.content||!!(e.config&&e.config.geo&&e.config.geo.enabled&&e.config.geo.gpsEnabled))},run:function(e,t){var o=function(e){var t=window.FFC.Platform.os();return"android"===t?e.text("browsersAndroid","Chrome, Firefox, Edge, Opera or Samsung Internet"):"ios"===t?e.text("browsersIos","Safari, Chrome, Firefox or Edge"):e.text("browsersOther","Chrome, Firefox, Edge or Safari")}(e);if(!1!==e.featureCheck&&k().length)return y(e,"browser_features"),void x(e,window.FFC.notice("warning","monitor",e.text("browserFeaturesBody","This browser is missing features the form needs to issue your certificate. Open this page in %s.").replace("%s",o),e.text("browserFeaturesTitle","This browser cannot run the form"),{role:"alert",live:"assertive",hook:"preflight-browser"}));if(window.FFC.Platform.inApp()&&!function(){try{return"1"===window.sessionStorage.getItem(c)}catch(e){return!1}}()){y(e,"browser_inapp");var n=window.FFC.Platform.os(),r=document.createElement("div");if(r.className="ffc-notice-actions","android"===n){var i=window.FFC.button({label:e.text("openInChrome","Open in Chrome"),category:"primary",icon:"external",className:"ffc-preflight-open-browser"});i.addEventListener("click",C),r.appendChild(i)}var a=document.createElement("button");a.type="button",a.className="ffc-preflight-continue",a.textContent=e.text("continueAnyway","Continue anyway"),r.appendChild(a);var f="ios"===n?e.text("inAppHowIos",'Tap the menu (••• or the share icon) and choose "Open in Safari" or "Open in browser".'):e.text("inAppHowAndroid",'Or tap the menu (⋮) and choose "Open in browser".'),s=window.FFC.notice("warning","phone",[e.text("inAppBody","You are viewing this page inside an app, where your location and the certificate download often fail. Open it in %s.").replace("%s",o),f],e.text("inAppTitle","Open this page in your browser"),{role:"alert",live:"assertive",hook:"preflight-browser",children:[r]});a.addEventListener("click",function(){y(e,"browser_inapp_continue"),function(){try{window.sessionStorage.setItem(c,"1")}catch(e){}}(),s.parentNode&&s.parentNode.removeChild(s),t()}),x(e,s)}else t()}}),w("continue",{run:function(e){"function"==typeof e.onPass&&e.onPass(e)}}),e.Preflight={ready:function(e){t[e]=!0,l()},expired:function(){return n},DEADLINE_MS:1e4,register:w,run:h,log:y,beforeDownload:function(e){var t=e.strings||{};e.wrapper&&"function"==typeof e.wrapper.find?h(["browser","continue"],{wrapper:e.wrapper,config:{},browserCheck:!1!==e.browserCheck,formSelector:e.trigger,telemetry:e.telemetry,text:function(e,o){return t[e]||o},onPass:function(t){t.wrapper.find(e.trigger).show(),e.go()}}):e.go()},secureContext:b,geoPermission:function(){var e,t={state:"unknown",status:null},o=navigator.permissions;if(!o||"function"!=typeof o.query)return Promise.resolve(t);try{e=o.query({name:"geolocation"})}catch(e){return Promise.resolve(t)}return Promise.resolve(e).then(function(e){var t=e&&e.state||"unknown";return"denied"!==t?{state:t,status:e||null}:new Promise(function(e){var t=navigator.geolocation;if(t&&"function"==typeof t.getCurrentPosition){var o=!1,n=function(t){o||(o=!0,e(t))},r=setTimeout(function(){n("prompt")},11e3);try{t.getCurrentPosition(function(){clearTimeout(r),n("granted")},function(e){clearTimeout(r),n(e&&1===e.code?"denied":"prompt")},{enableHighAccuracy:!1,maximumAge:1/0,timeout:a})}catch(e){clearTimeout(r),n("denied")}}else e("denied")}).then(function(t){return{state:t,status:e}})},function(){return t})},banner:g,cookiesWork:v,missingFeatures:k},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",m):m()}function f(){for(var e=[],t=document.querySelectorAll(".ffc-form-wrapper"),o=0;o [data-ffc-notice="boot-failed"]');t&&t.parentNode.removeChild(t);for(var o=e.querySelectorAll("[data-ffc-boot-disabled]"),n=0;n0?{type:String(e.telemetry.type),id:t}:null}e=e.wrapper}var o=e&&e.jquery?e.attr("id"):e&&e.id,n=parseInt((o||"").replace("ffc-form-",""),10);return n>0?{type:"form",id:n}:null}(e);if(!o)return;"function"==typeof window.FFC.request&&window.FFC.request("ffc_log_preflight_bail",{type:o.type,id:o.id,reason:t}).catch(function(){})}catch(e){}}function g(e,t,o,n,r,i,a){var c=document.createElement("button");c.type="button",c.className="ffc-preflight-try-anyway",c.textContent=i;var f=window.FFC.notice("warning","warning",[n,r],o,{role:"alert",live:"assertive",hook:t,children:[c]});c.addEventListener("click",function(){f.parentNode&&f.parentNode.removeChild(f),a()}),e.find(".ffc-submission-form, .ffc-form").hide(),e.find('[data-ffc-notice="'+t+'"]').remove(),e.prepend(f)}function v(){try{if("undefined"!=typeof navigator&&!1===navigator.cookieEnabled)return!1;if("undefined"==typeof document)return!1;var e="ffc_cookie_probe=1";document.cookie=e+"; path=/; SameSite=Lax; max-age=10";var t=document.cookie.split("; ").some(function(t){return 0===t.indexOf(e)});return document.cookie="ffc_cookie_probe=; path=/; SameSite=Lax; max-age=0",t}catch(e){return!1}}function b(){if("boolean"==typeof window.isSecureContext)return window.isSecureContext;var e=window.location;return"https:"===e.protocol||"localhost"===e.hostname||"127.0.0.1"===e.hostname}function k(){var e=[];"function"!=typeof window.Promise&&e.push("Promise"),"function"!=typeof window.FormData&&e.push("FormData"),"function"!=typeof window.fetch&&"function"!=typeof window.XMLHttpRequest&&e.push("fetch"),"function"!=typeof window.Blob&&e.push("Blob");try{var t=document.createElement("canvas");"function"==typeof t.getContext&&"function"==typeof t.toDataURL||e.push("canvas")}catch(t){e.push("canvas")}return e}function C(){var e=window.location.href.replace(/^https?:\/{2}/,"");try{window.location.href="intent://"+e+"#Intent;scheme=https;package=com.android.chrome;end"}catch(e){}}function x(e,t){e.wrapper.find(e.formSelector||".ffc-submission-form, .ffc-form").hide();var o=t.getAttribute("data-ffc-notice");o&&e.wrapper.find('[data-ffc-notice="'+o+'"]').remove(),e.wrapper.prepend(t)}}(); //# sourceMappingURL=ffc-preflight.min.js.map \ No newline at end of file diff --git a/tests/Unit/MinifierSafeRegexTest.php b/tests/Unit/MinifierSafeRegexTest.php new file mode 100644 index 000000000..e0adcc5a0 --- /dev/null +++ b/tests/Unit/MinifierSafeRegexTest.php @@ -0,0 +1,117 @@ + + */ + private static function sources(): array { + $files = array(); + foreach ( (array) glob( dirname( __DIR__, 2 ) . '/assets/js/*.js' ) as $file ) { + if ( ! str_ends_with( (string) $file, '.min.js' ) ) { + $files[] = (string) $file; + } + } + sort( $files ); + return $files; + } + + /** + * Lines of code that hold a regex literal ending in an escaped slash. + * Comments are skipped: the minifier drops them, so they cannot break. + * + * @param string $source JavaScript source. + * @return array Line numbers, 1-based. + */ + private static function offending_lines( string $source ): array { + $found = array(); + foreach ( explode( "\n", $source ) as $i => $line ) { + $code = ltrim( $line ); + if ( str_starts_with( $code, '*' ) || str_starts_with( $code, '//' ) || str_starts_with( $code, '/*' ) ) { + continue; + } + // An escaped slash directly followed by the closing slash and + // optional flags, outside a comment opener (`\//` is never `//`). + if ( preg_match( '#\\\\/\/[dgimsuyv]*(?![\w/*])#', $line ) ) { + $found[] = $i + 1; + } + } + return $found; + } + + public function test_no_regex_literal_ends_in_an_escaped_slash(): void { + $offenders = array(); + foreach ( self::sources() as $file ) { + foreach ( self::offending_lines( (string) file_get_contents( $file ) ) as $line ) { + $offenders[] = basename( $file ) . ':' . $line; + } + } + + $this->assertSame( + array(), + $offenders, + 'A regex literal ends in an escaped slash, which the LiteSpeed minifier cuts in half (#2056). Write `\/{2}` or `\/\d` instead.' + ); + } + + /** + * The scan must reach every script: a recount by another traversal. + */ + public function test_the_scan_reads_every_readable_script(): void { + $dir = dirname( __DIR__, 2 ) . '/assets/js'; + $recount = array(); + foreach ( (array) scandir( $dir ) as $name ) { + if ( preg_match( '/^(?!.*\.min\.js$).+\.js$/', (string) $name ) ) { + $recount[] = $dir . '/' . $name; + } + } + sort( $recount ); + + $this->assertNotEmpty( $recount ); + $this->assertSame( $recount, self::sources() ); + } + + /** + * Pins what the scan flags and what it leaves alone. + */ + public function test_canary(): void { + $this->assertSame( array( 1 ), self::offending_lines( "var a = [['LINE', /Line\\//]];" ) ); + $this->assertSame( array( 1 ), self::offending_lines( "x = /a\\//i.test(u);" ) ); + $this->assertSame( array( 1 ), self::offending_lines( "u.replace(/^https?:\\/\\//, '');" ) ); + $this->assertSame( array(), self::offending_lines( "var a = [['LINE', /Line\\/\\d/]];" ) ); + $this->assertSame( array(), self::offending_lines( "u.replace(/^https?:\\/{2}/, '');" ) ); + $this->assertSame( array(), self::offending_lines( "var url = 'https://example.org/';" ) ); + $this->assertSame( array(), self::offending_lines( " * `/Line\\//` is the old shape." ) ); + } +}