fix(ShadowDomStrategy): always inline import rules

fixes #1694
This commit is contained in:
Victor Berchet 2015-06-18 10:07:43 +02:00 committed by Yegor Jbanov
parent d575915d7a
commit 1c4d233fe7
9 changed files with 149 additions and 43 deletions

View File

@ -108,9 +108,9 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
}, },
[NgZone]), [NgZone]),
bind(ShadowDomStrategy) bind(ShadowDomStrategy)
.toFactory((styleUrlResolver, doc) => .toFactory((styleInliner, styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(
new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), styleInliner, styleUrlResolver, doc.head),
[StyleUrlResolver, DOCUMENT_TOKEN]), [StyleInliner, StyleUrlResolver, DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler, DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),

View File

@ -27,8 +27,11 @@ import {
* - see `ShadowCss` for more information and limitations. * - see `ShadowCss` for more information and limitations.
*/ */
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy { export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
constructor(public styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) { styleInliner: StyleInliner;
super(styleUrlResolver, styleHost);
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) {
super(styleInliner, styleUrlResolver, styleHost);
this.styleInliner = styleInliner;
} }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> { processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> {
@ -37,20 +40,21 @@ export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomSt
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl); cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl); var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
var ret = null;
if (isPromise(inlinedCss)) { if (isPromise(inlinedCss)) {
DOM.setText(styleEl, ''); DOM.setText(styleEl, '');
return (<Promise<string>>inlinedCss) ret = (<Promise<string>>inlinedCss)
.then((css) => { .then((css) => {
css = shimCssForComponent(css, hostComponentId); css = shimCssForComponent(css, hostComponentId);
DOM.setText(styleEl, css); DOM.setText(styleEl, css);
this._moveToStyleHost(styleEl);
}); });
} else { } else {
var css = shimCssForComponent(<string>inlinedCss, hostComponentId); var css = shimCssForComponent(<string>inlinedCss, hostComponentId);
DOM.setText(styleEl, css); DOM.setText(styleEl, css);
this._moveToStyleHost(styleEl);
return null;
} }
this._moveToStyleHost(styleEl);
return ret;
} }
_moveToStyleHost(styleEl) { _moveToStyleHost(styleEl) {

View File

@ -1,3 +1,4 @@
import {isPromise} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async'; import {Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -7,6 +8,7 @@ import * as viewModule from '../view/view';
import {LightDom} from './light_dom'; import {LightDom} from './light_dom';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {StyleUrlResolver} from './style_url_resolver'; import {StyleUrlResolver} from './style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {insertSharedStyleText} from './util'; import {insertSharedStyleText} from './util';
/** /**
@ -19,7 +21,10 @@ import {insertSharedStyleText} from './util';
* - you can **not** use shadow DOM specific selectors in the styles * - you can **not** use shadow DOM specific selectors in the styles
*/ */
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy { export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
constructor(public styleUrlResolver: StyleUrlResolver, public styleHost) { super(); } constructor(public styleInliner: StyleInliner, public styleUrlResolver: StyleUrlResolver,
public styleHost) {
super();
}
hasNativeContentElement(): boolean { return false; } hasNativeContentElement(): boolean { return false; }
@ -31,11 +36,19 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> { processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> {
var cssText = DOM.getText(styleEl); var cssText = DOM.getText(styleEl);
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl); cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
DOM.setText(styleEl, cssText); var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
DOM.remove(styleEl);
var ret = null;
if (isPromise(inlinedCss)) {
DOM.setText(styleEl, '');
ret = (<Promise<string>>inlinedCss).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>inlinedCss);
}
insertSharedStyleText(cssText, this.styleHost, styleEl); insertSharedStyleText(cssText, this.styleHost, styleEl);
return null; return ret;
} }
} }

View File

