feat(platform-server): add API to render Module and ModuleFactory to string (#14381)
- PlatformState provides an interface to serialize the current Platform State as a string or Document. - renderModule and renderModuleFactory are convenience methods to wait for Angular Application to stabilize and then render the state to a string. - refactor code to remove defaultDoc from DomAdapter and inject DOCUMENT where it's needed.
This commit is contained in:
@ -14,9 +14,8 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('Meta service', () => {
|
||||
|
||||
const metaService: Meta = new Meta(getDOM());
|
||||
const doc: HTMLDocument = getDOM().defaultDoc();
|
||||
const doc: HTMLDocument = getDOM().createHtmlDocument();
|
||||
const metaService: Meta = new Meta(doc);
|
||||
let defaultMeta: HTMLMetaElement;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -14,23 +14,24 @@ import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('title service', () => {
|
||||
const initialTitle = getDOM().getTitle();
|
||||
const titleService = new Title();
|
||||
const doc = getDOM().createHtmlDocument();
|
||||
const initialTitle = getDOM().getTitle(doc);
|
||||
const titleService = new Title(doc);
|
||||
|
||||
afterEach(() => { getDOM().setTitle(initialTitle); });
|
||||
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
|
||||
|
||||
it('should allow reading initial title',
|
||||
() => { expect(titleService.getTitle()).toEqual(initialTitle); });
|
||||
|
||||
it('should set a title on the injected document', () => {
|
||||
titleService.setTitle('test title');
|
||||
expect(getDOM().getTitle()).toEqual('test title');
|
||||
expect(getDOM().getTitle(doc)).toEqual('test title');
|
||||
expect(titleService.getTitle()).toEqual('test title');
|
||||
});
|
||||
|
||||
it('should reset title to empty string if title not provided', () => {
|
||||
titleService.setTitle(null);
|
||||
expect(getDOM().getTitle()).toEqual('');
|
||||
expect(getDOM().getTitle(doc)).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -15,16 +15,20 @@ import {el} from '../../../testing/browser_util';
|
||||
|
||||
export function main() {
|
||||
let domEventPlugin: DomEventsPlugin;
|
||||
let doc: any;
|
||||
|
||||
describe('EventManager', () => {
|
||||
|
||||
beforeEach(() => { domEventPlugin = new DomEventsPlugin(); });
|
||||
beforeEach(() => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
||||
() => {
|
||||
const element = el('<div></div>');
|
||||
const handler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin = new FakeEventManagerPlugin(['click']);
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['click']);
|
||||
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
expect(plugin.eventHandler['click']).toBe(handler);
|
||||
@ -34,8 +38,8 @@ export function main() {
|
||||
const element = el('<div></div>');
|
||||
const clickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin1 = new FakeEventManagerPlugin(['dblclick']);
|
||||
const plugin2 = new FakeEventManagerPlugin(['click', 'dblclick']);
|
||||
const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
|
||||
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', clickHandler);
|
||||
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
||||
@ -45,7 +49,7 @@ export function main() {
|
||||
|
||||
it('should throw when no plugin can handle the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const plugin = new FakeEventManagerPlugin(['dblclick']);
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const manager = new EventManager([plugin], new FakeNgZone());
|
||||
expect(() => manager.addEventListener(element, 'click', null))
|
||||
.toThrowError('No event manager plugin found for event click');
|
||||
@ -54,7 +58,7 @@ export function main() {
|
||||
it('events are caught when fired from a child', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||
getDOM().appendChild(getDOM().defaultDoc().body, element);
|
||||
getDOM().appendChild(doc.body, element);
|
||||
|
||||
const child = getDOM().firstChild(element);
|
||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||
@ -69,7 +73,7 @@ export function main() {
|
||||
|
||||
it('should add and remove global event listeners', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
getDOM().appendChild(getDOM().defaultDoc().body, element);
|
||||
getDOM().appendChild(doc.body, element);
|
||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||
@ -91,7 +95,7 @@ export function main() {
|
||||
class FakeEventManagerPlugin extends EventManagerPlugin {
|
||||
eventHandler: {[event: string]: Function} = {};
|
||||
|
||||
constructor(public supportedEvents: string[]) { super(); }
|
||||
constructor(doc: any, public supportedEvents: string[]) { super(doc); }
|
||||
|
||||
supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
|
||||
|
||||
|
@ -12,7 +12,7 @@ export function main() {
|
||||
describe('HammerGesturesPlugin', () => {
|
||||
|
||||
it('should implement addGlobalEventListener', () => {
|
||||
const plugin = new HammerGesturesPlugin(new HammerGestureConfig());
|
||||
const plugin = new HammerGesturesPlugin(document, new HammerGestureConfig());
|
||||
|
||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||
|
||||
|
@ -59,7 +59,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should implement addGlobalEventListener', () => {
|
||||
const plugin = new KeyEventsPlugin();
|
||||
const plugin = new KeyEventsPlugin(document);
|
||||
|
||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {DomSanitizerImpl} from '../../src/security/dom_sanitization_service';
|
||||
export function main() {
|
||||
t.describe('DOM Sanitization Service', () => {
|
||||
t.it('accepts resource URL values for resource contexts', () => {
|
||||
const svc = new DomSanitizerImpl();
|
||||
const svc = new DomSanitizerImpl(null);
|
||||
const resourceUrl = svc.bypassSecurityTrustResourceUrl('http://hello/world');
|
||||
t.expect(svc.sanitize(SecurityContext.URL, resourceUrl)).toBe('http://hello/world');
|
||||
});
|
||||
|
@ -14,10 +14,12 @@ import {sanitizeHtml} from '../../src/security/html_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('HTML sanitizer', () => {
|
||||
let defaultDoc: any;
|
||||
let originalLog: (msg: any) => any = null;
|
||||
let logMsgs: string[];
|
||||
|
||||
t.beforeEach(() => {
|
||||
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
@ -25,52 +27,55 @@ export function main() {
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
t.it('serializes nested structures', () => {
|
||||
t.expect(sanitizeHtml('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
||||
.toEqual('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('serializes self closing elements', () => {
|
||||
t.expect(sanitizeHtml('<p>Hello <br> World</p>')).toEqual('<p>Hello <br> World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello <br> World</p>'))
|
||||
.toEqual('<p>Hello <br> World</p>');
|
||||
});
|
||||
t.it('supports namespaced elements', () => {
|
||||
t.expect(sanitizeHtml('a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
||||
t.expect(sanitizeHtml(defaultDoc, 'a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
||||
});
|
||||
t.it('supports namespaced attributes', () => {
|
||||
t.expect(sanitizeHtml('<a xlink:href="something">t</a>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="something">t</a>'))
|
||||
.toEqual('<a xlink:href="something">t</a>');
|
||||
t.expect(sanitizeHtml('<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
||||
t.expect(sanitizeHtml('<a xlink:href="javascript:foo()">t</a>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="javascript:foo()">t</a>'))
|
||||
.toEqual('<a xlink:href="unsafe:javascript:foo()">t</a>');
|
||||
});
|
||||
t.it('supports HTML5 elements', () => {
|
||||
t.expect(sanitizeHtml('<main><summary>Works</summary></main>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<main><summary>Works</summary></main>'))
|
||||
.toEqual('<main><summary>Works</summary></main>');
|
||||
});
|
||||
t.it('sanitizes srcset attributes', () => {
|
||||
t.expect(sanitizeHtml('<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
||||
.toEqual('<img srcset="/foo.png 400px, unsafe:javascript:evil() 23px">');
|
||||
});
|
||||
|
||||
t.it('supports sanitizing plain text', () => {
|
||||
t.expect(sanitizeHtml('Hello, World')).toEqual('Hello, World');
|
||||
t.expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World');
|
||||
});
|
||||
t.it('ignores non-element, non-attribute nodes', () => {
|
||||
t.expect(sanitizeHtml('<!-- comments? -->no.')).toEqual('no.');
|
||||
t.expect(sanitizeHtml('<?pi nodes?>no.')).toEqual('no.');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<!-- comments? -->no.')).toEqual('no.');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<?pi nodes?>no.')).toEqual('no.');
|
||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/);
|
||||
});
|
||||
t.it('supports sanitizing escaped entities', () => {
|
||||
t.expect(sanitizeHtml('🚀')).toEqual('🚀');
|
||||
t.expect(sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('does not warn when just re-encoding text', () => {
|
||||
t.expect(sanitizeHtml('<p>Hellö Wörld</p>')).toEqual('<p>Hellö Wörld</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hellö Wörld</p>'))
|
||||
.toEqual('<p>Hellö Wörld</p>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('escapes entities', () => {
|
||||
t.expect(sanitizeHtml('<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml('<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml('<p alt="% & " !">Hello</p>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>'))
|
||||
.toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p alt="% & " !">Hello</p>'))
|
||||
.toEqual('<p alt="% & " !">Hello</p>'); // NB: quote encoded as ASCII ".
|
||||
});
|
||||
t.describe('should strip dangerous elements', () => {
|
||||
@ -80,11 +85,12 @@ export function main() {
|
||||
];
|
||||
|
||||
for (const tag of dangerousTags) {
|
||||
t.it(
|
||||
`${tag}`, () => { t.expect(sanitizeHtml(`<${tag}>evil!</${tag}>`)).toEqual('evil!'); });
|
||||
t.it(`${tag}`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<${tag}>evil!</${tag}>`)).toEqual('evil!');
|
||||
});
|
||||
}
|
||||
t.it(`swallows frame entirely`, () => {
|
||||
t.expect(sanitizeHtml(`<frame>evil!</frame>`)).not.toContain('<frame>');
|
||||
t.expect(sanitizeHtml(defaultDoc, `<frame>evil!</frame>`)).not.toContain('<frame>');
|
||||
});
|
||||
});
|
||||
t.describe('should strip dangerous attributes', () => {
|
||||
@ -92,14 +98,14 @@ export function main() {
|
||||
|
||||
for (const attr of dangerousAttrs) {
|
||||
t.it(`${attr}`, () => {
|
||||
t.expect(sanitizeHtml(`<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, `<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (browserDetection.isWebkit) {
|
||||
t.it('should prevent mXSS attacks', function() {
|
||||
t.expect(sanitizeHtml('<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||
.toEqual('<a href="unsafe:javascript:alert(1)">CLICKME</a>');
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user