HtmlPreview.js (3018B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 Component, 9 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 12 13 /* 14 * Response preview component 15 * Display HTML content within a sandbox enabled iframe 16 */ 17 class HTMLPreview extends Component { 18 static get propTypes() { 19 return { 20 responseContent: PropTypes.object.isRequired, 21 responseHeaders: PropTypes.object, 22 url: PropTypes.string.isRequired, 23 }; 24 } 25 26 componentDidMount() { 27 const { container } = this.refs; 28 const browser = container.ownerDocument.createXULElement("browser"); 29 this.browser = browser; 30 browser.setAttribute("type", "content"); 31 browser.setAttribute("remote", "true"); 32 browser.setAttribute("maychangeremoteness", "true"); 33 browser.setAttribute("disableglobalhistory", "true"); 34 35 // Bug 1800916 allow interaction with the preview page until 36 // we find a way to prevent navigation without preventing copy paste from it. 37 // 38 // browser.addEventListener("mousedown", e => e.preventDefault(), { 39 // capture: true, 40 // }); 41 container.appendChild(browser); 42 43 // browsingContext attribute is only available after the browser 44 // is attached to the DOM Tree. 45 browser.browsingContext.allowJavascript = false; 46 47 this.#updatePreview(); 48 } 49 50 componentDidUpdate() { 51 this.#updatePreview(); 52 } 53 54 componentWillUnmount() { 55 this.browser.remove(); 56 } 57 58 #updatePreview() { 59 const { responseContent, responseHeaders, url } = this.props; 60 const htmlBody = responseContent ? responseContent.content.text : ""; 61 const uri = Services.io.newURI( 62 "data:text/html;charset=UTF-8," + encodeURIComponent(htmlBody) 63 ); 64 65 let policyContainer; 66 const cspHeaders = responseHeaders?.headers.filter( 67 e => e.name.toLowerCase() === "content-security-policy" 68 ); 69 if (cspHeaders?.length) { 70 // Merge multiple CSP headers with a comma. 71 const merged = cspHeaders.map(e => e.value).join(","); 72 const csp = ChromeUtils.createCSPFromHeader( 73 merged, 74 new URL(url).URI, 75 Services.scriptSecurityManager.createNullPrincipal({}) 76 ); 77 const PolicyContainer = Components.Constructor( 78 "@mozilla.org/policycontainer;1", 79 "nsIPolicyContainer", 80 "initFromCSP" 81 ); 82 policyContainer = new PolicyContainer(csp); 83 } 84 85 const options = { 86 policyContainer, 87 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 88 }; 89 this.browser.loadURI(uri, options); 90 } 91 92 render() { 93 return dom.div({ className: "html-preview", ref: "container" }); 94 } 95 } 96 97 module.exports = HTMLPreview;