@ -1,3 +1,4 @@
import {isPromise} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async'; import {Promise} from 'angular2/src/facade/async';
import {Injectable} from 'angular2/di'; import {Injectable} from 'angular2/di';
@ -5,6 +6,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {StyleUrlResolver} from './style_url_resolver'; import {StyleUrlResolver} from './style_url_resolver';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
/** /**
* This strategies uses the native Shadow DOM support. * This strategies uses the native Shadow DOM support.
@ -14,14 +16,23 @@ import {ShadowDomStrategy} from './shadow_dom_strategy';
*/ */
@Injectable() @Injectable()
export class NativeShadowDomStrategy extends ShadowDomStrategy { export class NativeShadowDomStrategy extends ShadowDomStrategy {
constructor(public styleUrlResolver: StyleUrlResolver) { super(); } constructor(public styleInliner: StyleInliner, public styleUrlResolver: StyleUrlResolver) {
super();
}
prepareShadowRoot(el) { return DOM.createShadowRoot(el); } prepareShadowRoot(el) { return DOM.createShadowRoot(el); }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> { processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> {
var cssText = DOM.getText(styleEl); var cssText = DOM.getText(styleEl);
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl); cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
DOM.setText(styleEl, cssText); var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
if (isPromise(inlinedCss)) {
return (<Promise<string>>inlinedCss).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>inlinedCss);
return null; return null;
} }
}
} }

View File

@ -85,9 +85,9 @@ function _getAppBindings() {
bind(DOCUMENT_TOKEN) bind(DOCUMENT_TOKEN)
.toValue(appDoc), .toValue(appDoc),
bind(ShadowDomStrategy) bind(ShadowDomStrategy)
.toFactory((styleUrlResolver, doc) => .toFactory((styleInliner, styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(
new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), styleInliner, styleUrlResolver, doc.head),
[StyleUrlResolver, DOCUMENT_TOKEN]), [StyleInliner, StyleUrlResolver, DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler, DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),

View File

@ -23,18 +23,26 @@ import {
} from 'angular2/src/render/dom/shadow_dom/util'; } from 'angular2/src/render/dom/shadow_dom/util';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {isBlank} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {XHR} from 'angular2/src/render/xhr';
export function main() { export function main() {
var strategy; var strategy;
describe('EmulatedUnscopedShadowDomStrategy', () => { describe('EmulatedUnscopedShadowDomStrategy', () => {
var styleHost; var xhr, styleHost;
beforeEach(() => { beforeEach(() => {
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver); var styleUrlResolver = new StyleUrlResolver(urlResolver);
xhr = new FakeXHR();
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
styleHost = el('<div></div>'); styleHost = el('<div></div>');
strategy = new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, styleHost); strategy = new EmulatedUnscopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost);
resetShadowDomCache(); resetShadowDomCache();
}); });
@ -49,11 +57,20 @@ export function main() {
expect(styleElement).toHaveText(".foo {background-image: url('http://base/img.jpg');}"); expect(styleElement).toHaveText(".foo {background-image: url('http://base/img.jpg');}");
}); });
it('should not inline import rules', () => { it('should inline @import rules', inject([AsyncTestCompleter], (async) => {
var styleElement = el('<style>@import "other.css";</style>'); xhr.reply('http://base/one.css', '.one {}');
var styleElement = el('<style>@import "one.css";</style>');
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement); strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText("@import 'http://base/other.css';"); expect(stylePromise).toBePromise();
expect(styleElement).toHaveText('');
stylePromise.then((_) => {
expect(styleElement).toHaveText('.one {}\n');
async.done();
}); });
}));
it('should move the style element to the style host', () => { it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>'); var compileElement = el('<div><style>.one {}</style></div>');
@ -79,3 +96,23 @@ export function main() {
}); });
} }
class FakeXHR extends XHR {
_responses: Map<string, string>;
constructor() {
super();
this._responses = MapWrapper.create();
}
get(url: string): Promise<string> {
var response = MapWrapper.get(this._responses, url);
if (isBlank(response)) {
return PromiseWrapper.reject('xhr error', null);
}
return PromiseWrapper.resolve(response);
}
reply(url: string, response: string) { MapWrapper.set(this._responses, url, response); }
}

View File

