From 66ebf465a2e796d9defa6ecc8d86671dfe9bef88 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 24 Apr 2026 17:46:15 -0400 Subject: [PATCH] Remove ResizeObserver ponyfill We added this about 6 years ago for Firefox 68 ESR support. According to [CanIUse](https://caniuse.com/?search=ResizeObserver), `ResizeObserver` is well supported now on 95.84% of browsers by usage, and all browsers by _released version_ since early 2020. For Firefox specifically, support was added in 69, and 68 ESR stopped being supported after 78 ESR was released in mid 2020. --- LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER | 108 ------------------ lib/matplotlib/backends/web_backend/js/mpl.js | 16 +-- .../backends/web_backend/package.json | 3 - meson.build | 2 - tools/embed_js.py | 102 ----------------- 5 files changed, 1 insertion(+), 230 deletions(-) delete mode 100644 LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER delete mode 100644 tools/embed_js.py diff --git a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER deleted file mode 100644 index 0bc1fa7060b7..000000000000 --- a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER +++ /dev/null @@ -1,108 +0,0 @@ -# CC0 1.0 Universal - -## Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator and -subsequent owner(s) (each and all, an “owner”) of an original work of -authorship and/or a database (each, a “Work”). - -Certain owners wish to permanently relinquish those rights to a Work for the -purpose of contributing to a commons of creative, cultural and scientific works -(“Commons”) that the public can reliably and without fear of later claims of -infringement build upon, modify, incorporate in other works, reuse and -redistribute as freely as possible in any form whatsoever and for any purposes, -including without limitation commercial purposes. These owners may contribute -to the Commons to promote the ideal of a free culture and the further -production of creative, cultural and scientific works, or to gain reputation or -greater distribution for their Work in part through the use and efforts of -others. - -For these and/or other purposes and motivations, and without any expectation of -additional consideration or compensation, the person associating CC0 with a -Work (the “Affirmer”), to the extent that he or she is an owner of Copyright -and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and -publicly distribute the Work under its terms, with knowledge of his or her -Copyright and Related Rights in the Work and the meaning and intended legal -effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be - protected by copyright and related or neighboring rights (“Copyright and - Related Rights”). Copyright and Related Rights include, but are not limited - to, the following: - 1. the right to reproduce, adapt, distribute, perform, display, communicate, - and translate a Work; - 2. moral rights retained by the original author(s) and/or performer(s); - 3. publicity and privacy rights pertaining to a person’s image or likeness - depicted in a Work; - 4. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(i), below; - 5. rights protecting the extraction, dissemination, use and reuse of data in - a Work; - 6. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation thereof, - including any amended or successor version of such directive); and - 7. other similar, equivalent or corresponding rights throughout the world - based on applicable law or treaty, and any national implementations - thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention of, - applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and - unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright - and Related Rights and associated claims and causes of action, whether now - known or unknown (including existing as well as future claims and causes of - action), in the Work (i) in all territories worldwide, (ii) for the maximum - duration provided by applicable law or treaty (including future time - extensions), (iii) in any current or future medium and for any number of - copies, and (iv) for any purpose whatsoever, including without limitation - commercial, advertising or promotional purposes (the “Waiver”). Affirmer - makes the Waiver for the benefit of each member of the public at large and - to the detriment of Affirmer’s heirs and successors, fully intending that - such Waiver shall not be subject to revocation, rescission, cancellation, - termination, or any other legal or equitable action to disrupt the quiet - enjoyment of the Work by the public as contemplated by Affirmer’s express - Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason be - judged legally invalid or ineffective under applicable law, then the Waiver - shall be preserved to the maximum extent permitted taking into account - Affirmer’s express Statement of Purpose. In addition, to the extent the - Waiver is so judged Affirmer hereby grants to each affected person a - royalty-free, non transferable, non sublicensable, non exclusive, - irrevocable and unconditional license to exercise Affirmer’s Copyright and - Related Rights in the Work (i) in all territories worldwide, (ii) for the - maximum duration provided by applicable law or treaty (including future time - extensions), (iii) in any current or future medium and for any number of - copies, and (iv) for any purpose whatsoever, including without limitation - commercial, advertising or promotional purposes (the “License”). The License - shall be deemed effective as of the date CC0 was applied by Affirmer to the - Work. Should any part of the License for any reason be judged legally - invalid or ineffective under applicable law, such partial invalidity or - ineffectiveness shall not invalidate the remainder of the License, and in - such case Affirmer hereby affirms that he or she will not (i) exercise any - of his or her remaining Copyright and Related Rights in the Work or (ii) - assert any associated claims and causes of action with respect to the Work, - in either case contrary to Affirmer’s express Statement of Purpose. - -4. Limitations and Disclaimers. - 1. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - 2. Affirmer offers the Work as-is and makes no representations or warranties - of any kind concerning the Work, express, implied, statutory or - otherwise, including without limitation warranties of title, - merchantability, fitness for a particular purpose, non infringement, or - the absence of latent or other defects, accuracy, or the present or - absence of errors, whether or not discoverable, all to the greatest - extent permissible under applicable law. - 3. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person’s Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the Work. - 4. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to this - CC0 or use of the Work. - -For more information, please see -http://creativecommons.org/publicdomain/zero/1.0/. diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index 7745cbcf1e98..d42fe5e17972 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -183,17 +183,7 @@ mpl.figure.prototype._init_canvas = function () { 'z-index: 1;' ); - // Apply a ponyfill if ResizeObserver is not implemented by browser. - if (this.ResizeObserver === undefined) { - if (window.ResizeObserver !== undefined) { - this.ResizeObserver = window.ResizeObserver; - } else { - var obs = _JSXTOOLS_RESIZE_OBSERVER({}); - this.ResizeObserver = obs.ResizeObserver; - } - } - - this.resizeObserverInstance = new this.ResizeObserver(function (entries) { + this.resizeObserverInstance = new ResizeObserver(function (entries) { // There's no need to resize if the WebSocket is not connected: // - If it is still connecting, then we will get an initial resize from // Python once it connects. @@ -728,7 +718,3 @@ mpl.figure.prototype.toolbar_button_onclick = function (name) { mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) { this.message.textContent = tooltip; }; - -///////////////// REMAINING CONTENT GENERATED BY embed_js.py ///////////////// -// prettier-ignore -var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError("Constructor requires 'new' operator");i.set(this,e)}function h(){throw new TypeError("Function is not a constructor")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line diff --git a/lib/matplotlib/backends/web_backend/package.json b/lib/matplotlib/backends/web_backend/package.json index 95bd8fdf54e6..e2a4009a971b 100644 --- a/lib/matplotlib/backends/web_backend/package.json +++ b/lib/matplotlib/backends/web_backend/package.json @@ -11,8 +11,5 @@ "lint:check": "npm run prettier:check && npm run eslint:check", "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", "prettier:check": "prettier --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"" - }, - "dependencies": { - "@jsxtools/resize-observer": "^1.0.4" } } diff --git a/meson.build b/meson.build index 7d1f3a433fbb..24a09821a047 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,6 @@ project( find_program('python3', 'python', version: '>= 3.11'), '-m', 'setuptools_scm', check: true).stdout().strip(), # qt_editor backend is MIT - # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 # STIX, Computer Modern, and Last Resort are OFL # DejaVu is Bitstream Vera and Public Domain license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', @@ -19,7 +18,6 @@ project( 'LICENSE/LICENSE_COURIERTEN', 'LICENSE/LICENSE_FREETYPE', 'LICENSE/LICENSE_HARFBUZZ', - 'LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER', 'LICENSE/LICENSE_LAST_RESORT_FONT', 'LICENSE/LICENSE_LIBRAQM', 'LICENSE/LICENSE_QT4_EDITOR', diff --git a/tools/embed_js.py b/tools/embed_js.py deleted file mode 100644 index 571bf80238e9..000000000000 --- a/tools/embed_js.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Script to embed JavaScript dependencies in mpl.js. -""" - -from collections import namedtuple -from pathlib import Path -import re -import shutil -import subprocess -import sys - - -Package = namedtuple('Package', [ - # The package to embed, in some form that `npm install` can use. - 'name', - # The path to the source file within the package to embed. - 'source', - # The path to the license file within the package to embed. - 'license']) -# The list of packages to embed, in some form that `npm install` can use. -JAVASCRIPT_PACKAGES = [ - # Polyfill/ponyfill for ResizeObserver. - Package('@jsxtools/resize-observer', 'index.js', 'LICENSE.md'), -] -# This is the magic line that must exist in mpl.js, after which the embedded -# JavaScript will be appended. -MPLJS_MAGIC_HEADER = ( - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py " - "/////////////////\n") - - -def safe_name(name): - """ - Make *name* safe to use as a JavaScript variable name. - """ - return '_'.join(re.split(r'[@/-]', name)).upper() - - -def prep_package(web_backend_path, pkg): - source = web_backend_path / 'node_modules' / pkg.name / pkg.source - license = web_backend_path / 'node_modules' / pkg.name / pkg.license - if not source.exists(): - # Exact version should already be saved in package.json, so we use - # --no-save here. - try: - subprocess.run(['npm', 'install', '--no-save', pkg.name], - cwd=web_backend_path) - except FileNotFoundError as err: - raise ValueError( - f'npm must be installed to fetch {pkg.name}') from err - if not source.exists(): - raise ValueError( - f'{pkg.name} package is missing source in {pkg.source}') - elif not license.exists(): - raise ValueError( - f'{pkg.name} package is missing license in {pkg.license}') - - return source, license - - -def gen_embedded_lines(pkg, source): - name = safe_name(pkg.name) - print('Embedding', source, 'as', name) - yield '// prettier-ignore\n' - for line in source.read_text().splitlines(): - yield (line.replace('module.exports=function', f'var {name}=function') - + ' // eslint-disable-line\n') - - -def build_mpljs(web_backend_path, license_path): - mpljs_path = web_backend_path / "js/mpl.js" - mpljs_orig = mpljs_path.read_text().splitlines(keepends=True) - try: - mpljs_orig = mpljs_orig[:mpljs_orig.index(MPLJS_MAGIC_HEADER) + 1] - except IndexError as err: - raise ValueError( - f'The mpl.js file *must* have the exact line: {MPLJS_MAGIC_HEADER}' - ) from err - - with mpljs_path.open('w') as mpljs: - mpljs.writelines(mpljs_orig) - - for pkg in JAVASCRIPT_PACKAGES: - source, license = prep_package(web_backend_path, pkg) - mpljs.writelines(gen_embedded_lines(pkg, source)) - - shutil.copy(license, - license_path / f'LICENSE{safe_name(pkg.name)}') - - -if __name__ == '__main__': - # Write the mpl.js file. - if len(sys.argv) > 1: - web_backend_path = Path(sys.argv[1]) - else: - web_backend_path = (Path(__file__).parent.parent / - "lib/matplotlib/backends/web_backend") - if len(sys.argv) > 2: - license_path = Path(sys.argv[2]) - else: - license_path = Path(__file__).parent.parent / "LICENSE" - build_mpljs(web_backend_path, license_path)