[IMP] web_editor: remove global flag from URL regex

A Regex object with the 'g' flag is stateful [1]. As the URL_REGEX and
URL_REGEX_WITH_INFOS objects are exported, code that calls  their `exec`
method would be changing their `lastIndex` property, affecting future
`exec` calls in different code sections.

This commit removes the 'g' flag from the URL regexes, in order to allow
us to:
- avoid side effects of one call to `exec` to subsequent calls,
- remove the `getUrlsInfosInString` utility function,
- use `match` instead of `exec` for having results containing the
  capturing groups (see [2]).

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex#avoiding_side_effects
[2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match#return_value

task-3520513

Part-of: odoo/odoo#136497
This commit is contained in:
Rodolpho Lima
2023-10-03 19:20:09 +00:00
parent 707d76eec1
commit 4da0608f7c
3 changed files with 10 additions and 36 deletions
@@ -46,7 +46,6 @@ import {
isUnremovable,
fillEmpty,
isEmptyBlock,
getUrlsInfosInString,
URL_REGEX,
URL_REGEX_WITH_INFOS,
isSelectionFormat,
@@ -4594,16 +4593,15 @@ export class OdooEditor extends EventTarget {
) {
const textSliced = selection.anchorNode.textContent.slice(0, selection.anchorOffset);
const textNodeSplitted = textSliced.split(/\s/);
let potentialUrl = textNodeSplitted.pop();
const lastWordMatch = potentialUrl.match(URL_REGEX_WITH_INFOS) && !potentialUrl.match(EMAIL_REGEX);
const potentialUrl = textNodeSplitted.pop();
const match = potentialUrl.match(URL_REGEX_WITH_INFOS);
if (lastWordMatch) {
const matches = getUrlsInfosInString(textSliced);
const match = matches[matches.length - 1];
if (match && match[0] === potentialUrl && !EMAIL_REGEX.test(potentialUrl)) {
const url = match[2] ? match[0] : 'http://' + match[0];
const range = this.document.createRange();
range.setStart(selection.anchorNode, match.index);
range.setEnd(selection.anchorNode, match.index + match.length);
const link = this._createLink(range.extractContents().textContent, match.url);
range.setStart(selection.anchorNode, selection.anchorOffset - match[0].length);
range.setEnd(selection.anchorNode, selection.anchorOffset);
const link = this._createLink(range.extractContents().textContent, url);
range.insertNode(link);
const container = link.parentElement;
const offset = childNodeIndex(link) + 1;
@@ -94,11 +94,7 @@ export function deduceURLfromText(text, link) {
return match[1] ? match[0] : 'mailto:' + match[0];
}
// Check for http link.
// Regex with 'g' flag is stateful, reset lastIndex before and after using
// exec.
URL_REGEX_WITH_INFOS.lastIndex = 0;
match = URL_REGEX_WITH_INFOS.exec(label);
URL_REGEX_WITH_INFOS.lastIndex = 0;
match = label.match(URL_REGEX_WITH_INFOS);
if (match && match[0] === label) {
const currentHttpProtocol = (link?.href.match(/^http(s)?:\/\//gi) || [])[0];
if (match[2]) {
@@ -51,8 +51,8 @@ const urlRegexBase = `|(?:[-a-zA-Z0-9@:%._\\+~#=]{1,64}\\.))[-a-zA-Z0-9@:%._\\+~
const httpRegex = `(?:https?:\\/\\/)`;
const httpCapturedRegex= `(https?:\\/\\/)`;
export const URL_REGEX = new RegExp(`((?:(?:${httpRegex}${urlRegexBase})`, 'gi');
export const URL_REGEX_WITH_INFOS = new RegExp(`((?:(?:${httpCapturedRegex}${urlRegexBase})`, 'gi');
export const URL_REGEX = new RegExp(`((?:(?:${httpRegex}${urlRegexBase})`, 'i');
export const URL_REGEX_WITH_INFOS = new RegExp(`((?:(?:${httpCapturedRegex}${urlRegexBase})`, 'i');
export const YOUTUBE_URL_GET_VIDEO_ID =
/^(?:(?:https?:)?\/\/)?(?:(?:www|m)\.)?(?:youtube\.com|youtu\.be)(?:\/(?:[\w-]+\?v=|embed\/|v\/)?)([^\s?&#]+)(?:\S+)?$/i;
export const EMAIL_REGEX = /^(mailto:)?[\w-.]+@(?:[\w-]+\.)+[\w-]{2,4}$/i;
@@ -1520,26 +1520,6 @@ export function makeContentsInline(node) {
}
}
/**
* Returns an array of url infos for url matched in the given string.
*
* @param {String} string
* @returns {Array}
*/
export function getUrlsInfosInString(string) {
let infos = [],
match;
while ((match = URL_REGEX_WITH_INFOS.exec(string))) {
infos.push({
url: match[2] ? match[0] : 'http://' + match[0],
label: match[0],
index: match.index,
length: match[0].length,
});
}
return infos;
}
// optimize: use the parent Oid to speed up detection
export function getOuid(node, optimize = false) {
while (node && !isUnbreakable(node)) {