Skip to content

Commit

Permalink
Add a ponyfill for ResizeObserver on older browsers.
Browse files Browse the repository at this point in the history
This fixes nbagg on Firefox ESR.

Fixes matplotlib#18481.
  • Loading branch information
QuLogic committed Oct 15, 2020
1 parent 7d0cb13 commit cb77e0d
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 2 deletions.
18 changes: 16 additions & 2 deletions lib/matplotlib/backends/web_backend/js/mpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,17 @@ mpl.figure.prototype._init_canvas = function () {
'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'
);

var resizeObserver = new ResizeObserver(function (entries) {
// 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) {
var nentries = entries.length;
for (var i = 0; i < nentries; i++) {
var entry = entries[i];
Expand Down Expand Up @@ -222,7 +232,7 @@ mpl.figure.prototype._init_canvas = function () {
}
}
});
resizeObserver.observe(canvas_div);
this.resizeObserverInstance.observe(canvas_div);

function on_mouse_event_closure(name) {
return function (event) {
Expand Down Expand Up @@ -673,3 +683,7 @@ 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};
1 change: 1 addition & 0 deletions lib/matplotlib/backends/web_backend/js/nbagg_mpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mpl.figure.prototype.handle_close = function (fig, msg) {
'cleared',
fig._remove_fig_handler
);
fig.resizeObserverInstance.unobserve(fig.canvas_div);

// Update the output cell to use the data from the current canvas.
fig.push_to_output();
Expand Down
3 changes: 3 additions & 0 deletions lib/matplotlib/backends/web_backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
"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"
}
}
73 changes: 73 additions & 0 deletions tools/embed_js.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Script to embed JavaScript dependencies in mpl.js.
"""

from pathlib import Path
import re
import subprocess
import sys


# The list of packages to embed, in some form that `npm install` can use.
JAVASCRIPT_PACKAGES = [
# Polyfill/ponyfill for ResizeObserver.
'@jsxtools/resize-observer',
]
# 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 gen_embedded_lines(web_backend_path):
for pkg in JAVASCRIPT_PACKAGES:
index = web_backend_path / 'node_modules' / pkg / 'index.js'
if not index.exists():
# Exact version should already be saved in package.json, so we use
# --no-save here.
try:
subprocess.run(['npm', 'install', '--no-save', pkg],
cwd=web_backend_path)
except FileNotFoundError as err:
raise ValueError(
f'npm must be installed to fetch {pkg}') from err
name = safe_name(pkg)
print('Embedding', index, 'as', name)
yield '// prettier-ignore\n'
for line in index.read_text().splitlines(keepends=True):
line = line.replace('module.exports=function',
f'var {name}=function')
yield line


def build_mpljs(web_backend_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)
mpljs.writelines(gen_embedded_lines(web_backend_path))


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")
build_mpljs(web_backend_path)

0 comments on commit cb77e0d

Please sign in to comment.