mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-09 12:50:53 +00:00
[add] support for CSP policy requiring 'nonce' on <style>
CSP policy may prevent writing to `<style>` unless a `nonce` attribute
is set. This change makes that possible by moving the modality-related
styles into the main style sheet, and allowing additional props to be
provided to the `<style>` element when rendering on the server. For
example:
```
const { element, getStyleElement } = AppRegistry.getApplication('App');
const html = renderToString(element);
const css = renderToStaticMarkup(getStyleElement({ nonce }));
```
This commit is contained in:
@@ -39,6 +39,14 @@ describe('AppRegistry', () => {
|
|||||||
expect(styleElement).toMatchSnapshot();
|
expect(styleElement).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('"getStyleElement" adds props to <style>', () => {
|
||||||
|
const nonce = '2Bz9RM/UHvBbmo3jK/PbYZ==';
|
||||||
|
AppRegistry.registerComponent('App', () => RootComponent);
|
||||||
|
const { getStyleElement } = AppRegistry.getApplication('App', {});
|
||||||
|
const styleElement = getStyleElement({ nonce });
|
||||||
|
expect(styleElement.props.nonce).toBe(nonce);
|
||||||
|
});
|
||||||
|
|
||||||
test('"getStyleElement" produces styles that are a function of rendering "element"', () => {
|
test('"getStyleElement" produces styles that are a function of rendering "element"', () => {
|
||||||
const getApplicationStyles = appName => {
|
const getApplicationStyles = appName => {
|
||||||
const { element, getStyleElement } = AppRegistry.getApplication(appName, {});
|
const { element, getStyleElement } = AppRegistry.getApplication(appName, {});
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ export function getApplication(
|
|||||||
</AppContainer>
|
</AppContainer>
|
||||||
);
|
);
|
||||||
// Don't escape CSS text
|
// Don't escape CSS text
|
||||||
const getStyleElement = () => {
|
const getStyleElement = props => {
|
||||||
const sheet = styleResolver.getStyleSheet();
|
const sheet = styleResolver.getStyleSheet();
|
||||||
return <style dangerouslySetInnerHTML={{ __html: sheet.textContent }} id={sheet.id} />;
|
return (
|
||||||
|
<style {...props} dangerouslySetInnerHTML={{ __html: sheet.textContent }} id={sheet.id} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return { element, getStyleElement };
|
return { element, getStyleElement };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,6 @@ export default class StyleSheetManager {
|
|||||||
return className;
|
return className;
|
||||||
}
|
}
|
||||||
|
|
||||||
injectKeyframe(): string {
|
|
||||||
// return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
_addToCache(className, prop, value) {
|
_addToCache(className, prop, value) {
|
||||||
const cache = this._cache;
|
const cache = this._cache;
|
||||||
if (!cache.byProp[prop]) {
|
if (!cache.byProp[prop]) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||||
|
import modality from './modality';
|
||||||
|
|
||||||
export default class WebStyleSheet {
|
export default class WebStyleSheet {
|
||||||
_cssRules = [];
|
_cssRules = [];
|
||||||
@@ -29,6 +30,7 @@ export default class WebStyleSheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (domStyleElement) {
|
if (domStyleElement) {
|
||||||
|
modality(domStyleElement);
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
this._sheet = domStyleElement.sheet;
|
this._sheet = domStyleElement.sheet;
|
||||||
this._textContent = domStyleElement.textContent;
|
this._textContent = domStyleElement.textContent;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import modality from '../../modules/modality';
|
|
||||||
import StyleSheet from './StyleSheet';
|
import StyleSheet from './StyleSheet';
|
||||||
|
|
||||||
// initialize focus-ring fix
|
|
||||||
modality();
|
|
||||||
|
|
||||||
// allow component styles to be editable in React Dev Tools
|
// allow component styles to be editable in React Dev Tools
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
const { canUseDOM } = require('fbjs/lib/ExecutionEnvironment');
|
const { canUseDOM } = require('fbjs/lib/ExecutionEnvironment');
|
||||||
|
|||||||
+13
-24
@@ -18,12 +18,14 @@
|
|||||||
|
|
||||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||||
|
|
||||||
const modality = () => {
|
const rule = ':focus { outline: none; }';
|
||||||
|
let ruleExists = false;
|
||||||
|
|
||||||
|
const modality = styleElement => {
|
||||||
if (!canUseDOM) {
|
if (!canUseDOM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let styleElement;
|
|
||||||
let hadKeyboardEvent = false;
|
let hadKeyboardEvent = false;
|
||||||
let keyboardThrottleTimeoutID = 0;
|
let keyboardThrottleTimeoutID = 0;
|
||||||
|
|
||||||
@@ -55,21 +57,6 @@ const modality = () => {
|
|||||||
'[role=textbox]'
|
'[role=textbox]'
|
||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the focus ring by default
|
|
||||||
*/
|
|
||||||
const initialize = () => {
|
|
||||||
// check if the style sheet needs to be created
|
|
||||||
const id = 'react-native-modality';
|
|
||||||
styleElement = document.getElementById(id);
|
|
||||||
if (!styleElement) {
|
|
||||||
// removes focus styles by default
|
|
||||||
const style = `<style id="${id}">:focus { outline: none; }</style>`;
|
|
||||||
document.head.insertAdjacentHTML('afterbegin', style);
|
|
||||||
styleElement = document.getElementById(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes whether the given element should automatically trigger the
|
* Computes whether the given element should automatically trigger the
|
||||||
* `focus-ring`.
|
* `focus-ring`.
|
||||||
@@ -83,20 +70,22 @@ const modality = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the focus ring to the focused element
|
* Add the focus ring style
|
||||||
*/
|
*/
|
||||||
const addFocusRing = () => {
|
const addFocusRing = () => {
|
||||||
if (styleElement) {
|
if (styleElement && ruleExists) {
|
||||||
styleElement.disabled = true;
|
styleElement.sheet.deleteRule(0);
|
||||||
|
ruleExists = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the focus ring
|
* Remove the focus ring style
|
||||||
*/
|
*/
|
||||||
const removeFocusRing = () => {
|
const removeFocusRing = () => {
|
||||||
if (styleElement) {
|
if (styleElement && !ruleExists) {
|
||||||
styleElement.disabled = false;
|
styleElement.sheet.insertRule(rule, 0);
|
||||||
|
ruleExists = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,7 +125,7 @@ const modality = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (document.body && document.body.addEventListener) {
|
if (document.body && document.body.addEventListener) {
|
||||||
initialize();
|
removeFocusRing();
|
||||||
document.body.addEventListener('keydown', handleKeyDown, true);
|
document.body.addEventListener('keydown', handleKeyDown, true);
|
||||||
document.body.addEventListener('focus', handleFocus, true);
|
document.body.addEventListener('focus', handleFocus, true);
|
||||||
document.body.addEventListener('blur', handleBlur, true);
|
document.body.addEventListener('blur', handleBlur, true);
|
||||||
@@ -31,10 +31,18 @@ const AppRegistryScreen = () => (
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<DocItem
|
<DocItem
|
||||||
description="Use this for server-side rendering to HTML. Returns a object of the given application's element, and a function to get styles once the element is rendered."
|
description={
|
||||||
|
<AppText>
|
||||||
|
Use this for server-side rendering to HTML. Returns an object containing the given
|
||||||
|
application's element and a function to get styles once the element is rendered.
|
||||||
|
Additional props can be passed to the <Code>getStyleElement</Code> function, e.g., your
|
||||||
|
CSP policy may require a <Code>nonce</Code> to be set on <Code>style</Code>
|
||||||
|
elements.
|
||||||
|
</AppText>
|
||||||
|
}
|
||||||
label="web"
|
label="web"
|
||||||
name="static getApplication"
|
name="static getApplication"
|
||||||
typeInfo="(appKey: string, appParameters: ?object) => { element: ReactElement; getStyleElement: () => ReactElement }"
|
typeInfo="(appKey: string, appParameters: ?object) => { element: ReactElement; getStyleElement: (props) => ReactElement }"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DocItem
|
<DocItem
|
||||||
|
|||||||
Reference in New Issue
Block a user