]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Allow switching platforms (#6964)
authorDavid Winegar <david.s.winegar@gmail.com>
Sun, 26 Jan 2020 20:33:20 +0000 (12:33 -0800)
committerEvert Timberg <evert.timberg+github@gmail.com>
Sun, 26 Jan 2020 20:33:20 +0000 (15:33 -0500)
Allow switching platforms

Move the Chart.platform to Chart.platform.current instead, and add
ways to see available platforms and set the current platform. This
is necessary for adding tests that use the "basic" platform.

12 files changed:
docs/getting-started/v3-migration.md
samples/advanced/content-security-policy.js
src/core/core.controller.js
src/index.js
src/platform/platform.base.js [moved from src/platforms/platform.js with 61% similarity]
src/platform/platform.basic.js [new file with mode: 0644]
src/platform/platform.dom.css [moved from src/platforms/platform.dom.css with 100% similarity]
src/platform/platform.dom.js [moved from src/platforms/platform.dom.js with 89% similarity]
src/platform/platform.js [new file with mode: 0644]
src/platform/platforms.js [new file with mode: 0644]
src/platforms/platform.basic.js [deleted file]
test/specs/global.namespace.tests.js

index c1ce806003ef6e8b932668e541fef06a23f6666d..5f1decb31771ff089c1269748a79b3ff043185fe 100644 (file)
@@ -63,6 +63,7 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
 * `scales.[x/y]Axes.time.max` was renamed to `scales[id].max`
 * `scales.[x/y]Axes.time.min` was renamed to `scales[id].min`
 * The dataset option `tension` was renamed to `lineTension`
+* To override the platform class used in a chart instance, pass `platform: PlatformClass` in the config object. Note that the class should be passed, not an instance of the class.
 
 ### Animations
 
@@ -162,6 +163,7 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
 
 * `helpers._alignPixel` was renamed to `helpers.canvas._alignPixel`
 * `helpers._decimalPlaces` was renamed to `helpers.math._decimalPlaces`
+* `chart.initialize` was renamed to `chart._initialize` (labeled as private but not named as such)
 
 ### Changed
 
@@ -207,3 +209,8 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
 
 * The second parameter to `drawPoint` is now the full options object, so `style`, `rotation`, and `radius` are no longer passed explicitly
 
+#### Platform
+
+* `Chart.platform` is no longer the platform object used by charts. It contains only a single configuration option, `disableCSSInjection`. Every chart instance now has a separate platform instance.
+* `Chart.platforms` is an object that contains two usable platform classes, `BasicPlatform` and `DomPlatform`. It also contains `BasePlatform`, a class that all platforms must extend from.
+* If the canvas passed in is an instance of `OffscreenCanvas`, the `BasicPlatform` is automatically used.
index a185332f74bcc9a69097d87741b5697a8ccaafce..a974fc6f349cbe556a4da7dbcdc8d62612d1a916 100644 (file)
@@ -1,10 +1,9 @@
 var utils = Samples.utils;
 
+utils.srand(110);
 // CSP: disable automatic style injection
 Chart.platform.disableCSSInjection = true;
 
-utils.srand(110);
-
 function generateData() {
        var DATA_COUNT = 16;
        var MIN_XY = -150;
index 3d4ca427c386993eb17d2f6393c74acbaa012503..2da5705ebf42bbceafa82385d3c84adbc44336d6 100644 (file)
@@ -6,7 +6,7 @@ import defaults from './core.defaults';
 import helpers from '../helpers/index';
 import Interaction from './core.interaction';
 import layouts from './core.layouts';
-import platform from '../platforms/platform';
+import {BasicPlatform, DomPlatform} from '../platform/platforms';
 import plugins from './core.plugins';
 import scaleService from '../core/core.scaleService';
 
@@ -145,13 +145,38 @@ function onAnimationProgress(ctx) {
        helpers.callback(animationOptions && animationOptions.onProgress, arguments, chart);
 }
 
+function isDomSupported() {
+       return typeof window !== undefined && typeof document !== undefined;
+}
+
+/**
+ * Chart.js can take a string id of a canvas element, a 2d context, or a canvas element itself.
+ * Attempt to unwrap the item passed into the chart constructor so that it is a canvas element (if possible).
+ */
+function getCanvas(item) {
+       if (isDomSupported() && typeof item === 'string') {
+               item = document.getElementById(item);
+       } else if (item.length) {
+               // Support for array based queries (such as jQuery)
+               item = item[0];
+       }
+
+       if (item && item.canvas) {
+               // Support for any object associated to a canvas (including a context2d)
+               item = item.canvas;
+       }
+       return item;
+}
+
 class Chart {
        constructor(item, config) {
                const me = this;
 
                config = initConfig(config);
+               const initialCanvas = getCanvas(item);
+               me._initializePlatform(initialCanvas, config);
 
-               const context = platform.acquireContext(item, config);
+               const context = me.platform.acquireContext(initialCanvas, config);
                const canvas = context && context.canvas;
                const height = canvas && canvas.height;
                const width = canvas && canvas.width;
@@ -193,14 +218,14 @@ class Chart {
                Animator.listen(me, 'complete', onAnimationsComplete);
                Animator.listen(me, 'progress', onAnimationProgress);
 
-               me.initialize();
+               me._initialize();
                me.update();
        }
 
        /**
         * @private
         */
-       initialize() {
+       _initialize() {
                const me = this;
 
                // Before init plugin notification
@@ -221,6 +246,23 @@ class Chart {
                return me;
        }
 
+       /**
+        * @private
+        */
+       _initializePlatform(canvas, config) {
+               const me = this;
+
+               if (config.platform) {
+                       me.platform = new config.platform();
+               } else if (!isDomSupported()) {
+                       me.platform = new BasicPlatform();
+               } else if (window.OffscreenCanvas && canvas instanceof window.OffscreenCanvas) {
+                       me.platform = new BasicPlatform();
+               } else {
+                       me.platform = new DomPlatform();
+               }
+       }
+
        clear() {
                helpers.canvas.clear(this);
                return this;
@@ -244,7 +286,7 @@ class Chart {
                // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
                const newWidth = Math.max(0, Math.floor(helpers.dom.getMaximumWidth(canvas)));
                const newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.dom.getMaximumHeight(canvas)));
-               const newRatio = options.devicePixelRatio || platform.getDevicePixelRatio();
+               const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio();
 
                if (me.width === newWidth && me.height === newHeight && oldRatio === newRatio) {
                        return;
@@ -824,7 +866,7 @@ class Chart {
                if (canvas) {
                        me.unbindEvents();
                        helpers.canvas.clear(me);
-                       platform.releaseContext(me.ctx);
+                       me.platform.releaseContext(me.ctx);
                        me.canvas = null;
                        me.ctx = null;
                }
@@ -849,7 +891,7 @@ class Chart {
                };
 
                helpers.each(me.options.events, function(type) {
-                       platform.addEventListener(me, type, listener);
+                       me.platform.addEventListener(me, type, listener);
                        listeners[type] = listener;
                });
 
@@ -860,7 +902,7 @@ class Chart {
                                me.resize();
                        };
 
-                       platform.addEventListener(me, 'resize', listener);
+                       me.platform.addEventListener(me, 'resize', listener);
                        listeners.resize = listener;
                }
        }
@@ -877,7 +919,7 @@ class Chart {
 
                delete me._listeners;
                helpers.each(listeners, function(listener, type) {
-                       platform.removeEventListener(me, type, listener);
+                       me.platform.removeEventListener(me, type, listener);
                });
        }
 
index e472b1528fa55326abe495e42bc23714d202ebe1..b32770a0972b0b9847a02e2f6306bfc60fa2681b 100644 (file)
@@ -15,7 +15,8 @@ import Element from './core/core.element';
 import elements from './elements';
 import Interaction from './core/core.interaction';
 import layouts from './core/core.layouts';
-import platform from './platforms/platform';
+import platforms from './platform/platforms';
+import platform from './platform/platform';
 import pluginsCore from './core/core.plugins';
 import Scale from './core/core.scale';
 import scaleService from './core/core.scaleService';
@@ -33,6 +34,7 @@ Chart.Element = Element;
 Chart.elements = elements;
 Chart.Interaction = Interaction;
 Chart.layouts = layouts;
+Chart.platforms = platforms;
 Chart.platform = platform;
 Chart.plugins = pluginsCore;
 Chart.Scale = Scale;
@@ -57,8 +59,6 @@ for (var k in plugins) {
        }
 }
 
-Chart.platform.initialize();
-
 if (typeof window !== 'undefined') {
        window.Chart = Chart;
 }
similarity index 61%
rename from src/platforms/platform.js
rename to src/platform/platform.base.js
index a135c0992635836d75ab1b1fee71069965e1be12..5ef81f744987482d35ad4107c4619eccaf9f8d4d 100644 (file)
@@ -1,31 +1,22 @@
 'use strict';
 
-import helpers from '../helpers/index';
-import basic from './platform.basic';
-import dom from './platform.dom';
-
-// @TODO Make possible to select another platform at build time.
-const implementation = dom._enabled ? dom : basic;
-
 /**
- * @namespace Chart.platform
- * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
- * @since 2.4.0
+ * Abstract class that allows abstracting platform dependencies away from the chart.
  */
-export default helpers.extend({
+export default class BasePlatform {
        /**
-        * @since 2.7.0
+        * @constructor
         */
-       initialize: function() {},
+       constructor() {}
 
        /**
         * Called at chart construction time, returns a context2d instance implementing
         * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
-        * @param {*} item - The native item from which to acquire context (platform specific)
+        * @param {canvas} canvas - The canvas from which to acquire context (platform specific)
         * @param {object} options - The chart options
         * @returns {CanvasRenderingContext2D} context2d instance
         */
-       acquireContext: function() {},
+       acquireContext() {}
 
        /**
         * Called at chart destruction time, releases any resources associated to the context
@@ -33,7 +24,7 @@ export default helpers.extend({
         * @param {CanvasRenderingContext2D} context - The context2d instance
         * @returns {boolean} true if the method succeeded, else false
         */
-       releaseContext: function() {},
+       releaseContext() {}
 
        /**
         * Registers the specified listener on the given chart.
@@ -42,7 +33,7 @@ export default helpers.extend({
         * @param {function} listener - Receives a notification (an object that implements
         * the {@link IEvent} interface) when an event of the specified type occurs.
         */
-       addEventListener: function() {},
+       addEventListener() {}
 
        /**
         * Removes the specified listener previously registered with addEventListener.
@@ -50,25 +41,15 @@ export default helpers.extend({
         * @param {string} type - The ({@link IEvent}) type to remove
         * @param {function} listener - The listener function to remove from the event target.
         */
-       removeEventListener: function() {},
+       removeEventListener() {}
 
        /**
-        * Returs current devicePixelRatio of the device this platform is connected to.
+        * @returns {number} the current devicePixelRatio of the device this platform is connected to.
         */
-       getDevicePixelRatio: function() {
+       getDevicePixelRatio() {
                return 1;
        }
-
-}, implementation);
-
-/**
- * @interface IPlatform
- * Allows abstracting platform dependencies away from the chart
- * @borrows Chart.platform.acquireContext as acquireContext
- * @borrows Chart.platform.releaseContext as releaseContext
- * @borrows Chart.platform.addEventListener as addEventListener
- * @borrows Chart.platform.removeEventListener as removeEventListener
- */
+}
 
 /**
  * @interface IEvent
diff --git a/src/platform/platform.basic.js b/src/platform/platform.basic.js
new file mode 100644 (file)
index 0000000..2f2d52c
--- /dev/null
@@ -0,0 +1,22 @@
+/**
+ * Platform fallback implementation (minimal).
+ * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
+ */
+
+'use strict';
+
+import BasePlatform from './platform.base';
+
+/**
+ * Platform class for charts without access to the DOM or to many element properties
+ * This platform is used by default for any chart passed an OffscreenCanvas.
+ * @extends BasePlatform
+ */
+export default class BasicPlatform extends BasePlatform {
+       acquireContext(item) {
+               // To prevent canvas fingerprinting, some add-ons undefine the getContext
+               // method, for example: https://github.com/kkapsner/CanvasBlocker
+               // https://github.com/chartjs/Chart.js/issues/2807
+               return item && item.getContext && item.getContext('2d') || null;
+       }
+}
similarity index 89%
rename from src/platforms/platform.dom.js
rename to src/platform/platform.dom.js
index 6a7c07a2226f6d2768b7a7956ef41b4463af5dfd..4b1cd2b7d59db362e9bd2aadd98e1da413e71a60 100644 (file)
@@ -6,6 +6,8 @@
 
 import helpers from '../helpers';
 import stylesheet from './platform.dom.css';
+import BasePlatform from './platform.base';
+import platform from './platform';
 
 var EXPANDO_KEY = '$chartjs';
 var CSS_PREFIX = 'chartjs-';
@@ -312,29 +314,33 @@ function injectCSS(rootNode, css) {
        }
 }
 
-export default {
+/**
+ * Platform class for charts that can access the DOM and global window/document properties
+ * @extends BasePlatform
+ */
+export default class DomPlatform extends BasePlatform {
        /**
-        * When `true`, prevents the automatic injection of the stylesheet required to
-        * correctly detect when the chart is added to the DOM and then resized. This
-        * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
-        * to be manually imported to make this library compatible with any CSP.
-        * See https://github.com/chartjs/Chart.js/issues/5208
+        * @constructor
         */
-       disableCSSInjection: false,
-
-       /**
-        * This property holds whether this platform is enabled for the current environment.
-        * Currently used by platform.js to select the proper implementation.
-        * @private
-        */
-       _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
+       constructor() {
+               super();
+
+               /**
+                * When `true`, prevents the automatic injection of the stylesheet required to
+                * correctly detect when the chart is added to the DOM and then resized. This
+                * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
+                * to be manually imported to make this library compatible with any CSP.
+                * See https://github.com/chartjs/Chart.js/issues/5208
+                */
+               this.disableCSSInjection = platform.disableCSSInjection;
+       }
 
        /**
         * Initializes resources that depend on platform options.
         * @param {HTMLCanvasElement} canvas - The Canvas element.
         * @private
         */
-       _ensureLoaded: function(canvas) {
+       _ensureLoaded(canvas) {
                if (!this.disableCSSInjection) {
                        // If the canvas is in a shadow DOM, then the styles must also be inserted
                        // into the same shadow DOM.
@@ -343,45 +349,33 @@ export default {
                        var targetNode = root.host ? root : document.head;
                        injectCSS(targetNode, stylesheet);
                }
-       },
-
-       acquireContext: function(item, config) {
-               if (typeof item === 'string') {
-                       item = document.getElementById(item);
-               } else if (item.length) {
-                       // Support for array based queries (such as jQuery)
-                       item = item[0];
-               }
-
-               if (item && item.canvas) {
-                       // Support for any object associated to a canvas (including a context2d)
-                       item = item.canvas;
-               }
+       }
 
+       acquireContext(canvas, config) {
                // To prevent canvas fingerprinting, some add-ons undefine the getContext
                // method, for example: https://github.com/kkapsner/CanvasBlocker
                // https://github.com/chartjs/Chart.js/issues/2807
-               var context = item && item.getContext && item.getContext('2d');
+               var context = canvas && canvas.getContext && canvas.getContext('2d');
 
-               // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
+               // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the canvas is
                // inside an iframe or when running in a protected environment. We could guess the
                // types from their toString() value but let's keep things flexible and assume it's
-               // a sufficient condition if the item has a context2D which has item as `canvas`.
+               // a sufficient condition if the canvas has a context2D which has canvas as `canvas`.
                // https://github.com/chartjs/Chart.js/issues/3887
                // https://github.com/chartjs/Chart.js/issues/4102
                // https://github.com/chartjs/Chart.js/issues/4152
-               if (context && context.canvas === item) {
+               if (context && context.canvas === canvas) {
                        // Load platform resources on first chart creation, to make it possible to
                        // import the library before setting platform options.
-                       this._ensureLoaded(item);
-                       initCanvas(item, config);
+                       this._ensureLoaded(canvas);
+                       initCanvas(canvas, config);
                        return context;
                }
 
                return null;
-       },
+       }
 
-       releaseContext: function(context) {
+       releaseContext(context) {
                const canvas = context.canvas;
                if (!canvas[EXPANDO_KEY]) {
                        return;
@@ -410,9 +404,9 @@ export default {
                canvas.width = canvas.width;
 
                delete canvas[EXPANDO_KEY];
-       },
+       }
 
-       addEventListener: function(chart, type, listener) {
+       addEventListener(chart, type, listener) {
                var canvas = chart.canvas;
                if (type === 'resize') {
                        // Note: the resize event is not supported on all browsers.
@@ -427,9 +421,9 @@ export default {
                }, chart);
 
                addListener(canvas, type, proxy);
-       },
+       }
 
-       removeEventListener: function(chart, type, listener) {
+       removeEventListener(chart, type, listener) {
                var canvas = chart.canvas;
                if (type === 'resize') {
                        // Note: the resize event is not supported on all browsers.
@@ -445,9 +439,9 @@ export default {
                }
 
                removeListener(canvas, type, proxy);
-       },
+       }
 
-       getDevicePixelRatio: function() {
+       getDevicePixelRatio() {
                return window.devicePixelRatio;
        }
-};
+}
diff --git a/src/platform/platform.js b/src/platform/platform.js
new file mode 100644 (file)
index 0000000..ec81cae
--- /dev/null
@@ -0,0 +1,3 @@
+'use strict';
+
+export default {disableCSSInjection: false};
diff --git a/src/platform/platforms.js b/src/platform/platforms.js
new file mode 100644 (file)
index 0000000..a8701f2
--- /dev/null
@@ -0,0 +1,13 @@
+'use strict';
+
+import BasePlatform from './platform.base';
+import BasicPlatform from './platform.basic';
+import DomPlatform from './platform.dom';
+
+export {BasicPlatform, DomPlatform, BasePlatform};
+
+/**
+ * @namespace Chart.platforms
+ * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
+*/
+export default {BasicPlatform, DomPlatform, BasePlatform};
diff --git a/src/platforms/platform.basic.js b/src/platforms/platform.basic.js
deleted file mode 100644 (file)
index f510b35..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Platform fallback implementation (minimal).
- * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
- */
-
-module.exports = {
-       acquireContext: function(item) {
-               if (item && item.canvas) {
-                       // Support for any object associated to a canvas (including a context2d)
-                       item = item.canvas;
-               }
-
-               return item && item.getContext('2d') || null;
-       }
-};
index 8e4ffa5e7e705fa45e9b8a04d52190e80eba7c18..975670c3a41334c1ab90dc65c73e80f473d83d74 100644 (file)
@@ -12,7 +12,7 @@ describe('Chart namespace', function() {
                        expect(Chart.Interaction instanceof Object).toBeTruthy();
                        expect(Chart.layouts instanceof Object).toBeTruthy();
                        expect(Chart.plugins instanceof Object).toBeTruthy();
-                       expect(Chart.platform instanceof Object).toBeTruthy();
+                       expect(Chart.platforms instanceof Object).toBeTruthy();
                        expect(Chart.Scale instanceof Object).toBeTruthy();
                        expect(Chart.scaleService instanceof Object).toBeTruthy();
                        expect(Chart.Ticks instanceof Object).toBeTruthy();