From 8e37b28b449011eb427f20e279ed5366d127fb0f Mon Sep 17 00:00:00 2001 From: Daniel Freedman Date: Mon, 3 Feb 2014 16:23:26 -0800 Subject: [PATCH] Recursively resolve @import rules add tests for style loader Use cssRules, wait for polymer to fully load Fix bug after removing index as an argument Add more @imports to test with Make the XHR loader more generic, expose on Platform.loader.fetch Split apart "Loading" from "Flattening" Generic Loader takes urls, returns a map of url -> text. StyleResolver takes maps of urls and flattens the @imports Place recursive test in a non-conflicting location Factor Loader to deduplicate XHRs for the same absolute url Add a "process" to Loader that acts as an all in one. Add tests for Loader deduplication --- build.json | 1 + platform.js | 1 + src/loader.js | 152 +++++++++--------- src/styleloader.js | 108 +++++++++++++ test/html/loader-deduplicate.html | 38 +++++ test/html/styling/recursive-style-import.html | 78 +++++++++ test/html/styling/rules/coloring/border.css | 3 + test/html/styling/rules/coloring/colors.css | 5 + .../styling/rules/coloring/foreground.css | 3 + test/html/styling/rules/colors.css | 1 + test/html/styling/rules/sizing.css | 1 + test/html/styling/rules/sizing/height.css | 3 + test/html/styling/rules/sizing/hw.css | 2 + test/html/styling/rules/sizing/width.css | 3 + test/html/styling/rules/translation.css | 1 + test/html/styling/rules/translations/2d.css | 4 + test/js/tests.js | 5 + 17 files changed, 334 insertions(+), 75 deletions(-) create mode 100644 src/styleloader.js create mode 100644 test/html/loader-deduplicate.html create mode 100644 test/html/styling/recursive-style-import.html create mode 100644 test/html/styling/rules/coloring/border.css create mode 100644 test/html/styling/rules/coloring/colors.css create mode 100644 test/html/styling/rules/coloring/foreground.css create mode 100644 test/html/styling/rules/colors.css create mode 100644 test/html/styling/rules/sizing.css create mode 100644 test/html/styling/rules/sizing/height.css create mode 100644 test/html/styling/rules/sizing/hw.css create mode 100644 test/html/styling/rules/sizing/width.css create mode 100644 test/html/styling/rules/translation.css create mode 100644 test/html/styling/rules/translations/2d.css diff --git a/build.json b/build.json index 664bef6..de976c8 100644 --- a/build.json +++ b/build.json @@ -23,6 +23,7 @@ "../CustomElements/build.json", "src/patches-custom-elements.js", "src/loader.js", + "src/styleloader.js", "../PointerEvents/build.json", "../PointerGestures/build.json", diff --git a/platform.js b/platform.js index 8d12949..3c6d501 100644 --- a/platform.js +++ b/platform.js @@ -62,6 +62,7 @@ function processFlags(flags) { '../CustomElements/custom-elements.js', 'src/patches-custom-elements.js', 'src/loader.js', + 'src/styleloader.js', // TODO(sjmiles): pointergestures.js loads pointerevents, but // the build.json does not diff --git a/src/loader.js b/src/loader.js index b8d47ed..80a8ac4 100644 --- a/src/loader.js +++ b/src/loader.js @@ -4,86 +4,88 @@ * license that can be found in the LICENSE file. */ (function(scope) { + var endOfMicrotask = scope.endOfMicrotask; -var STYLE_SELECTOR = 'style'; - -var urlResolver = scope.urlResolver; - -var loader = { - cacheStyles: function(styles, callback) { - var css = []; - for (var i=0, l=styles.length, s; (i text from matches + fetch: function(matches, map, callback) { + var inflight = matches.length; -var atImportRe = /@import\s[(]?['"]?([^\s'";)]*)/; + // return early if there is no fetching to be done + if (!inflight) { + return callback(map); + } -// get the first @import rule from a style -function atImportUrlFromStyle(style) { - var matches = style.textContent.match(atImportRe); - return matches && matches[1]; -} + var done = function() { + if (--inflight === 0) { + callback(map); + } + }; -function replaceAtImportWithCssText(style, url, cssText) { - var re = new RegExp('@import[^;]*' + url + '[^;]*;', 'i'); - style.textContent = style.textContent.replace(re, cssText); -} + // map url -> responseText + var handleXhr = function(err, request) { + var match = request.match; + var key = match.url; + // handle errors with an empty string + if (err) { + map[key] = ''; + return done(); + } + var response = request.response || request.responseText; + map[key] = response; + this.fetch(this.extractUrls(response, key), map, done); + }; -// exports -scope.loader = loader; + var m, req, url; + for (var i = 0; i < inflight; i++) { + m = matches[i]; + url = m.url; + // if this url has already been requested, skip requesting it again + if (map[url]) { + // Async call to done to simplify the inflight logic + endOfMicrotask(done); + continue; + } + req = this.xhr(url, handleXhr, this); + req.match = m; + // tag the map with an XHR request to deduplicate at the same level + map[url] = req; + } + }, + xhr: function(url, callback, scope) { + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.send(); + request.onload = function() { + callback.call(scope, null, request); + }; + request.onerror = function() { + callback.call(scope, request, null); + }; + return request; + } + }; + scope.Loader = Loader; })(window.Platform); diff --git a/src/styleloader.js b/src/styleloader.js new file mode 100644 index 0000000..292af55 --- /dev/null +++ b/src/styleloader.js @@ -0,0 +1,108 @@ +/* + * Copyright 2014 The Polymer Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ +(function(scope) { + +var urlResolver = scope.urlResolver; +var Loader = scope.Loader; + +function StyleResolver() { + this.loader = new Loader(this.regex); +} +StyleResolver.prototype = { + regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, + // Recursively replace @imports with the text at that url + resolve: function(text, url, callback) { + var done = function(map) { + callback(this.flatten(text, url, map)); + }.bind(this); + this.loader.process(text, url, done); + }, + // resolve the textContent of a style node + resolveNode: function(style, callback) { + var text = style.textContent; + var url = style.ownerDocument.baseURI; + var done = function(text) { + style.textContent = text; + callback(style); + }; + this.resolve(text, url, done); + }, + // flatten all the @imports to text + flatten: function(text, base, map) { + var matches = this.loader.extractUrls(text, base); + var match, url, intermediate; + for (var i = 0; i < matches.length; i++) { + match = matches[i]; + url = match.url; + // resolve any css text to be relative to the importer + intermediate = urlResolver.resolveCssText(map[url], url); + // flatten intermediate @imports + intermediate = this.flatten(intermediate, url, map); + text = text.replace(match.matched, intermediate); + } + return text; + } +}; + +var styleResolver = new StyleResolver(); +var loader = { + cacheStyles: function(styles, callback) { + var css = []; + for (var i=0, l=styles.length, s; (i + + + + Deduplicating Loader + + + + + + + + + import: styling/rules/colors.css; + import: styling/rules/colors.css; + + + + diff --git a/test/html/styling/recursive-style-import.html b/test/html/styling/recursive-style-import.html new file mode 100644 index 0000000..9e3980d --- /dev/null +++ b/test/html/styling/recursive-style-import.html @@ -0,0 +1,78 @@ + + + + + + Styling via @import + + + + + +
+ I am Orange! +
+ + + + diff --git a/test/html/styling/rules/coloring/border.css b/test/html/styling/rules/coloring/border.css new file mode 100644 index 0000000..325b122 --- /dev/null +++ b/test/html/styling/rules/coloring/border.css @@ -0,0 +1,3 @@ +#target { + border: 2px solid gray; +} diff --git a/test/html/styling/rules/coloring/colors.css b/test/html/styling/rules/coloring/colors.css new file mode 100644 index 0000000..253de1a --- /dev/null +++ b/test/html/styling/rules/coloring/colors.css @@ -0,0 +1,5 @@ +@import url('foreground.css'); +@import 'border.css'; +#target { + background: green; +} diff --git a/test/html/styling/rules/coloring/foreground.css b/test/html/styling/rules/coloring/foreground.css new file mode 100644 index 0000000..fa884a2 --- /dev/null +++ b/test/html/styling/rules/coloring/foreground.css @@ -0,0 +1,3 @@ +#target { + color: orange; +} diff --git a/test/html/styling/rules/colors.css b/test/html/styling/rules/colors.css new file mode 100644 index 0000000..8828867 --- /dev/null +++ b/test/html/styling/rules/colors.css @@ -0,0 +1 @@ +@import url(coloring/colors.css); diff --git a/test/html/styling/rules/sizing.css b/test/html/styling/rules/sizing.css new file mode 100644 index 0000000..e6b8451 --- /dev/null +++ b/test/html/styling/rules/sizing.css @@ -0,0 +1 @@ +@import url(sizing/hw.css); diff --git a/test/html/styling/rules/sizing/height.css b/test/html/styling/rules/sizing/height.css new file mode 100644 index 0000000..83b8f97 --- /dev/null +++ b/test/html/styling/rules/sizing/height.css @@ -0,0 +1,3 @@ +#target { + height: 100px; +} diff --git a/test/html/styling/rules/sizing/hw.css b/test/html/styling/rules/sizing/hw.css new file mode 100644 index 0000000..d86e4ac --- /dev/null +++ b/test/html/styling/rules/sizing/hw.css @@ -0,0 +1,2 @@ +@import 'height.css'; +@import url('width.css'); diff --git a/test/html/styling/rules/sizing/width.css b/test/html/styling/rules/sizing/width.css new file mode 100644 index 0000000..d94dfb1 --- /dev/null +++ b/test/html/styling/rules/sizing/width.css @@ -0,0 +1,3 @@ +#target { + width: 100px; +} diff --git a/test/html/styling/rules/translation.css b/test/html/styling/rules/translation.css new file mode 100644 index 0000000..0151276 --- /dev/null +++ b/test/html/styling/rules/translation.css @@ -0,0 +1 @@ +@import 'translations/2d.css'; diff --git a/test/html/styling/rules/translations/2d.css b/test/html/styling/rules/translations/2d.css new file mode 100644 index 0000000..9d24af6 --- /dev/null +++ b/test/html/styling/rules/translations/2d.css @@ -0,0 +1,4 @@ +#target { + -webkit-transform: translate(50px, 50px); + transform: translate(50px, 50px); +} diff --git a/test/js/tests.js b/test/js/tests.js index c03e50a..16a3055 100644 --- a/test/js/tests.js +++ b/test/js/tests.js @@ -21,6 +21,10 @@ htmlSuite('integration', function() { htmlTest('html/ce-import.html?shadow'); }); +htmlSuite('URL Loader', function() { + htmlTest('html/loader-deduplicate.html'); +}); + htmlSuite('styling', function() { htmlTest('html/styling/pseudo-scoping.html'); htmlTest('html/styling/pseudo-scoping.html?shadow®ister'); @@ -36,6 +40,7 @@ htmlSuite('styling', function() { htmlTest('html/styling/before-content.html?shadow®ister'); htmlTest('html/styling/before-content.html'); htmlTest('html/styling/style-import.html'); + htmlTest('html/styling/recursive-style-import.html'); }); htmlSuite('Library Cooperation', function() {