@ -17,6 +17,13 @@ import {
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy'; } from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {XHR} from 'angular2/src/render/xhr';
import {isBlank} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -24,10 +31,13 @@ export function main() {
var strategy; var strategy;
describe('NativeShadowDomStrategy', () => { describe('NativeShadowDomStrategy', () => {
var xhr;
beforeEach(() => { beforeEach(() => {
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver); var styleUrlResolver = new StyleUrlResolver(urlResolver);
strategy = new NativeShadowDomStrategy(styleUrlResolver); xhr = new FakeXHR();
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
strategy = new NativeShadowDomStrategy(styleInliner, styleUrlResolver);
}); });
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
@ -44,10 +54,39 @@ export function main() {
.toHaveText(".foo {" + "background-image: url('http://base/img.jpg');" + "}"); .toHaveText(".foo {" + "background-image: url('http://base/img.jpg');" + "}");
}); });
it('should not inline import rules', () => { it('should inline @import rules', inject([AsyncTestCompleter], (async) => {
var styleElement = el('<style>@import "other.css";</style>'); xhr.reply('http://base/one.css', '.one {}');
var styleElement = el('<style>@import "one.css";</style>');
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement); strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText("@import 'http://base/other.css';"); expect(stylePromise).toBePromise();
stylePromise.then((_) => {
expect(styleElement).toHaveText('.one {}\n');
async.done();
}); });
}));
}); });
} }
class FakeXHR extends XHR {
_responses: Map<string, string>;
constructor() {
super();
this._responses = MapWrapper.create();
}
get(url: string): Promise<string> {
var response = MapWrapper.get(this._responses, url);
if (isBlank(response)) {
return PromiseWrapper.reject('xhr error', null);
}
return PromiseWrapper.resolve(response);
}
reply(url: string, response: string) { MapWrapper.set(this._responses, url, response); }
}

View File

@ -43,17 +43,17 @@ export function main() {
.toFactory((styleInliner, styleUrlResolver) => new EmulatedScopedShadowDomStrategy( .toFactory((styleInliner, styleUrlResolver) => new EmulatedScopedShadowDomStrategy(
styleInliner, styleUrlResolver, styleHost), styleInliner, styleUrlResolver, styleHost),
[StyleInliner, StyleUrlResolver]), [StyleInliner, StyleUrlResolver]),
"unscoped": bind(ShadowDomStrategy) "unscoped": bind(ShadowDomStrategy).toFactory(
.toFactory((styleUrlResolver) => new EmulatedUnscopedShadowDomStrategy( (styleInliner, styleUrlResolver) => new EmulatedUnscopedShadowDomStrategy(
styleUrlResolver, styleHost), styleInliner, styleUrlResolver, null), [StyleInliner, StyleUrlResolver])
[StyleUrlResolver])
}; };
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
StringMapWrapper.set( StringMapWrapper.set(
strategies, "native", strategies, "native",
bind(ShadowDomStrategy) bind(ShadowDomStrategy)
.toFactory((styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), .toFactory((styleInliner, styleUrlResolver) =>
[StyleUrlResolver])); new NativeShadowDomStrategy(styleInliner, styleUrlResolver),
[StyleInliner, StyleUrlResolver]));
} }
beforeEach(() => { styleHost = el('<div></div>'); }); beforeEach(() => { styleHost = el('<div></div>'); });

View File

@ -17,6 +17,7 @@ import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {reflector} from 'angular2/src/reflection/reflection'; import {reflector} from 'angular2/src/reflection/reflection';
@ -37,7 +38,8 @@ export function main() {
count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]); count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]);
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver); var styleUrlResolver = new StyleUrlResolver(urlResolver);
var shadowDomStrategy = new NativeShadowDomStrategy(styleUrlResolver); var styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
var shadowDomStrategy = new NativeShadowDomStrategy(styleInliner, styleUrlResolver);
var renderCompiler = new rc.DefaultDomCompiler(new Parser(new Lexer()), shadowDomStrategy, var renderCompiler = new rc.DefaultDomCompiler(new Parser(new Lexer()), shadowDomStrategy,
new TemplateLoader(null, urlResolver)); new TemplateLoader(null, urlResolver));
var compiler = var compiler =