diff --git a/@plotly/dash-component-plugins/package.json b/@plotly/dash-component-plugins/package.json index f7324754c4..eaf967ced9 100644 --- a/@plotly/dash-component-plugins/package.json +++ b/@plotly/dash-component-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@plotly/dash-component-plugins", - "version": "1.0.2", + "version": "1.2.0", "description": "Plugins for Dash Components", "repository": { "type": "git", diff --git a/@plotly/dash-component-plugins/src/History.js b/@plotly/dash-component-plugins/src/History.js new file mode 100644 index 0000000000..7b34bc0e14 --- /dev/null +++ b/@plotly/dash-component-plugins/src/History.js @@ -0,0 +1,13 @@ +const ON_CHANGE = '_dashprivate_historychange'; + +export default class History { + static dispatchChangeEvent() { + window.dispatchEvent(new CustomEvent(ON_CHANGE)); + } + + static onChange(listener) { + window.addEventListener(ON_CHANGE, listener); + + return () => window.removeEventListener(ON_CHANGE, listener); + } +} \ No newline at end of file diff --git a/@plotly/dash-component-plugins/src/dynamicImport.js b/@plotly/dash-component-plugins/src/dynamicImport.js index 29530bc9cf..c4ed18b121 100644 --- a/@plotly/dash-component-plugins/src/dynamicImport.js +++ b/@plotly/dash-component-plugins/src/dynamicImport.js @@ -27,5 +27,11 @@ export const asyncDecorator = (target, promise) => { return state.get; }; +export const inheritAsyncDecorator = (target, source) => { + Object.defineProperty(target, '_dashprivate_isLazyComponentReady', { + get: () => isReady(source) + }); +} + export const isReady = target => target && target._dashprivate_isLazyComponentReady; diff --git a/@plotly/dash-component-plugins/src/index.js b/@plotly/dash-component-plugins/src/index.js index 4a670ca381..3448f9e7b8 100644 --- a/@plotly/dash-component-plugins/src/index.js +++ b/@plotly/dash-component-plugins/src/index.js @@ -1,3 +1,5 @@ -import { asyncDecorator, isReady } from './dynamicImport'; +import { asyncDecorator, inheritAsyncDecorator, isReady } from './dynamicImport'; +import History from './History'; -export { asyncDecorator, isReady }; \ No newline at end of file +export { asyncDecorator, inheritAsyncDecorator, isReady }; +export { History }; \ No newline at end of file diff --git a/@plotly/webpack-dash-dynamic-import/package.json b/@plotly/webpack-dash-dynamic-import/package.json index 824fd5de02..b2504683b2 100644 --- a/@plotly/webpack-dash-dynamic-import/package.json +++ b/@plotly/webpack-dash-dynamic-import/package.json @@ -1,6 +1,6 @@ { "name": "@plotly/webpack-dash-dynamic-import", - "version": "1.1.4", + "version": "1.1.5", "description": "Webpack Plugin for Dynamic Import in Dash", "repository": { "type": "git", diff --git a/@plotly/webpack-dash-dynamic-import/src/index.js b/@plotly/webpack-dash-dynamic-import/src/index.js index 816c6d278f..ff7a14dad1 100644 --- a/@plotly/webpack-dash-dynamic-import/src/index.js +++ b/@plotly/webpack-dash-dynamic-import/src/index.js @@ -20,7 +20,15 @@ var getCurrentScript = function() { if (!script) { /* Shim for IE11 and below */ /* Do not take into account async scripts and inline scripts */ - var scripts = Array.from(document.getElementsByTagName('script')).filter(function(s) { return !s.async && !s.text && !s.textContent; }); + + var doc_scripts = document.getElementsByTagName('script'); + var scripts = []; + + for (var i = 0; i < doc_scripts.length; i++) { + scripts.push(doc_scripts[i]); + } + + scripts = scripts.filter(function(s) { return !s.async && !s.text && !s.textContent; }); script = scripts.slice(-1)[0]; } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7e4a3c85..6028f4751c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [1.9.0] - 2020-02-04 +### Fixed +- [#1080](https://github.com/plotly/dash/pull/1080) Handle case where dash fails to load when used inside an iframe with a sandbox attribute that only has allow-scripts + ## [1.8.0] - 2020-01-14 ### Added - [#1073](https://github.com/plotly/dash/pull/1073) Two new functions to simplify usage handling URLs and pathnames: `app.get_relative_path` & `app.trim_relative_path`. diff --git a/dash-renderer/package.json b/dash-renderer/package.json index f993949f20..6c5d279b54 100644 --- a/dash-renderer/package.json +++ b/dash-renderer/package.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "1.2.3", + "version": "1.2.4", "description": "render dash components in react", "main": "dash_renderer/dash_renderer.min.js", "scripts": { diff --git a/dash-renderer/src/AccessDenied.react.js b/dash-renderer/src/AccessDenied.react.js index 6dd7ab2808..f70625548a 100644 --- a/dash-renderer/src/AccessDenied.react.js +++ b/dash-renderer/src/AccessDenied.react.js @@ -1,10 +1,13 @@ /* global window:true, document:true */ import React from 'react'; -import {mergeRight} from 'ramda'; +import {mergeRight, once} from 'ramda'; import PropTypes from 'prop-types'; import * as styles from './styles/styles.js'; import * as constants from './constants/constants.js'; +/* eslint-disable-next-line no-console */ +const logWarningOnce = once(console.warn); + function AccessDenied(props) { const {config} = props; const fid = config.fid; @@ -28,9 +31,14 @@ function AccessDenied(props) { { - document.cookie = - `${constants.OAUTH_COOKIE_NAME}=; ` + - 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ + try { + document.cookie = + `${constants.OAUTH_COOKIE_NAME}=; ` + + 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + } catch (e) { + logWarningOnce(e); + } window.location.reload(true); }} > diff --git a/dash-renderer/src/actions/index.js b/dash-renderer/src/actions/index.js index c6c9b6c772..fc582775a7 100644 --- a/dash-renderer/src/actions/index.js +++ b/dash-renderer/src/actions/index.js @@ -16,6 +16,7 @@ import { lensPath, mergeLeft, mergeDeepRight, + once, path, pluck, propEq, @@ -54,10 +55,18 @@ export function hydrateInitialOutputs() { }; } +/* eslint-disable-next-line no-console */ +const logWarningOnce = once(console.warn); + export function getCSRFHeader() { - return { - 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, - }; + try { + return { + 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, + }; + } catch (e) { + logWarningOnce(e); + return {}; + } } function triggerDefaultState(dispatch, getState) { diff --git a/dash/version.py b/dash/version.py index b280975791..e5102d3015 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = '1.8.0' +__version__ = '1.9.0' diff --git a/requires-install.txt b/requires-install.txt index 291eeb1fd2..fe1a4fcf66 100644 --- a/requires-install.txt +++ b/requires-install.txt @@ -1,8 +1,8 @@ Flask>=1.0.2 flask-compress plotly -dash_renderer>=1.2.2 -dash-core-components>=1.6.0 +dash_renderer==1.2.4 +dash-core-components==1.8.0 dash-html-components==1.0.2 -dash-table>=4.5.1 +dash-table==4.6.0 future \ No newline at end of file diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py new file mode 100644 index 0000000000..6a4a93f52c --- /dev/null +++ b/tests/integration/renderer/test_iframe.py @@ -0,0 +1,57 @@ +from multiprocessing import Value + +import dash +from dash.dependencies import Input, Output +from dash.exceptions import PreventUpdate + +import dash_html_components as html + + +def test_rdif001_sandbox_allow_scripts(dash_duo): + app = dash.Dash(__name__) + call_count = Value("i") + + N_OUTPUTS = 50 + + app.layout = html.Div([ + html.Button("click me", id="btn"), + ] + [html.Div(id="output-{}".format(i)) for i in range(N_OUTPUTS)]) + + @app.callback( + [Output("output-{}".format(i), "children") for i in range(N_OUTPUTS)], + [Input("btn", "n_clicks")] + ) + def update_output(n_clicks): + if n_clicks is None: + raise PreventUpdate + + call_count.value += 1 + return ["{}={}".format(i, i + n_clicks) for i in range(N_OUTPUTS)] + + @app.server.after_request + def apply_cors(response): + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Authorization" + return response + + dash_duo.start_server(app) + + iframe = """ + + + + + """ + + html_content = iframe.format(dash_duo.server_url) + + dash_duo.driver.get("data:text/html;charset=utf-8," + html_content) + + dash_duo.driver.switch_to.frame(0) + + dash_duo.wait_for_element('#output-0') + dash_duo.wait_for_element_by_id('btn').click() + dash_duo.wait_for_element('#output-0').text == '0=1' + + assert len(dash_duo.get_logs()) != 0