/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-env browser */ "use strict"; const { Task } = require("devtools/shared/task"); const flags = require("devtools/shared/flags"); const { getToplevelWindow } = require("sdk/window/utils"); const { DOM: dom, createClass, addons, PropTypes } = require("devtools/client/shared/vendor/react"); const Types = require("../types"); const e10s = require("../utils/e10s"); const message = require("../utils/message"); module.exports = createClass({ /** * This component is not allowed to depend directly on frequently changing * data (width, height) due to the use of `dangerouslySetInnerHTML` below. * Any changes in props will cause the <iframe> to be removed and added again, * throwing away the current state of the page. */ displayName: "Browser", propTypes: { location: Types.location.isRequired, swapAfterMount: PropTypes.bool.isRequired, onBrowserMounted: PropTypes.func.isRequired, onContentResize: PropTypes.func.isRequired, }, mixins: [ addons.PureRenderMixin ], /** * Once the browser element has mounted, load the frame script and enable * various features, like floating scrollbars. */ componentDidMount: Task.async(function* () { // If we are not swapping browsers after mount, it's safe to start the frame // script now. if (!this.props.swapAfterMount) { yield this.startFrameScript(); } // Notify manager.js that this browser has mounted, so that it can trigger // a swap if needed and continue with the rest of its startup. this.props.onBrowserMounted(); // If we are swapping browsers after mount, wait for the swap to complete // and start the frame script after that. if (this.props.swapAfterMount) { yield message.wait(window, "start-frame-script"); yield this.startFrameScript(); message.post(window, "start-frame-script:done"); } // Stop the frame script when requested in the future. message.wait(window, "stop-frame-script").then(() => { this.stopFrameScript(); }); }), onContentResize(msg) { let { onContentResize } = this.props; let { width, height } = msg.data; onContentResize({ width, height, }); }, startFrameScript: Task.async(function* () { let { onContentResize } = this; let browser = this.refs.browserContainer.querySelector("iframe.browser"); let mm = browser.frameLoader.messageManager; // Notify tests when the content has received a resize event. This is not // quite the same timing as when we _set_ a new size around the browser, // since it still needs to do async work before the content is actually // resized to match. e10s.on(mm, "OnContentResize", onContentResize); let ready = e10s.once(mm, "ChildScriptReady"); mm.loadFrameScript("resource://devtools/client/responsivedesign/" + "responsivedesign-child.js", true); yield ready; let browserWindow = getToplevelWindow(window); let requiresFloatingScrollbars = !browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches; yield e10s.request(mm, "Start", { requiresFloatingScrollbars, // Tests expect events on resize to yield on various size changes notifyOnResize: flags.testing, }); }), stopFrameScript: Task.async(function* () { let { onContentResize } = this; let browser = this.refs.browserContainer.querySelector("iframe.browser"); let mm = browser.frameLoader.messageManager; e10s.off(mm, "OnContentResize", onContentResize); yield e10s.request(mm, "Stop"); message.post(window, "stop-frame-script:done"); }), render() { let { location, } = this.props; // innerHTML expects & to be an HTML entity location = location.replace(/&/g, "&"); return dom.div( { ref: "browserContainer", className: "browser-container", /** * React uses a whitelist for attributes, so we need some way to set * attributes it does not know about, such as @mozbrowser. If this were * the only issue, we could use componentDidMount or ref: node => {} to * set the atttibutes. In the case of @remote, the attribute must be set * before the element is added to the DOM to have any effect, which we * are able to do with this approach. * * @noisolation and @allowfullscreen are needed so that these frames * have the same access to browser features as regular browser tabs. * The `swapFrameLoaders` platform API we use compares such features * before allowing the swap to proceed. */ dangerouslySetInnerHTML: { __html: `<iframe class="browser" mozbrowser="true" remote="true" noisolation="true" allowfullscreen="true" src="${location}" width="100%" height="100%"> </iframe>` } } ); }, });