From c1727a65fcef3ab6ac72e34856f918386970c19a Mon Sep 17 00:00:00 2001
From: plainheart <yhen@all-my-life.cn>
Date: Wed, 19 Jun 2024 11:19:25 +0800
Subject: [PATCH] refactor(tooltip): add `encodeHTMLContent` option to specify
 whether the content should be encoded by default

---
 src/component/tooltip/TooltipView.ts | 14 +++++++++++++-
 src/util/graphic.ts                  |  6 ++----
 src/util/types.ts                    |  6 ++++++
 3 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts
index e585aef9ef..d5bf5d8f21 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -20,7 +20,7 @@ import { bind, each, clone, trim, isString, isFunction, isArray, isObject, exten
 import env from 'zrender/src/core/env';
 import TooltipHTMLContent from './TooltipHTMLContent';
 import TooltipRichContent from './TooltipRichContent';
-import { convertToColorString, formatTpl, TooltipMarker } from '../../util/format';
+import { convertToColorString, encodeHTML, formatTpl, TooltipMarker } from '../../util/format';
 import { parsePercent } from '../../util/number';
 import { Rect } from '../../util/graphic';
 import findPointFromSeries from '../axisPointer/findPointFromSeries';
@@ -724,9 +724,11 @@ class TooltipView extends ComponentView {
         el: ECElement,
         dispatchAction: ExtensionAPI['dispatchAction']
     ) {
+        const isHTMLRenderMode = this._renderMode === 'html';
         const ecData = getECData(el);
         const tooltipConfig = ecData.tooltipConfig;
         let tooltipOpt = tooltipConfig.option || {};
+        let encodeHTMLContent = tooltipOpt.encodeHTMLContent;
         if (isString(tooltipOpt)) {
             const content = tooltipOpt;
             tooltipOpt = {
@@ -734,6 +736,16 @@ class TooltipView extends ComponentView {
                 // Fixed formatter
                 formatter: content
             };
+            // when `tooltipConfig.option` is a string rather than an object,
+            // we can't know if the content needs to be encoded
+            // for the sake of security, encode it by default.
+            encodeHTMLContent = true;
+        }
+
+        if (encodeHTMLContent && isHTMLRenderMode && tooltipOpt.content) {
+            // clone might be unnecessary?
+            tooltipOpt = clone(tooltipOpt);
+            tooltipOpt.content = encodeHTML(tooltipOpt.content);
         }
 
         const tooltipModelCascade = [tooltipOpt] as TooltipModelOptionCascade[];
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index d98cf674c8..09c84953c4 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -67,8 +67,6 @@ import {
 } from 'zrender/src/core/util';
 import { getECData } from './innerStore';
 import ComponentModel from '../model/Component';
-import { encodeHTML } from 'zrender/src/core/dom';
-
 
 import {
     updateProps,
@@ -601,11 +599,11 @@ export function setTooltipConfig(opt: {
     const ecData = getECData(opt.el);
     ecData.componentMainType = mainType;
     ecData.componentIndex = componentIndex;
-
     ecData.tooltipConfig = {
         name: itemName,
         option: defaults({
-            content: encodeHTML(itemName),
+            content: itemName,
+            encodeHTMLContent: true,
             formatterParams: formatterParams
         }, itemTooltipOptionObj)
     };
diff --git a/src/util/types.ts b/src/util/types.ts
index 8fd9435013..653b687d91 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -1336,6 +1336,12 @@ export interface CommonTooltipOption<FormatterParams> {
 export type ComponentItemTooltipOption<T> = CommonTooltipOption<T> & {
     // Default content HTML.
     content?: string;
+    /**
+     * Whether to encode HTML content according to `tooltip.renderMode`.
+     *
+     * e.g. renderMode 'html' needs to encode but 'richText' does not.
+     */
+    encodeHTMLContent?: boolean;
     formatterParams?: ComponentItemTooltipLabelFormatterParams;
 };
 export type ComponentItemTooltipLabelFormatterParams = {