forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			174 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
 * Copied from <https://github.com/ryanseddon/react-frame-component> 0.3.2,
 | 
						|
 * by Ryan Seddon, under the MIT license, since that original version requires
 | 
						|
 * a browserify-style loader.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * This is an array of frames that are queued and waiting to be loaded before
 | 
						|
 * their rendering is completed.
 | 
						|
 *
 | 
						|
 * @type {Array}
 | 
						|
 */
 | 
						|
window.queuedFrames = [];
 | 
						|
 | 
						|
(function() {
 | 
						|
  "use strict";
 | 
						|
 | 
						|
  /**
 | 
						|
   * Renders this.props.children inside an <iframe>.
 | 
						|
   *
 | 
						|
   * Works by creating the iframe, waiting for that to finish, and then
 | 
						|
   * rendering the children inside that.  Waits for a while in the hopes that the
 | 
						|
   * contents will have been rendered, and then fires a callback indicating that.
 | 
						|
   *
 | 
						|
   * @see onContentsRendered for the gory details about this.
 | 
						|
   *
 | 
						|
   * @type {ReactComponentFactory<P>}
 | 
						|
   */
 | 
						|
  window.Frame = React.createClass({
 | 
						|
    propTypes: {
 | 
						|
      children: React.PropTypes.oneOfType([
 | 
						|
        React.PropTypes.element,
 | 
						|
        React.PropTypes.arrayOf(React.PropTypes.element)
 | 
						|
      ]).isRequired,
 | 
						|
      className: React.PropTypes.string,
 | 
						|
      /* By default, <link rel="stylesheet> nodes from the containing frame's
 | 
						|
         head will be cloned into this iframe.  However, if the link also has
 | 
						|
         a "class" attribute, we only clone it if that class attribute is the
 | 
						|
         same as cssClass.  This allows us to avoid injecting stylesheets that
 | 
						|
         aren't intended for this rendering of this component. */
 | 
						|
      cssClass: React.PropTypes.string,
 | 
						|
      head: React.PropTypes.node,
 | 
						|
      height: React.PropTypes.number,
 | 
						|
      onContentsRendered: React.PropTypes.func,
 | 
						|
      style: React.PropTypes.object,
 | 
						|
      width: React.PropTypes.number
 | 
						|
    },
 | 
						|
    render: function() {
 | 
						|
      return React.createElement("iframe", {
 | 
						|
        style: this.props.style,
 | 
						|
        head: this.props.head,
 | 
						|
        width: this.props.width,
 | 
						|
        height: this.props.height,
 | 
						|
        className: this.props.className
 | 
						|
      });
 | 
						|
    },
 | 
						|
    componentDidMount: function() {
 | 
						|
      this.renderFrameContents();
 | 
						|
    },
 | 
						|
    renderFrameContents: function() {
 | 
						|
      function isStyleSheet(node) {
 | 
						|
        return node.tagName.toLowerCase() === "link" &&
 | 
						|
          node.getAttribute("rel") === "stylesheet";
 | 
						|
      }
 | 
						|
 | 
						|
      var childDoc = this.getDOMNode().contentDocument;
 | 
						|
      if (childDoc && childDoc.readyState === "complete") {
 | 
						|
        // Remove this from the queue.
 | 
						|
        window.queuedFrames.splice(window.queuedFrames.indexOf(this), 1);
 | 
						|
 | 
						|
        var iframeHead = childDoc.querySelector("head");
 | 
						|
        var parentHeadChildren = document.querySelector("head").children;
 | 
						|
 | 
						|
        [].forEach.call(parentHeadChildren, function(parentHeadNode) {
 | 
						|
 | 
						|
          // if this node is a CSS stylesheet...
 | 
						|
          if (isStyleSheet(parentHeadNode)) {
 | 
						|
            // and it has a class different from the one that this frame does,
 | 
						|
            // return immediately instead of appending it.  Note that this
 | 
						|
            // explicitly does not check for cssClass existence, because
 | 
						|
            // non-existence of cssClass will be different from a style
 | 
						|
            // element that does have a class on it, and we want it to return
 | 
						|
            // in that case.
 | 
						|
            if (parentHeadNode.hasAttribute("class") &&
 | 
						|
                parentHeadNode.getAttribute("class") !== this.props.cssClass) {
 | 
						|
              return;
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          iframeHead.appendChild(parentHeadNode.cloneNode(true));
 | 
						|
        }.bind(this));
 | 
						|
 | 
						|
        var contents = React.createElement("div",
 | 
						|
          undefined,
 | 
						|
          this.props.head,
 | 
						|
          this.props.children
 | 
						|
        );
 | 
						|
 | 
						|
        React.render(contents, childDoc.body, this.fireOnContentsRendered);
 | 
						|
 | 
						|
        // Set the RTL mode. We assume for now that rtl is the only query parameter.
 | 
						|
        //
 | 
						|
        // See also "ShowCase" in ui-showcase.jsx
 | 
						|
        if (document.location.search === "?rtl=1") {
 | 
						|
          childDoc.documentElement.setAttribute("lang", "ar");
 | 
						|
          childDoc.documentElement.setAttribute("dir", "rtl");
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        // Queue it, only if it isn't already. We do need to set the timeout
 | 
						|
        // regardless, as this function can get re-entered several times.
 | 
						|
        if (window.queuedFrames.indexOf(this) === -1) {
 | 
						|
          window.queuedFrames.push(this);
 | 
						|
        }
 | 
						|
        setTimeout(this.renderFrameContents, 0);
 | 
						|
      }
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Fires the onContentsRendered callback passed in via this.props,
 | 
						|
     * with the first argument set to the window global used by the iframe.
 | 
						|
     * This is useful in extracting things specific to that iframe (such as
 | 
						|
     * the matchMedia function) for use by code running in that iframe.  Once
 | 
						|
     * React gets a more complete "context" feature:
 | 
						|
     *
 | 
						|
     * https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#solution-make-context-parent-based-instead-of-owner-based
 | 
						|
     *
 | 
						|
     * we should be able to avoid reaching into the DOM like this.
 | 
						|
     *
 | 
						|
     * XXX wait a little while.  After React has rendered this iframe (eg the
 | 
						|
     * virtual DOM cache gets flushed to the browser), there's still more stuff
 | 
						|
     * that needs to happen before layout completes.  If onContentsRendered fires
 | 
						|
     * before that happens, the wrong sizes (eg remote stream vertical height
 | 
						|
     * of 0) are used to compute the position in the MediaSetupStream, resulting
 | 
						|
     * in everything looking wonky.  One high likelihood candidate for the delay
 | 
						|
     * here involves loading/decode poster images, but even using link
 | 
						|
     * rel=prefetch on those isn't enough to workaround this problem, so there
 | 
						|
     * may be more.
 | 
						|
     *
 | 
						|
     * There doesn't appear to be a good cross-browser way to handle this
 | 
						|
     * at the moment without gross violation of encapsulation (see
 | 
						|
     * http://stackoverflow.com/questions/27241186/how-to-determine-when-document-has-loaded-after-loading-external-csshttp://stackoverflow.com/questions/27241186/how-to-determine-when-document-has-loaded-after-loading-external-css
 | 
						|
     * for discussion of a related problem.
 | 
						|
     *
 | 
						|
     * For now, just wait for multiple seconds.  Yuck.
 | 
						|
     */
 | 
						|
    fireOnContentsRendered: function() {
 | 
						|
      if (!this.props.onContentsRendered) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      var contentWindow;
 | 
						|
      try {
 | 
						|
        contentWindow = this.getDOMNode().contentWindow;
 | 
						|
        if (!contentWindow) {
 | 
						|
          throw new Error("no content window returned");
 | 
						|
        }
 | 
						|
 | 
						|
      } catch (ex) {
 | 
						|
        console.error("exception getting content window", ex);
 | 
						|
      }
 | 
						|
 | 
						|
      // Using bind to construct a "partial function", where |this| is unchanged,
 | 
						|
      // but the first parameter is guaranteed to be set.  Details at
 | 
						|
      // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Example.3A_Partial_Functions
 | 
						|
      setTimeout(this.props.onContentsRendered.bind(undefined, contentWindow),
 | 
						|
                 3000);
 | 
						|
    },
 | 
						|
    componentDidUpdate: function() {
 | 
						|
      this.renderFrameContents();
 | 
						|
    },
 | 
						|
    componentWillUnmount: function() {
 | 
						|
      React.unmountComponentAtNode(React.findDOMNode(this).contentDocument.body);
 | 
						|
    }
 | 
						|
  });
 | 
						|
})();
 |