Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow switching platforms #6964

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/getting-started/v3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ 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.
* To disable CSS injection (necessary in some iframe contexts), `Chart.platform.disableCSSInjection = true` is no longer supported. Pass `disableCSSInjection: true` in the config object instead.
benmccann marked this conversation as resolved.
Show resolved Hide resolved

### Animations

Expand Down Expand Up @@ -161,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

Expand Down Expand Up @@ -206,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` no longer exists. Every chart instance now has a separate platform instance, `chart.platform`.
* `Chart.platforms` is an object that contains two usable platform classes, `BasicPlatform` and `DomPlatform`. It also contains `Platform`, a class that all platforms must extend from.
* If the canvas passed in is an instance of `OffscreenCanvas`, the `BasicPlatform` is automatically used.
7 changes: 3 additions & 4 deletions samples/advanced/content-security-policy.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
var utils = Samples.utils;

// CSP: disable automatic style injection
Chart.platform.disableCSSInjection = true;

utils.srand(110);

function generateData() {
Expand Down Expand Up @@ -48,7 +45,9 @@ window.addEventListener('load', function() {
return (size / 24) * base;
}
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: trailing comma

// CSP: disable automatic style injection
disableCSSInjection: true
}
});
});
58 changes: 50 additions & 8 deletions src/core/core.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 '../platforms/platforms';
import plugins from './core.plugins';
import scaleService from '../core/core.scaleService';
import Tooltip from './core.tooltip';
Expand Down Expand Up @@ -148,13 +148,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;
Expand Down Expand Up @@ -196,14 +221,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
Expand All @@ -226,6 +251,23 @@ class Chart {
return me;
}

/**
* @private
*/
_initializePlatform(canvas, config) {
const me = this;

if (config.platform) {
me.platform = new config.platform(config);
} else if (!isDomSupported()) {
me.platform = new BasicPlatform(config);
} else if (window.OffscreenCanvas && canvas instanceof window.OffscreenCanvas) {
me.platform = new BasicPlatform(config);
} else {
me.platform = new DomPlatform(config);
}
}

clear() {
helpers.canvas.clear(this);
return this;
Expand Down Expand Up @@ -851,7 +893,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;
}
Expand Down Expand Up @@ -880,7 +922,7 @@ class Chart {
};

helpers.each(me.options.events, function(type) {
platform.addEventListener(me, type, listener);
me.platform.addEventListener(me, type, listener);
listeners[type] = listener;
});

Expand All @@ -891,7 +933,7 @@ class Chart {
me.resize();
};

platform.addEventListener(me, 'resize', listener);
me.platform.addEventListener(me, 'resize', listener);
listeners.resize = listener;
}
}
Expand All @@ -908,7 +950,7 @@ class Chart {

delete me._listeners;
helpers.each(listeners, function(listener, type) {
platform.removeEventListener(me, type, listener);
me.platform.removeEventListener(me, type, listener);
});
}

Expand Down
6 changes: 2 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ 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 './platforms/platforms';
import pluginsCore from './core/core.plugins';
import Scale from './core/core.scale';
import scaleService from './core/core.scaleService';
Expand All @@ -34,7 +34,7 @@ Chart.Element = Element;
Chart.elements = elements;
Chart.Interaction = Interaction;
Chart.layouts = layouts;
Chart.platform = platform;
Chart.platforms = platforms;
Chart.plugins = pluginsCore;
Chart.Scale = Scale;
Chart.scaleService = scaleService;
Expand All @@ -59,8 +59,6 @@ for (var k in plugins) {
}
}

Chart.platform.initialize();

if (typeof window !== 'undefined') {
window.Chart = Chart;
}
Expand Down
23 changes: 15 additions & 8 deletions src/platforms/platform.basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
* @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;
}
'use strict';

return item && item.getContext('2d') || null;
import Platform from './platform';

/**
* 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 Platform
*/
export default class BasicPlatform extends Platform {
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;
}
};
}
78 changes: 36 additions & 42 deletions src/platforms/platform.dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import helpers from '../helpers';
import stylesheet from './platform.dom.css';
import Platform from './platform';

var EXPANDO_KEY = '$chartjs';
var CSS_PREFIX = 'chartjs-';
Expand Down Expand Up @@ -312,29 +313,34 @@ function injectCSS(rootNode, css) {
}
}

export default {
/**
* 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
*/
disableCSSInjection: false,

/**
* Platform class for charts that can access the DOM and global window/document properties
* @extends Platform
*/
export default class DomPlatform extends Platform {
/**
* This property holds whether this platform is enabled for the current environment.
* Currently used by platform.js to select the proper implementation.
* @private
* @constructor
* @param {object} options - The chart options
*/
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
constructor(options) {
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 = options.disableCSSInjection;
benmccann marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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.
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -446,4 +440,4 @@ export default {

removeListener(canvas, type, proxy);
}
};
}
Loading