refactor(core): move files to the right place
- render/xhr_* -> compiler/xhr_* - render/event_config -> linker/event_config - render/dom/schema -> compiler/schema - render/dom/compiler/* -> compiler/* - render/dom/view/shared_styles_host -> render/dom/shared_styles_host - services/url_resolver -> compiler/url_resolver - services/app_root_urlo -> compiler/app_root_url
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
import {AppRootUrl} from "angular2/src/core/services/app_root_url";
|
||||
import {DOM} from "angular2/src/core/dom/dom_adapter";
|
||||
import {Injectable} from "angular2/src/core/di";
|
||||
|
||||
/**
|
||||
* Extension of {@link AppRootUrl} that uses a DOM anchor tag to set the root url to
|
||||
* the current page's url.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AnchorBasedAppRootUrl extends AppRootUrl {
|
||||
constructor() {
|
||||
super("");
|
||||
// compute the root url to pass to AppRootUrl
|
||||
var rootUrl: string;
|
||||
var a = DOM.createElement('a');
|
||||
DOM.resolveAndSetHref(a, './', null);
|
||||
rootUrl = DOM.getHref(a);
|
||||
|
||||
this.value = rootUrl;
|
||||
}
|
||||
}
|
25
modules/angular2/src/core/compiler/app_root_url.ts
Normal file
25
modules/angular2/src/core/compiler/app_root_url.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {isBlank} from 'angular2/src/core/facade/lang';
|
||||
|
||||
/**
|
||||
* Specifies app root url for the application.
|
||||
*
|
||||
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
|
||||
*
|
||||
* This interface can be overridden by the application developer to create custom behavior.
|
||||
*
|
||||
* See {@link Compiler}
|
||||
*/
|
||||
@Injectable()
|
||||
export class AppRootUrl {
|
||||
private _value: string;
|
||||
|
||||
constructor(value: string) { this._value = value; }
|
||||
|
||||
/**
|
||||
* Returns the base URL of the currently running application.
|
||||
*/
|
||||
get value() { return this._value; }
|
||||
|
||||
set value(value: string) { this._value = value; }
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
|
||||
import {ElementSchemaRegistry} from './element_schema_registry';
|
||||
|
||||
@Injectable()
|
||||
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||
private _protoElements = new Map<string, Element>();
|
||||
|
||||
private _getProtoElement(tagName: string): Element {
|
||||
var element = this._protoElements.get(tagName);
|
||||
if (isBlank(element)) {
|
||||
element = DOM.createElement(tagName);
|
||||
this._protoElements.set(tagName, element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
hasProperty(tagName: string, propName: string): boolean {
|
||||
if (tagName.indexOf('-') !== -1) {
|
||||
// can't tell now as we don't know which properties a custom element will get
|
||||
// once it is instantiated
|
||||
return true;
|
||||
} else {
|
||||
var elm = this._getProtoElement(tagName);
|
||||
return DOM.hasProperty(elm, propName);
|
||||
}
|
||||
}
|
||||
|
||||
getMappedPropName(propName: string): string {
|
||||
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, propName);
|
||||
return isPresent(mappedPropName) ? mappedPropName : propName;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export class ElementSchemaRegistry {
|
||||
hasProperty(tagName: string, propName: string): boolean { return true; }
|
||||
getMappedPropName(propName: string): string { return propName; }
|
||||
}
|
387
modules/angular2/src/core/compiler/selector.ts
Normal file
387
modules/angular2/src/core/compiler/selector.ts
Normal file
@ -0,0 +1,387 @@
|
||||
import {Map, ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
RegExpWrapper,
|
||||
RegExpMatcherWrapper,
|
||||
StringWrapper
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
|
||||
const _EMPTY_ATTR_VALUE = '';
|
||||
|
||||
// TODO: Can't use `const` here as
|
||||
// in Dart this is not transpiled into `final` yet...
|
||||
var _SELECTOR_REGEXP = RegExpWrapper.create(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)'); // ","
|
||||
|
||||
/**
|
||||
* A css selector contains an element name,
|
||||
* css classes and attribute/value pairs with the purpose
|
||||
* of selecting subsets out of them.
|
||||
*/
|
||||
export class CssSelector {
|
||||
element: string = null;
|
||||
classNames: string[] = [];
|
||||
attrs: string[] = [];
|
||||
notSelectors: CssSelector[] = [];
|
||||
|
||||
static parse(selector: string): CssSelector[] {
|
||||
var results: CssSelector[] = [];
|
||||
var _addResult = (res: CssSelector[], cssSel) => {
|
||||
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
||||
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
||||
cssSel.element = "*";
|
||||
}
|
||||
res.push(cssSel);
|
||||
};
|
||||
var cssSelector = new CssSelector();
|
||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||
var match;
|
||||
var current = cssSelector;
|
||||
var inNot = false;
|
||||
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||
if (isPresent(match[1])) {
|
||||
if (inNot) {
|
||||
throw new BaseException('Nesting :not is not allowed in a selector');
|
||||
}
|
||||
inNot = true;
|
||||
current = new CssSelector();
|
||||
cssSelector.notSelectors.push(current);
|
||||
}
|
||||
if (isPresent(match[2])) {
|
||||
current.setElement(match[2]);
|
||||
}
|
||||
if (isPresent(match[3])) {
|
||||
current.addClassName(match[3]);
|
||||
}
|
||||
if (isPresent(match[4])) {
|
||||
current.addAttribute(match[4], match[5]);
|
||||
}
|
||||
if (isPresent(match[6])) {
|
||||
inNot = false;
|
||||
current = cssSelector;
|
||||
}
|
||||
if (isPresent(match[7])) {
|
||||
if (inNot) {
|
||||
throw new BaseException('Multiple selectors in :not are not supported');
|
||||
}
|
||||
_addResult(results, cssSelector);
|
||||
cssSelector = current = new CssSelector();
|
||||
}
|
||||
}
|
||||
_addResult(results, cssSelector);
|
||||
return results;
|
||||
}
|
||||
|
||||
isElementSelector(): boolean {
|
||||
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
|
||||
ListWrapper.isEmpty(this.attrs) && this.notSelectors.length === 0;
|
||||
}
|
||||
|
||||
setElement(element: string = null) {
|
||||
if (isPresent(element)) {
|
||||
element = element.toLowerCase();
|
||||
}
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/** Gets a template string for an element that matches the selector. */
|
||||
getMatchingElementTemplate(): string {
|
||||
let tagName = isPresent(this.element) ? this.element : 'div';
|
||||
let classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : '';
|
||||
|
||||
let attrs = '';
|
||||
for (let i = 0; i < this.attrs.length; i += 2) {
|
||||
let attrName = this.attrs[i];
|
||||
let attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : '';
|
||||
attrs += ` ${attrName}${attrValue}`;
|
||||
}
|
||||
|
||||
return `<${tagName}${classAttr}${attrs}></${tagName}>`;
|
||||
}
|
||||
|
||||
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
|
||||
this.attrs.push(name.toLowerCase());
|
||||
if (isPresent(value)) {
|
||||
value = value.toLowerCase();
|
||||
} else {
|
||||
value = _EMPTY_ATTR_VALUE;
|
||||
}
|
||||
this.attrs.push(value);
|
||||
}
|
||||
|
||||
addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
|
||||
|
||||
toString(): string {
|
||||
var res = '';
|
||||
if (isPresent(this.element)) {
|
||||
res += this.element;
|
||||
}
|
||||
if (isPresent(this.classNames)) {
|
||||
for (var i = 0; i < this.classNames.length; i++) {
|
||||
res += '.' + this.classNames[i];
|
||||
}
|
||||
}
|
||||
if (isPresent(this.attrs)) {
|
||||
for (var i = 0; i < this.attrs.length;) {
|
||||
var attrName = this.attrs[i++];
|
||||
var attrValue = this.attrs[i++];
|
||||
res += '[' + attrName;
|
||||
if (attrValue.length > 0) {
|
||||
res += '=' + attrValue;
|
||||
}
|
||||
res += ']';
|
||||
}
|
||||
}
|
||||
ListWrapper.forEach(this.notSelectors,
|
||||
(notSelector) => { res += ":not(" + notSelector.toString() + ")"; });
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of CssSelectors and allows to calculate which ones
|
||||
* are contained in a given CssSelector.
|
||||
*/
|
||||
export class SelectorMatcher {
|
||||
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
||||
var notMatcher = new SelectorMatcher();
|
||||
notMatcher.addSelectables(notSelectors, null);
|
||||
return notMatcher;
|
||||
}
|
||||
|
||||
private _elementMap = new Map<string, SelectorContext[]>();
|
||||
private _elementPartialMap = new Map<string, SelectorMatcher>();
|
||||
private _classMap = new Map<string, SelectorContext[]>();
|
||||
private _classPartialMap = new Map<string, SelectorMatcher>();
|
||||
private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
|
||||
private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
|
||||
private _listContexts: SelectorListContext[] = [];
|
||||
|
||||
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
||||
var listContext = null;
|
||||
if (cssSelectors.length > 1) {
|
||||
listContext = new SelectorListContext(cssSelectors);
|
||||
this._listContexts.push(listContext);
|
||||
}
|
||||
for (var i = 0; i < cssSelectors.length; i++) {
|
||||
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object that can be found later on by calling `match`.
|
||||
* @param cssSelector A css selector
|
||||
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
|
||||
*/
|
||||
private _addSelectable(cssSelector: CssSelector, callbackCtxt: any,
|
||||
listContext: SelectorListContext) {
|
||||
var matcher = this;
|
||||
var element = cssSelector.element;
|
||||
var classNames = cssSelector.classNames;
|
||||
var attrs = cssSelector.attrs;
|
||||
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
||||
|
||||
if (isPresent(element)) {
|
||||
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
||||
if (isTerminal) {
|
||||
this._addTerminal(matcher._elementMap, element, selectable);
|
||||
} else {
|
||||
matcher = this._addPartial(matcher._elementPartialMap, element);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(classNames)) {
|
||||
for (var index = 0; index < classNames.length; index++) {
|
||||
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
|
||||
var className = classNames[index];
|
||||
if (isTerminal) {
|
||||
this._addTerminal(matcher._classMap, className, selectable);
|
||||
} else {
|
||||
matcher = this._addPartial(matcher._classPartialMap, className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(attrs)) {
|
||||
for (var index = 0; index < attrs.length;) {
|
||||
var isTerminal = index === attrs.length - 2;
|
||||
var attrName = attrs[index++];
|
||||
var attrValue = attrs[index++];
|
||||
if (isTerminal) {
|
||||
var terminalMap = matcher._attrValueMap;
|
||||
var terminalValuesMap = terminalMap.get(attrName);
|
||||
if (isBlank(terminalValuesMap)) {
|
||||
terminalValuesMap = new Map<string, SelectorContext[]>();
|
||||
terminalMap.set(attrName, terminalValuesMap);
|
||||
}
|
||||
this._addTerminal(terminalValuesMap, attrValue, selectable);
|
||||
} else {
|
||||
var parttialMap = matcher._attrValuePartialMap;
|
||||
var partialValuesMap = parttialMap.get(attrName);
|
||||
if (isBlank(partialValuesMap)) {
|
||||
partialValuesMap = new Map<string, SelectorMatcher>();
|
||||
parttialMap.set(attrName, partialValuesMap);
|
||||
}
|
||||
matcher = this._addPartial(partialValuesMap, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _addTerminal(map: Map<string, SelectorContext[]>, name: string,
|
||||
selectable: SelectorContext) {
|
||||
var terminalList = map.get(name);
|
||||
if (isBlank(terminalList)) {
|
||||
terminalList = [];
|
||||
map.set(name, terminalList);
|
||||
}
|
||||
terminalList.push(selectable);
|
||||
}
|
||||
|
||||
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
|
||||
var matcher = map.get(name);
|
||||
if (isBlank(matcher)) {
|
||||
matcher = new SelectorMatcher();
|
||||
map.set(name, matcher);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the objects that have been added via `addSelectable`
|
||||
* whose css selector is contained in the given css selector.
|
||||
* @param cssSelector A css selector
|
||||
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
|
||||
* @return boolean true if a match was found
|
||||
*/
|
||||
match(cssSelector: CssSelector, matchedCallback: (CssSelector, any) => void): boolean {
|
||||
var result = false;
|
||||
var element = cssSelector.element;
|
||||
var classNames = cssSelector.classNames;
|
||||
var attrs = cssSelector.attrs;
|
||||
|
||||
for (var i = 0; i < this._listContexts.length; i++) {
|
||||
this._listContexts[i].alreadyMatched = false;
|
||||
}
|
||||
|
||||
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
|
||||
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
|
||||
result;
|
||||
|
||||
if (isPresent(classNames)) {
|
||||
for (var index = 0; index < classNames.length; index++) {
|
||||
var className = classNames[index];
|
||||
result =
|
||||
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
||||
result =
|
||||
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
|
||||
result;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(attrs)) {
|
||||
for (var index = 0; index < attrs.length;) {
|
||||
var attrName = attrs[index++];
|
||||
var attrValue = attrs[index++];
|
||||
|
||||
var terminalValuesMap = this._attrValueMap.get(attrName);
|
||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
||||
result = this._matchTerminal(terminalValuesMap, _EMPTY_ATTR_VALUE, cssSelector,
|
||||
matchedCallback) ||
|
||||
result;
|
||||
}
|
||||
result = this._matchTerminal(terminalValuesMap, attrValue, cssSelector, matchedCallback) ||
|
||||
result;
|
||||
|
||||
var partialValuesMap = this._attrValuePartialMap.get(attrName);
|
||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
||||
result = this._matchPartial(partialValuesMap, _EMPTY_ATTR_VALUE, cssSelector,
|
||||
matchedCallback) ||
|
||||
result;
|
||||
}
|
||||
result =
|
||||
this._matchPartial(partialValuesMap, attrValue, cssSelector, matchedCallback) || result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_matchTerminal(map: Map<string, SelectorContext[]>, name, cssSelector: CssSelector,
|
||||
matchedCallback: (CssSelector, any) => void): boolean {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectables = map.get(name);
|
||||
var starSelectables = map.get("*");
|
||||
if (isPresent(starSelectables)) {
|
||||
selectables = selectables.concat(starSelectables);
|
||||
}
|
||||
if (isBlank(selectables)) {
|
||||
return false;
|
||||
}
|
||||
var selectable;
|
||||
var result = false;
|
||||
for (var index = 0; index < selectables.length; index++) {
|
||||
selectable = selectables[index];
|
||||
result = selectable.finalize(cssSelector, matchedCallback) || result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_matchPartial(map: Map<string, SelectorMatcher>, name, cssSelector: CssSelector,
|
||||
matchedCallback /*: (CssSelector, any) => void*/): boolean {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return false;
|
||||
}
|
||||
var nestedSelector = map.get(name);
|
||||
if (isBlank(nestedSelector)) {
|
||||
return false;
|
||||
}
|
||||
// TODO(perf): get rid of recursion and measure again
|
||||
// TODO(perf): don't pass the whole selector into the recursion,
|
||||
// but only the not processed parts
|
||||
return nestedSelector.match(cssSelector, matchedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SelectorListContext {
|
||||
alreadyMatched: boolean = false;
|
||||
|
||||
constructor(public selectors: CssSelector[]) {}
|
||||
}
|
||||
|
||||
// Store context to pass back selector and context when a selector is matched
|
||||
export class SelectorContext {
|
||||
notSelectors: CssSelector[];
|
||||
|
||||
constructor(public selector: CssSelector, public cbContext: any,
|
||||
public listContext: SelectorListContext) {
|
||||
this.notSelectors = selector.notSelectors;
|
||||
}
|
||||
|
||||
finalize(cssSelector: CssSelector, callback: (CssSelector, any) => void): boolean {
|
||||
var result = true;
|
||||
if (this.notSelectors.length > 0 &&
|
||||
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
||||
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
||||
result = !notMatcher.match(cssSelector, null);
|
||||
}
|
||||
if (result && isPresent(callback) &&
|
||||
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
||||
if (isPresent(this.listContext)) {
|
||||
this.listContext.alreadyMatched = true;
|
||||
}
|
||||
callback(this.selector, this.cbContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
529
modules/angular2/src/core/compiler/shadow_css.ts
Normal file
529
modules/angular2/src/core/compiler/shadow_css.ts
Normal file
@ -0,0 +1,529 @@
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {
|
||||
StringWrapper,
|
||||
RegExp,
|
||||
RegExpWrapper,
|
||||
RegExpMatcherWrapper,
|
||||
isPresent,
|
||||
isBlank
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
|
||||
/**
|
||||
* This file is a port of shadowCSS from webcomponents.js to TypeScript.
|
||||
*
|
||||
* Please make sure to keep to edits in sync with the source file.
|
||||
*
|
||||
* Source:
|
||||
* https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
|
||||
*
|
||||
* The original file level comment is reproduced below
|
||||
*/
|
||||
|
||||
/*
|
||||
This is a limited shim for ShadowDOM css styling.
|
||||
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
|
||||
|
||||
The intention here is to support only the styling features which can be
|
||||
relatively simply implemented. The goal is to allow users to avoid the
|
||||
most obvious pitfalls and do so without compromising performance significantly.
|
||||
For ShadowDOM styling that's not covered here, a set of best practices
|
||||
can be provided that should allow users to accomplish more complex styling.
|
||||
|
||||
The following is a list of specific ShadowDOM styling features and a brief
|
||||
discussion of the approach used to shim.
|
||||
|
||||
Shimmed features:
|
||||
|
||||
* :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
|
||||
element using the :host rule. To shim this feature, the :host styles are
|
||||
reformatted and prefixed with a given scope name and promoted to a
|
||||
document level stylesheet.
|
||||
For example, given a scope name of .foo, a rule like this:
|
||||
|
||||
:host {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
.foo {
|
||||
background: red;
|
||||
}
|
||||
|
||||
* encapsultion: Styles defined within ShadowDOM, apply only to
|
||||
dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
|
||||
this feature.
|
||||
|
||||
By default, rules are prefixed with the host element tag name
|
||||
as a descendant selector. This ensures styling does not leak out of the 'top'
|
||||
of the element's ShadowDOM. For example,
|
||||
|
||||
div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
x-foo div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
|
||||
Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then
|
||||
selectors are scoped by adding an attribute selector suffix to each
|
||||
simple selector that contains the host element tag name. Each element
|
||||
in the element's ShadowDOM template is also given the scope attribute.
|
||||
Thus, these rules match only elements that have the scope attribute.
|
||||
For example, given a scope name of x-foo, a rule like this:
|
||||
|
||||
div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
div[x-foo] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
Note that elements that are dynamically added to a scope must have the scope
|
||||
selector added to them manually.
|
||||
|
||||
* upper/lower bound encapsulation: Styles which are defined outside a
|
||||
shadowRoot should not cross the ShadowDOM boundary and should not apply
|
||||
inside a shadowRoot.
|
||||
|
||||
This styling behavior is not emulated. Some possible ways to do this that
|
||||
were rejected due to complexity and/or performance concerns include: (1) reset
|
||||
every possible property for every possible selector for a given scope name;
|
||||
(2) re-implement css in javascript.
|
||||
|
||||
As an alternative, users should make sure to use selectors
|
||||
specific to the scope in which they are working.
|
||||
|
||||
* ::distributed: This behavior is not emulated. It's often not necessary
|
||||
to style the contents of a specific insertion point and instead, descendants
|
||||
of the host element can be styled selectively. Users can also create an
|
||||
extra node around an insertion point and style that node's contents
|
||||
via descendent selectors. For example, with a shadowRoot like this:
|
||||
|
||||
<style>
|
||||
::content(div) {
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
<content></content>
|
||||
|
||||
could become:
|
||||
|
||||
<style>
|
||||
/ *@polyfill .content-container div * /
|
||||
::content(div) {
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
<div class="content-container">
|
||||
<content></content>
|
||||
</div>
|
||||
|
||||
Note the use of @polyfill in the comment above a ShadowDOM specific style
|
||||
declaration. This is a directive to the styling shim to use the selector
|
||||
in comments in lieu of the next selector when running under polyfill.
|
||||
*/
|
||||
|
||||
export class ShadowCss {
|
||||
strictStyling: boolean = true;
|
||||
|
||||
constructor() {}
|
||||
|
||||
/*
|
||||
* Shim a style element with the given selector. Returns cssText that can
|
||||
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
|
||||
*/
|
||||
shimStyle(style: string, selector: string, hostSelector: string = ''): string {
|
||||
var cssText = DOM.getText(style);
|
||||
return this.shimCssText(cssText, selector, hostSelector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Shim some cssText with the given selector. Returns cssText that can
|
||||
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
|
||||
*
|
||||
* When strictStyling is true:
|
||||
* - selector is the attribute added to all elements inside the host,
|
||||
* - hostSelector is the attribute added to the host itself.
|
||||
*/
|
||||
shimCssText(cssText: string, selector: string, hostSelector: string = ''): string {
|
||||
cssText = this._insertDirectives(cssText);
|
||||
return this._scopeCssText(cssText, selector, hostSelector);
|
||||
}
|
||||
|
||||
_insertDirectives(cssText: string): string {
|
||||
cssText = this._insertPolyfillDirectivesInCssText(cssText);
|
||||
return this._insertPolyfillRulesInCssText(cssText);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process styles to convert native ShadowDOM rules that will trip
|
||||
* up the css parser; we rely on decorating the stylesheet with inert rules.
|
||||
*
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* polyfill-next-selector { content: ':host menu-item'; }
|
||||
* ::content menu-item {
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* scopeName menu-item {
|
||||
*
|
||||
**/
|
||||
_insertPolyfillDirectivesInCssText(cssText: string): string {
|
||||
// Difference with webcomponents.js: does not handle comments
|
||||
return StringWrapper.replaceAllMapped(cssText, _cssContentNextSelectorRe,
|
||||
function(m) { return m[1] + '{'; });
|
||||
}
|
||||
|
||||
/*
|
||||
* Process styles to add rules which will only apply under the polyfill
|
||||
*
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* polyfill-rule {
|
||||
* content: ':host menu-item';
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* scopeName menu-item {...}
|
||||
*
|
||||
**/
|
||||
_insertPolyfillRulesInCssText(cssText: string): string {
|
||||
// Difference with webcomponents.js: does not handle comments
|
||||
return StringWrapper.replaceAllMapped(cssText, _cssContentRuleRe, function(m) {
|
||||
var rule = m[0];
|
||||
rule = StringWrapper.replace(rule, m[1], '');
|
||||
rule = StringWrapper.replace(rule, m[2], '');
|
||||
return m[3] + rule;
|
||||
});
|
||||
}
|
||||
|
||||
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
|
||||
*
|
||||
* .foo {... }
|
||||
*
|
||||
* and converts this to
|
||||
*
|
||||
* scopeName .foo { ... }
|
||||
*/
|
||||
_scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
|
||||
var unscoped = this._extractUnscopedRulesFromCssText(cssText);
|
||||
cssText = this._insertPolyfillHostInCssText(cssText);
|
||||
cssText = this._convertColonHost(cssText);
|
||||
cssText = this._convertColonHostContext(cssText);
|
||||
cssText = this._convertShadowDOMSelectors(cssText);
|
||||
if (isPresent(scopeSelector)) {
|
||||
_withCssRules(cssText,
|
||||
(rules) => { cssText = this._scopeRules(rules, scopeSelector, hostSelector); });
|
||||
}
|
||||
cssText = cssText + '\n' + unscoped;
|
||||
return cssText.trim();
|
||||
}
|
||||
|
||||
/*
|
||||
* Process styles to add rules which will only apply under the polyfill
|
||||
* and do not process via CSSOM. (CSSOM is destructive to rules on rare
|
||||
* occasions, e.g. -webkit-calc on Safari.)
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* @polyfill-unscoped-rule {
|
||||
* content: 'menu-item';
|
||||
* ... }
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* menu-item {...}
|
||||
*
|
||||
**/
|
||||
_extractUnscopedRulesFromCssText(cssText: string): string {
|
||||
// Difference with webcomponents.js: does not handle comments
|
||||
var r = '', m;
|
||||
var matcher = RegExpWrapper.matcher(_cssContentUnscopedRuleRe, cssText);
|
||||
while (isPresent(m = RegExpMatcherWrapper.next(matcher))) {
|
||||
var rule = m[0];
|
||||
rule = StringWrapper.replace(rule, m[2], '');
|
||||
rule = StringWrapper.replace(rule, m[1], m[3]);
|
||||
r += rule + '\n\n';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* convert a rule like :host(.foo) > .bar { }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo > .bar
|
||||
*/
|
||||
_convertColonHost(cssText: string): string {
|
||||
return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer);
|
||||
}
|
||||
|
||||
/*
|
||||
* convert a rule like :host-context(.foo) > .bar { }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo > .bar, .foo scopeName > .bar { }
|
||||
*
|
||||
* and
|
||||
*
|
||||
* :host-context(.foo:host) .bar { ... }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo .bar { ... }
|
||||
*/
|
||||
_convertColonHostContext(cssText: string): string {
|
||||
return this._convertColonRule(cssText, _cssColonHostContextRe,
|
||||
this._colonHostContextPartReplacer);
|
||||
}
|
||||
|
||||
_convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string {
|
||||
// p1 = :host, p2 = contents of (), p3 rest of rule
|
||||
return StringWrapper.replaceAllMapped(cssText, regExp, function(m) {
|
||||
if (isPresent(m[2])) {
|
||||
var parts = m[2].split(','), r = [];
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
if (isBlank(p)) break;
|
||||
p = p.trim();
|
||||
r.push(partReplacer(_polyfillHostNoCombinator, p, m[3]));
|
||||
}
|
||||
return r.join(',');
|
||||
} else {
|
||||
return _polyfillHostNoCombinator + m[3];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_colonHostContextPartReplacer(host: string, part: string, suffix: string): string {
|
||||
if (StringWrapper.contains(part, _polyfillHost)) {
|
||||
return this._colonHostPartReplacer(host, part, suffix);
|
||||
} else {
|
||||
return host + part + suffix + ', ' + part + ' ' + host + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
_colonHostPartReplacer(host: string, part: string, suffix: string): string {
|
||||
return host + StringWrapper.replace(part, _polyfillHost, '') + suffix;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert combinators like ::shadow and pseudo-elements like ::content
|
||||
* by replacing with space.
|
||||
*/
|
||||
_convertShadowDOMSelectors(cssText: string): string {
|
||||
for (var i = 0; i < _shadowDOMSelectorsRe.length; i++) {
|
||||
cssText = StringWrapper.replaceAll(cssText, _shadowDOMSelectorsRe[i], ' ');
|
||||
}
|
||||
return cssText;
|
||||
}
|
||||
|
||||
// change a selector like 'div' to 'name div'
|
||||
_scopeRules(cssRules, scopeSelector: string, hostSelector: string): string {
|
||||
var cssText = '';
|
||||
if (isPresent(cssRules)) {
|
||||
for (var i = 0; i < cssRules.length; i++) {
|
||||
var rule = cssRules[i];
|
||||
if (DOM.isStyleRule(rule) || DOM.isPageRule(rule)) {
|
||||
cssText += this._scopeSelector(rule.selectorText, scopeSelector, hostSelector,
|
||||
this.strictStyling) +
|
||||
' {\n';
|
||||
cssText += this._propertiesFromRule(rule) + '\n}\n\n';
|
||||
} else if (DOM.isMediaRule(rule)) {
|
||||
cssText += '@media ' + rule.media.mediaText + ' {\n';
|
||||
cssText += this._scopeRules(rule.cssRules, scopeSelector, hostSelector);
|
||||
cssText += '\n}\n\n';
|
||||
} else {
|
||||
// KEYFRAMES_RULE in IE throws when we query cssText
|
||||
// when it contains a -webkit- property.
|
||||
// if this happens, we fallback to constructing the rule
|
||||
// from the CSSRuleSet
|
||||
// https://connect.microsoft.com/IE/feedbackdetail/view/955703/accessing-csstext-of-a-keyframe-rule-that-contains-a-webkit-property-via-cssom-generates-exception
|
||||
try {
|
||||
if (isPresent(rule.cssText)) {
|
||||
cssText += rule.cssText + '\n\n';
|
||||
}
|
||||
} catch (x) {
|
||||
if (DOM.isKeyframesRule(rule) && isPresent(rule.cssRules)) {
|
||||
cssText += this._ieSafeCssTextFromKeyFrameRule(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cssText;
|
||||
}
|
||||
|
||||
_ieSafeCssTextFromKeyFrameRule(rule): string {
|
||||
var cssText = '@keyframes ' + rule.name + ' {';
|
||||
for (var i = 0; i < rule.cssRules.length; i++) {
|
||||
var r = rule.cssRules[i];
|
||||
cssText += ' ' + r.keyText + ' {' + r.style.cssText + '}';
|
||||
}
|
||||
cssText += ' }';
|
||||
return cssText;
|
||||
}
|
||||
|
||||
_scopeSelector(selector: string, scopeSelector: string, hostSelector: string,
|
||||
strict: boolean): string {
|
||||
var r = [], parts = selector.split(',');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
p = p.trim();
|
||||
if (this._selectorNeedsScoping(p, scopeSelector)) {
|
||||
p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ?
|
||||
this._applyStrictSelectorScope(p, scopeSelector) :
|
||||
this._applySelectorScope(p, scopeSelector, hostSelector);
|
||||
}
|
||||
r.push(p);
|
||||
}
|
||||
return r.join(', ');
|
||||
}
|
||||
|
||||
_selectorNeedsScoping(selector: string, scopeSelector: string): boolean {
|
||||
var re = this._makeScopeMatcher(scopeSelector);
|
||||
return !isPresent(RegExpWrapper.firstMatch(re, selector));
|
||||
}
|
||||
|
||||
_makeScopeMatcher(scopeSelector: string): RegExp {
|
||||
var lre = /\[/g;
|
||||
var rre = /\]/g;
|
||||
scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\[');
|
||||
scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]');
|
||||
return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
|
||||
}
|
||||
|
||||
_applySelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
|
||||
// Difference from webcomponentsjs: scopeSelector could not be an array
|
||||
return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector);
|
||||
}
|
||||
|
||||
// scope via name and [is=name]
|
||||
_applySimpleSelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
|
||||
if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) {
|
||||
var replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
|
||||
selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy);
|
||||
return StringWrapper.replaceAll(selector, _polyfillHostRe, replaceBy + ' ');
|
||||
} else {
|
||||
return scopeSelector + ' ' + selector;
|
||||
}
|
||||
}
|
||||
|
||||
// return a selector with [name] suffix on each simple selector
|
||||
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
|
||||
_applyStrictSelectorScope(selector: string, scopeSelector: string): string {
|
||||
var isRe = /\[is=([^\]]*)\]/g;
|
||||
scopeSelector = StringWrapper.replaceAllMapped(scopeSelector, isRe, (m) => m[1]);
|
||||
var splits = [' ', '>', '+', '~'], scoped = selector, attrName = '[' + scopeSelector + ']';
|
||||
for (var i = 0; i < splits.length; i++) {
|
||||
var sep = splits[i];
|
||||
var parts = scoped.split(sep);
|
||||
scoped = ListWrapper.map(parts, function(p) {
|
||||
// remove :host since it should be unnecessary
|
||||
var t = StringWrapper.replaceAll(p.trim(), _polyfillHostRe, '');
|
||||
if (t.length > 0 && !ListWrapper.contains(splits, t) &&
|
||||
!StringWrapper.contains(t, attrName)) {
|
||||
var re = /([^:]*)(:*)(.*)/g;
|
||||
var m = RegExpWrapper.firstMatch(re, t);
|
||||
if (isPresent(m)) {
|
||||
p = m[1] + attrName + m[2] + m[3];
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}).join(sep);
|
||||
}
|
||||
return scoped;
|
||||
}
|
||||
|
||||
_insertPolyfillHostInCssText(selector: string): string {
|
||||
selector = StringWrapper.replaceAll(selector, _colonHostContextRe, _polyfillHostContext);
|
||||
selector = StringWrapper.replaceAll(selector, _colonHostRe, _polyfillHost);
|
||||
return selector;
|
||||
}
|
||||
|
||||
_propertiesFromRule(rule): string {
|
||||
var cssText = rule.style.cssText;
|
||||
// TODO(sorvell): Safari cssom incorrectly removes quotes from the content
|
||||
// property. (https://bugs.webkit.org/show_bug.cgi?id=118045)
|
||||
// don't replace attr rules
|
||||
var attrRe = /['"]+|attr/g;
|
||||
if (rule.style.content.length > 0 &&
|
||||
!isPresent(RegExpWrapper.firstMatch(attrRe, rule.style.content))) {
|
||||
var contentRe = /content:[^;]*;/g;
|
||||
cssText =
|
||||
StringWrapper.replaceAll(cssText, contentRe, 'content: \'' + rule.style.content + '\';');
|
||||
}
|
||||
// TODO(sorvell): we can workaround this issue here, but we need a list
|
||||
// of troublesome properties to fix https://github.com/Polymer/platform/issues/53
|
||||
//
|
||||
// inherit rules can be omitted from cssText
|
||||
// TODO(sorvell): remove when Blink bug is fixed:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=358273
|
||||
// var style = rule.style;
|
||||
// for (var i = 0; i < style.length; i++) {
|
||||
// var name = style.item(i);
|
||||
// var value = style.getPropertyValue(name);
|
||||
// if (value == 'initial') {
|
||||
// cssText += name + ': initial; ';
|
||||
// }
|
||||
//}
|
||||
return cssText;
|
||||
}
|
||||
}
|
||||
var _cssContentNextSelectorRe =
|
||||
/polyfill-next-selector[^}]*content:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim;
|
||||
var _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
|
||||
var _cssContentUnscopedRuleRe =
|
||||
/(polyfill-unscoped-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
|
||||
var _polyfillHost = '-shadowcsshost';
|
||||
// note: :host-context pre-processed to -shadowcsshostcontext.
|
||||
var _polyfillHostContext = '-shadowcsscontext';
|
||||
var _parenSuffix = ')(?:\\((' +
|
||||
'(?:\\([^)(]*\\)|[^)(]*)+?' +
|
||||
')\\))?([^,{]*)';
|
||||
var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, 'im');
|
||||
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
|
||||
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
|
||||
var _shadowDOMSelectorsRe = [
|
||||
/>>>/g,
|
||||
/::shadow/g,
|
||||
/::content/g,
|
||||
// Deprecated selectors
|
||||
// TODO(vicb): see https://github.com/angular/clang-format/issues/16
|
||||
// clang-format off
|
||||
/\/deep\//g, // former >>>
|
||||
/\/shadow-deep\//g, // former /deep/
|
||||
/\/shadow\//g, // former ::shadow
|
||||
// clanf-format on
|
||||
];
|
||||
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
|
||||
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
|
||||
var _colonHostRe = /:host/gim;
|
||||
var _colonHostContextRe = /:host-context/gim;
|
||||
|
||||
function _cssToRules(cssText: string) {
|
||||
return DOM.cssToRules(cssText);
|
||||
}
|
||||
|
||||
function _withCssRules(cssText: string, callback: Function) {
|
||||
// Difference from webcomponentjs: remove the workaround for an old bug in Chrome
|
||||
if (isBlank(callback)) return;
|
||||
var rules = _cssToRules(cssText);
|
||||
callback(rules);
|
||||
}
|
41
modules/angular2/src/core/compiler/url_resolver.dart
Normal file
41
modules/angular2/src/core/compiler/url_resolver.dart
Normal file
@ -0,0 +1,41 @@
|
||||
library angular2.src.services.url_resolver;
|
||||
|
||||
import 'package:angular2/src/core/di.dart' show Injectable;
|
||||
|
||||
@Injectable()
|
||||
class UrlResolver {
|
||||
/// This will be the location where 'package:' Urls will resolve. Default is
|
||||
/// '/packages'
|
||||
final String _packagePrefix;
|
||||
|
||||
const UrlResolver() : _packagePrefix = '/packages';
|
||||
|
||||
/// Creates a UrlResolver that will resolve 'package:' Urls to a different
|
||||
/// prefixed location.
|
||||
const UrlResolver.withUrlPrefix(this._packagePrefix);
|
||||
|
||||
/**
|
||||
* Resolves the `url` given the `baseUrl`:
|
||||
* - when the `url` is null, the `baseUrl` is returned,
|
||||
* - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
|
||||
* `baseUrl` and `url`,
|
||||
* - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
|
||||
* returned as is (ignoring the `baseUrl`)
|
||||
*
|
||||
* @param {string} baseUrl
|
||||
* @param {string} url
|
||||
* @returns {string} the resolved URL
|
||||
*/
|
||||
String resolve(String baseUrl, String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
if (uri.scheme == 'package') {
|
||||
return '$_packagePrefix/${uri.path}';
|
||||
}
|
||||
|
||||
if (uri.isAbsolute) return uri.toString();
|
||||
|
||||
Uri baseUri = Uri.parse(baseUrl);
|
||||
return baseUri.resolveUri(uri).toString();
|
||||
}
|
||||
}
|
295
modules/angular2/src/core/compiler/url_resolver.ts
Normal file
295
modules/angular2/src/core/compiler/url_resolver.ts
Normal file
@ -0,0 +1,295 @@
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {isPresent, isBlank, RegExpWrapper, normalizeBlank} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
|
||||
/**
|
||||
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
|
||||
*
|
||||
* This interface can be overridden by the application developer to create custom behavior.
|
||||
*
|
||||
* See {@link Compiler}
|
||||
*/
|
||||
@Injectable()
|
||||
export class UrlResolver {
|
||||
/**
|
||||
* Resolves the `url` given the `baseUrl`:
|
||||
* - when the `url` is null, the `baseUrl` is returned,
|
||||
* - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
|
||||
* `baseUrl` and `url`,
|
||||
* - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
|
||||
* returned as is (ignoring the `baseUrl`)
|
||||
*
|
||||
* @param {string} baseUrl
|
||||
* @param {string} url
|
||||
* @returns {string} the resolved URL
|
||||
*/
|
||||
resolve(baseUrl: string, url: string): string { return _resolveUrl(baseUrl, url); }
|
||||
}
|
||||
|
||||
// The code below is adapted from Traceur:
|
||||
// https://github.com/google/traceur-compiler/blob/9511c1dafa972bf0de1202a8a863bad02f0f95a8/src/runtime/url.js
|
||||
|
||||
/**
|
||||
* Builds a URI string from already-encoded parts.
|
||||
*
|
||||
* No encoding is performed. Any component may be omitted as either null or
|
||||
* undefined.
|
||||
*
|
||||
* @param {?string=} opt_scheme The scheme such as 'http'.
|
||||
* @param {?string=} opt_userInfo The user name before the '@'.
|
||||
* @param {?string=} opt_domain The domain such as 'www.google.com', already
|
||||
* URI-encoded.
|
||||
* @param {(string|null)=} opt_port The port number.
|
||||
* @param {?string=} opt_path The path, already URI-encoded. If it is not
|
||||
* empty, it must begin with a slash.
|
||||
* @param {?string=} opt_queryData The URI-encoded query data.
|
||||
* @param {?string=} opt_fragment The URI-encoded fragment identifier.
|
||||
* @return {string} The fully combined URI.
|
||||
*/
|
||||
function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,
|
||||
opt_port?: string, opt_path?: string, opt_queryData?: string,
|
||||
opt_fragment?: string): string {
|
||||
var out = [];
|
||||
|
||||
if (isPresent(opt_scheme)) {
|
||||
out.push(opt_scheme + ':');
|
||||
}
|
||||
|
||||
if (isPresent(opt_domain)) {
|
||||
out.push('//');
|
||||
|
||||
if (isPresent(opt_userInfo)) {
|
||||
out.push(opt_userInfo + '@');
|
||||
}
|
||||
|
||||
out.push(opt_domain);
|
||||
|
||||
if (isPresent(opt_port)) {
|
||||
out.push(':' + opt_port);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(opt_path)) {
|
||||
out.push(opt_path);
|
||||
}
|
||||
|
||||
if (isPresent(opt_queryData)) {
|
||||
out.push('?' + opt_queryData);
|
||||
}
|
||||
|
||||
if (isPresent(opt_fragment)) {
|
||||
out.push('#' + opt_fragment);
|
||||
}
|
||||
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular expression for breaking a URI into its component parts.
|
||||
*
|
||||
* {@link http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234} says
|
||||
* As the "first-match-wins" algorithm is identical to the "greedy"
|
||||
* disambiguation method used by POSIX regular expressions, it is natural and
|
||||
* commonplace to use a regular expression for parsing the potential five
|
||||
* components of a URI reference.
|
||||
*
|
||||
* The following line is the regular expression for breaking-down a
|
||||
* well-formed URI reference into its components.
|
||||
*
|
||||
* <pre>
|
||||
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
|
||||
* 12 3 4 5 6 7 8 9
|
||||
* </pre>
|
||||
*
|
||||
* The numbers in the second line above are only to assist readability; they
|
||||
* indicate the reference points for each subexpression (i.e., each paired
|
||||
* parenthesis). We refer to the value matched for subexpression <n> as $<n>.
|
||||
* For example, matching the above expression to
|
||||
* <pre>
|
||||
* http://www.ics.uci.edu/pub/ietf/uri/#Related
|
||||
* </pre>
|
||||
* results in the following subexpression matches:
|
||||
* <pre>
|
||||
* $1 = http:
|
||||
* $2 = http
|
||||
* $3 = //www.ics.uci.edu
|
||||
* $4 = www.ics.uci.edu
|
||||
* $5 = /pub/ietf/uri/
|
||||
* $6 = <undefined>
|
||||
* $7 = <undefined>
|
||||
* $8 = #Related
|
||||
* $9 = Related
|
||||
* </pre>
|
||||
* where <undefined> indicates that the component is not present, as is the
|
||||
* case for the query component in the above example. Therefore, we can
|
||||
* determine the value of the five components as
|
||||
* <pre>
|
||||
* scheme = $2
|
||||
* authority = $4
|
||||
* path = $5
|
||||
* query = $7
|
||||
* fragment = $9
|
||||
* </pre>
|
||||
*
|
||||
* The regular expression has been modified slightly to expose the
|
||||
* userInfo, domain, and port separately from the authority.
|
||||
* The modified version yields
|
||||
* <pre>
|
||||
* $1 = http scheme
|
||||
* $2 = <undefined> userInfo -\
|
||||
* $3 = www.ics.uci.edu domain | authority
|
||||
* $4 = <undefined> port -/
|
||||
* $5 = /pub/ietf/uri/ path
|
||||
* $6 = <undefined> query without ?
|
||||
* $7 = Related fragment without #
|
||||
* </pre>
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
var _splitRe =
|
||||
RegExpWrapper.create('^' +
|
||||
'(?:' +
|
||||
'([^:/?#.]+)' + // scheme - ignore special characters
|
||||
// used by other URL parts such as :,
|
||||
// ?, /, #, and .
|
||||
':)?' +
|
||||
'(?://' +
|
||||
'(?:([^/?#]*)@)?' + // userInfo
|
||||
'([\\w\\d\\-\\u0100-\\uffff.%]*)' + // domain - restrict to letters,
|
||||
// digits, dashes, dots, percent
|
||||
// escapes, and unicode characters.
|
||||
'(?::([0-9]+))?' + // port
|
||||
')?' +
|
||||
'([^?#]+)?' + // path
|
||||
'(?:\\?([^#]*))?' + // query
|
||||
'(?:#(.*))?' + // fragment
|
||||
'$');
|
||||
|
||||
/**
|
||||
* The index of each URI component in the return value of goog.uri.utils.split.
|
||||
* @enum {number}
|
||||
*/
|
||||
enum _ComponentIndex {
|
||||
Scheme = 1,
|
||||
UserInfo,
|
||||
Domain,
|
||||
Port,
|
||||
Path,
|
||||
QueryData,
|
||||
Fragment
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a URI into its component parts.
|
||||
*
|
||||
* Each component can be accessed via the component indices; for example:
|
||||
* <pre>
|
||||
* goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
|
||||
* </pre>
|
||||
*
|
||||
* @param {string} uri The URI string to examine.
|
||||
* @return {!Array.<string|undefined>} Each component still URI-encoded.
|
||||
* Each component that is present will contain the encoded value, whereas
|
||||
* components that are not present will be undefined or empty, depending
|
||||
* on the browser's regular expression implementation. Never null, since
|
||||
* arbitrary strings may still look like path names.
|
||||
*/
|
||||
function _split(uri: string): Array<string | any> {
|
||||
return RegExpWrapper.firstMatch(_splitRe, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes dot segments in given path component, as described in
|
||||
* RFC 3986, section 5.2.4.
|
||||
*
|
||||
* @param {string} path A non-empty path component.
|
||||
* @return {string} Path component with removed dot segments.
|
||||
*/
|
||||
function _removeDotSegments(path: string): string {
|
||||
if (path == '/') return '/';
|
||||
|
||||
var leadingSlash = path[0] == '/' ? '/' : '';
|
||||
var trailingSlash = path[path.length - 1] === '/' ? '/' : '';
|
||||
var segments = path.split('/');
|
||||
|
||||
var out = [];
|
||||
var up = 0;
|
||||
for (var pos = 0; pos < segments.length; pos++) {
|
||||
var segment = segments[pos];
|
||||
switch (segment) {
|
||||
case '':
|
||||
case '.':
|
||||
break;
|
||||
case '..':
|
||||
if (out.length > 0) {
|
||||
ListWrapper.removeAt(out, out.length - 1);
|
||||
} else {
|
||||
up++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
out.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (leadingSlash == '') {
|
||||
while (up-- > 0) {
|
||||
ListWrapper.insert(out, 0, '..');
|
||||
}
|
||||
|
||||
if (out.length === 0) out.push('.');
|
||||
}
|
||||
|
||||
return leadingSlash + out.join('/') + trailingSlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of the parts from split and canonicalizes the path part
|
||||
* and then joins all the parts.
|
||||
* @param {Array.<string?>} parts
|
||||
* @return {string}
|
||||
*/
|
||||
function _joinAndCanonicalizePath(parts: any[]): string {
|
||||
var path = parts[_ComponentIndex.Path];
|
||||
path = isBlank(path) ? '' : _removeDotSegments(path);
|
||||
parts[_ComponentIndex.Path] = path;
|
||||
|
||||
return _buildFromEncodedParts(parts[_ComponentIndex.Scheme], parts[_ComponentIndex.UserInfo],
|
||||
parts[_ComponentIndex.Domain], parts[_ComponentIndex.Port], path,
|
||||
parts[_ComponentIndex.QueryData], parts[_ComponentIndex.Fragment]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a URL.
|
||||
* @param {string} base The URL acting as the base URL.
|
||||
* @param {string} to The URL to resolve.
|
||||
* @return {string}
|
||||
*/
|
||||
function _resolveUrl(base: string, url: string): string {
|
||||
var parts = _split(encodeURI(url));
|
||||
var baseParts = _split(base);
|
||||
|
||||
if (isPresent(parts[_ComponentIndex.Scheme])) {
|
||||
return _joinAndCanonicalizePath(parts);
|
||||
} else {
|
||||
parts[_ComponentIndex.Scheme] = baseParts[_ComponentIndex.Scheme];
|
||||
}
|
||||
|
||||
for (var i = _ComponentIndex.Scheme; i <= _ComponentIndex.Port; i++) {
|
||||
if (isBlank(parts[i])) {
|
||||
parts[i] = baseParts[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (parts[_ComponentIndex.Path][0] == '/') {
|
||||
return _joinAndCanonicalizePath(parts);
|
||||
}
|
||||
|
||||
var path = baseParts[_ComponentIndex.Path];
|
||||
if (isBlank(path)) path = '/';
|
||||
var index = path.lastIndexOf('/');
|
||||
path = path.substring(0, index + 1) + parts[_ComponentIndex.Path];
|
||||
parts[_ComponentIndex.Path] = path;
|
||||
return _joinAndCanonicalizePath(parts);
|
||||
}
|
5
modules/angular2/src/core/compiler/xhr.ts
Normal file
5
modules/angular2/src/core/compiler/xhr.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import {Promise} from 'angular2/src/core/facade/async';
|
||||
|
||||
export class XHR {
|
||||
get(url: string): Promise<string> { return null; }
|
||||
}
|
14
modules/angular2/src/core/compiler/xhr_impl.dart
Normal file
14
modules/angular2/src/core/compiler/xhr_impl.dart
Normal file
@ -0,0 +1,14 @@
|
||||
library angular2.src.services.xhr_impl;
|
||||
|
||||
import 'dart:async' show Future;
|
||||
import 'dart:html' show HttpRequest;
|
||||
import 'package:angular2/core.dart';
|
||||
import './xhr.dart' show XHR;
|
||||
|
||||
@Injectable()
|
||||
class XHRImpl extends XHR {
|
||||
Future<String> get(String url) {
|
||||
return HttpRequest.request(url).then((HttpRequest req) => req.responseText,
|
||||
onError: (_) => new Future.error('Failed to load $url'));
|
||||
}
|
||||
}
|
41
modules/angular2/src/core/compiler/xhr_impl.ts
Normal file
41
modules/angular2/src/core/compiler/xhr_impl.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/core/facade/async';
|
||||
import {isPresent} from 'angular2/src/core/facade/lang';
|
||||
import {XHR} from './xhr';
|
||||
|
||||
@Injectable()
|
||||
export class XHRImpl extends XHR {
|
||||
get(url: string): Promise<string> {
|
||||
var completer: PromiseCompleter < string >= PromiseWrapper.completer();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'text';
|
||||
|
||||
xhr.onload = function() {
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
|
||||
var response = isPresent(xhr.response) ? xhr.response : xhr.responseText;
|
||||
|
||||
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
|
||||
var status = xhr.status === 1223 ? 204 : xhr.status;
|
||||
|
||||
// fix status code when it is 0 (0 status is undocumented).
|
||||
// Occurs when accessing file resources or on Android 4.1 stock browser
|
||||
// while retrieving files from application cache.
|
||||
if (status === 0) {
|
||||
status = response ? 200 : 0;
|
||||
}
|
||||
|
||||
if (200 <= status && status <= 300) {
|
||||
completer.resolve(response);
|
||||
} else {
|
||||
completer.reject(`Failed to load ${url}`, null);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() { completer.reject(`Failed to load ${url}`, null); };
|
||||
|
||||
xhr.send();
|
||||
return completer.promise;
|
||||
}
|
||||
}
|
105
modules/angular2/src/core/compiler/xhr_mock.ts
Normal file
105
modules/angular2/src/core/compiler/xhr_mock.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {XHR} from 'angular2/src/core/render/xhr';
|
||||
import {ListWrapper, Map, MapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {isBlank, isPresent, normalizeBlank} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
import {PromiseCompleter, PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
|
||||
|
||||
export class MockXHR extends XHR {
|
||||
private _expectations: _Expectation[];
|
||||
private _definitions = new Map<string, string>();
|
||||
private _requests: _PendingRequest[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._expectations = [];
|
||||
this._requests = [];
|
||||
}
|
||||
|
||||
get(url: string): Promise<string> {
|
||||
var request = new _PendingRequest(url);
|
||||
this._requests.push(request);
|
||||
return request.getPromise();
|
||||
}
|
||||
|
||||
expect(url: string, response: string) {
|
||||
var expectation = new _Expectation(url, response);
|
||||
this._expectations.push(expectation);
|
||||
}
|
||||
|
||||
when(url: string, response: string) { this._definitions.set(url, response); }
|
||||
|
||||
flush() {
|
||||
if (this._requests.length === 0) {
|
||||
throw new BaseException('No pending requests to flush');
|
||||
}
|
||||
|
||||
do {
|
||||
var request = ListWrapper.removeAt(this._requests, 0);
|
||||
this._processRequest(request);
|
||||
} while (this._requests.length > 0);
|
||||
|
||||
this.verifyNoOustandingExpectations();
|
||||
}
|
||||
|
||||
verifyNoOustandingExpectations() {
|
||||
if (this._expectations.length === 0) return;
|
||||
|
||||
var urls = [];
|
||||
for (var i = 0; i < this._expectations.length; i++) {
|
||||
var expectation = this._expectations[i];
|
||||
urls.push(expectation.url);
|
||||
}
|
||||
|
||||
throw new BaseException(`Unsatisfied requests: ${ListWrapper.join(urls, ', ')}`);
|
||||
}
|
||||
|
||||
private _processRequest(request: _PendingRequest) {
|
||||
var url = request.url;
|
||||
|
||||
if (this._expectations.length > 0) {
|
||||
var expectation = this._expectations[0];
|
||||
if (expectation.url == url) {
|
||||
ListWrapper.remove(this._expectations, expectation);
|
||||
request.complete(expectation.response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._definitions.has(url)) {
|
||||
var response = this._definitions.get(url);
|
||||
request.complete(normalizeBlank(response));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BaseException(`Unexpected request ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
class _PendingRequest {
|
||||
url: string;
|
||||
completer: PromiseCompleter<string>;
|
||||
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.completer = PromiseWrapper.completer();
|
||||
}
|
||||
|
||||
complete(response: string) {
|
||||
if (isBlank(response)) {
|
||||
this.completer.reject(`Failed to load ${this.url}`, null);
|
||||
} else {
|
||||
this.completer.resolve(response);
|
||||
}
|
||||
}
|
||||
|
||||
getPromise(): Promise<string> { return this.completer.promise; }
|
||||
}
|
||||
|
||||
class _Expectation {
|
||||
url: string;
|
||||
response: string;
|
||||
constructor(url: string, response: string) {
|
||||
this.url = url;
|
||||
this.response = response;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user