mediawiki-extensions-Visual.../modules/ve/ui/ve.ui.Frame.js
Roan Kattouw e9ca44c86c Transplant CSS from the main document to each iframe
We previously manually loaded CSS into these frames, which is flawed
because it completely bypasses ResourceLoader (so CSSJanus didn't flip
them, necessitating a bunch of hacks for RTL), and doesn't pull in
MediaWiki styles (so templates inside references don't render correctly).
Instead, this commit copies all styles from the main document into each
frame's document, inlining what it can.

Loading all styles in dialogs and inspectors caused some problems,
initially. We didn't namespace our styles for dialogs vs. inspectors
at all; the only reason inspector styles weren't being applied to dialogs
and vice versa was because we controlled which files were being loaded
in which context. This commit namespaces the inspector and dialog styles
where needed so they don't conflict and try to override each other.

Tested in Vector and Monobook, but not in Apex and not in RTL.

ve.init.mw.ViewPageTarget*.css:
* Namespace styles that are only intended for the main document
* Undo Monobook's font-size: x-small; in frames

*Dialog.js:
* Remove addLocalStylesheet() calls, we don't need those any more
** ve.ui.MWDialog seems to be unneeded now, we may want to remove it

*.css:
* Remove @noflip-ped RTL rules where they were just flipped versions of
  their LTR counterparts

ve.ui.Dialog.css, ve.ui.Inspector.css:
* Namespace styles with .ve-ui-dialog-content / .ve-ui-inspector-content

ve.ui.Frame.css:
* Move the margin:0 and padding:0 here (were in the frame <body>'s style
  attribute) and add background:none to prevent frames from getting
  the skin's background (grey in Vector, a book in Monobook)

ve.ui.Dialog.js, ve.ui.Inspector.js:
* Add ve-ui-dialog-content / ve-ui-inspector-content class to the
  frame's content <div> so we can restrict styles to only apply in
  dialogs / inspectors

ve.ui.Frame.js:
* Replace infrastructure for @import-ing stylesheets with transplantation
* Remove code polling to see when the stylesheets were loaded
** We can't do this in the new approach AFAIK, since all styles in the
   frame are either inlined or inaccessible due to the same-origin policy
** We also shouldn't need it because the browser should have cached the
   styles when it loaded the main document
* Apply ve-ui-frame-body class to the frame's <body> so we can style it
** Move inline padding:0;margin:0; into ve.ui.Frame.css
** Move the ve-ltr/ve-rtl class up to the <body>

ve.ui.Window.js:
* Remove infrastructure registering stylesheet URLs to load

Change-Id: I4a37115301811ad860f4578344a04873ea8c2b69
2013-07-09 16:13:28 -07:00

165 lines
4.5 KiB
JavaScript

/*!
* VisualEditor UserInterface Frame class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* UserInterface iframe abstraction.
*
* @class
* @extends ve.Element
* @mixins ve.EventEmitter
*
* @constructor
* @param {Object} [config] Config options
*/
ve.ui.Frame = function VeUiFrame( config ) {
// Parent constructor
ve.Element.call( this, config );
// Mixin constructors
ve.EventEmitter.call( this );
// Properties
this.initialized = false;
this.config = config;
// Initialize
this.$
.addClass( 've-ui-frame' )
.attr( { 'frameborder': 0, 'scrolling': 'no' } );
};
/* Inheritance */
ve.inheritClass( ve.ui.Frame, ve.Element );
ve.mixinClass( ve.ui.Frame, ve.EventEmitter );
/* Static Properties */
ve.ui.Frame.static.tagName = 'iframe';
/* Events */
/**
* @event initialize
*/
/* Methods */
/**
* Load the frame contents.
*
* Once the iframe's stylesheets are loaded, the `initialize` event will be emitted.
*
* Sounds simple right? Read on...
*
* When you create a dynamic iframe using open/write/close, the window.load event for the
* iframe is triggered when you call close, and there's no further load event to indicate that
* everything is actually loaded.
*
* By dynamically adding stylesheet links, we can detect when each link is loaded by testing if we
* have access to each of their `sheet.cssRules` properties. Every 10ms we poll to see if we have
* access to the style's `sheet.cssRules` property yet.
*
* However, because of security issues, we never have such access if the stylesheet came from a
* different site. Thus, we are left with linking to the stylesheets through a style element with
* multiple `@import` statements - which ends up being simpler anyway. Since we created that style,
* we always have access, and its contents are only available when everything is done loading.
*
* @emits initialize
*/
ve.ui.Frame.prototype.load = function () {
var win = this.$.prop( 'contentWindow' ),
doc = win.document;
// Figure out directionality:
this.dir = this.$.closest( '[dir]' ).prop( 'dir' ) || 'ltr';
// Initialize contents
doc.open();
doc.write(
'<!doctype html>' +
'<html>' +
'<body class="ve-ui-frame-body ve-' + this.dir + '" style="direction:' + this.dir + ';" dir="' + this.dir + '">' +
'<div class="ve-ui-frame-content"></div>' +
'</body>' +
'</html>'
);
doc.close();
// Properties
this.$$ = ve.Element.get$$( doc, this );
this.$content = this.$$( '.ve-ui-frame-content' );
this.$document = this.$$( doc );
this.transplantStyles();
this.initialized = true;
this.emit( 'initialize' );
};
/**
* Transplant the CSS styles from the frame's parent document to the frame's document.
*
* This loops over the style sheets in the parent document, and copies their tags to the
* frame's document. `<link>` tags pointing to same-origin style sheets are inlined as `<style>` tags;
* `<link>` tags pointing to foreign URLs and `<style>` tags are copied verbatim.
*/
ve.ui.Frame.prototype.transplantStyles = function () {
var i, ilen, j, jlen, sheet, rules, cssText, styleNode,
newDoc = this.$document[0],
parentDoc = this.getElementDocument();
for ( i = 0, ilen = parentDoc.styleSheets.length; i < ilen; i++ ) {
sheet = parentDoc.styleSheets[i];
try {
rules = sheet.cssRules;
} catch ( e ) { }
if ( sheet.ownerNode.nodeName.toLowerCase() === 'link' && rules ) {
// This is a <link> tag pointing to a same-origin style sheet. Rebuild it as a
// <style> tag
cssText = '';
for ( j = 0, jlen = sheet.cssRules.length; j < jlen; j++ ) {
cssText += sheet.cssRules[j].cssText + '\n';
}
cssText += '/* Transplanted styles from ' + sheet.href + ' */\n';
styleNode = newDoc.createElement( 'style' );
styleNode.textContent = cssText;
} else {
// It's either a <style> tag or a <link> tag pointing to a foreign URL; just copy
// it to the new document
styleNode = newDoc.importNode( sheet.ownerNode, true );
}
newDoc.body.appendChild( styleNode );
}
};
/**
* Run a callback as soon as the frame has been initialized.
*
* @param {Function} callback
*/
ve.ui.Frame.prototype.run = function ( callback ) {
if ( this.initialized ) {
callback();
} else {
this.once( 'initialize', callback );
}
};
/**
* Sets the size of the frame.
*
* @method
* @param {number} width Frame width in pixels
* @param {number} height Frame height in pixels
* @chainable
*/
ve.ui.Frame.prototype.setSize = function ( width, height ) {
this.$.css( { 'width': width, 'height': height } );
return this;
};