Infrastructure for cross browser support of simple, experimental functions

This commit is contained in:
Chris Eppstein 2010-12-30 12:19:00 -08:00
parent 4c289c9db0
commit 15f760e002
7 changed files with 223 additions and 21 deletions
doc-src/content
lib/compass/sass_extensions
test/fixtures/stylesheets/compass

View File

@ -26,6 +26,21 @@ The Documentation for the [latest preview release](http://beta.compass-style.org
The CLI options will still override the values set within the config file, but they might
inform other values. For instance `compass compile -e production` will have the environment
parameter preset to `:production` so that you can set other values in the project accordingly.
* New infrastructure for supporting experimental css3 functions that have the same syntax
across all browsers. It is now possible to configure which browsers support which experimental
functions outside of the compass release cycle by calling
`Compass::BrowserSupport.add_support('function', 'prefix')` and
`Compass::BrowserSupport.remove_support('function', 'prefix')`.
Once properly configured, the browser specific aspect can be inspected
via the `prefixed()` helper function and accessed via the various support
aspect helpers like `-moz()` and `-webkit()`. For example, if you call:
`Compass::BrowserSupport.add_support('image', 'webkit')` in your config
then in your stylesheet, `prefixed(-webkit, image("foo.png", "foo.gif"))` would
return true and `prefixed(-moz, image("foo.png", "foo.gif"))` would return false.
Additionally `-webkit(image("foo.png", "foo.gif"))` will return
`-webkit-image("foo.png", "foo.gif")`. This is very useful for creating mixins
that can support a range of inputs and vary the outputs according to the
support needs.
0.11.alpha.4 (12/08/2010)

View File

@ -133,29 +133,29 @@ module Compass::SassExtensions::Functions::GradientSupport
# Check if any of the arguments passed have a tendency towards vendor prefixing.
def prefixed(prefix, *args)
method = prefix.value.sub(/^-/,"to_").to_sym
method = prefix.value.sub(/^-/,"")
args.map!{|a| a.is_a?(List) ? a.values : a}.flatten!
Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)})
Sass::Script::Bool.new(args.any?{|a| a.supports?(method)})
end
%w(webkit moz o ms svg pie css2).each do |prefix|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _#{prefix}(*args)
List.new(*args.map! {|a| add_prefix(:to_#{prefix}, a)})
List.new(*args.map! {|a| add_prefix("#{prefix}", a)})
end
RUBY
end
protected
def add_prefix(prefix_method, object)
def add_prefix(prefix, object)
if object.is_a?(List)
object.class.new(object.value.map{|e|
add_prefix(prefix_method, e)
add_prefix(prefix, e)
})
elsif object.respond_to?(prefix_method)
elsif object.respond_to?(:supports?) && object.supports?(prefix)
object.options = options
object.send(prefix_method)
object.send(:"to_#{prefix_method}")
else
object
end
@ -269,33 +269,31 @@ module Compass::SassExtensions::Functions::GradientSupport
Sass::Script::List.new list.values[start_index..end_index], list.separator
end
# Check if any of the arguments passed have a tendency towards vendor prefixing.
# Check if any of the arguments passed have require the vendor prefix.
def prefixed(prefix, *args)
method = prefix.value.sub(/^-/,"to_").to_sym
2.times do
args.map!{|a| a.is_a?(Sass::Script::List) ? a.value : a}.flatten!
end
Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)})
aspect = prefix.value.sub(/^-/,"")
needed = args.any?{|a| a.respond_to?(:supports?) && a.supports?(aspect)}
Sass::Script::Bool.new(needed)
end
%w(webkit moz o ms svg pie css2).each do |prefix|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _#{prefix}(*args)
Sass::Script::List.new(args.map! {|a| add_prefix(:to_#{prefix}, a)}, :comma)
Sass::Script::List.new(args.map! {|a| add_prefix("#{prefix}", a)}, :comma)
end
RUBY
end
protected
def add_prefix(prefix_method, object)
def add_prefix(prefix, object)
if object.is_a?(Sass::Script::List)
Sass::Script::List.new(object.value.map{|e|
add_prefix(prefix_method, e)
add_prefix(prefix, e)
}, object.separator)
elsif object.respond_to?(prefix_method)
elsif object.respond_to?(:supports?) && object.supports?(prefix)
object.options = options
object.send(prefix_method)
object.send(:"to_#{prefix}")
else
object
end
@ -317,7 +315,6 @@ module Compass::SassExtensions::Functions::GradientSupport
end
class ColorStop < Sass::Script::Literal
attr_accessor :color, :stop
def children
@ -349,6 +346,8 @@ module Compass::SassExtensions::Functions::GradientSupport
end
end
GRADIENT_ASPECTS = %w(webkit moz svg pie css2).freeze
class RadialGradient < Sass::Script::Literal
attr_accessor :position_and_angle, :shape_and_size, :color_stops
def children
@ -372,6 +371,12 @@ module Compass::SassExtensions::Functions::GradientSupport
s << color_stops.to_s(options)
s << ")"
end
def supports?(aspect)
GRADIENT_ASPECTS.include?(aspect)
end
def has_aspect?
true
end
def to_webkit(options = self.options)
args = [
grad_point(position_and_angle || _center_position),
@ -421,6 +426,12 @@ module Compass::SassExtensions::Functions::GradientSupport
s << color_stops.to_s(options)
s << ")"
end
def supports?(aspect)
GRADIENT_ASPECTS.include?(aspect)
end
def has_aspect?
true
end
def to_webkit(options = self.options)
args = []
args << grad_point(position_and_angle || Sass::Script::String.new("top"))
@ -726,6 +737,7 @@ EOS
end
end
class LinearGradient < Sass::Script::Literal
include Functions
include Compass::SassExtensions::Functions::Constants

View File

@ -1,3 +1,3 @@
%w(traversal).each do |patch|
%w(traversal browser_support).each do |patch|
require "compass/sass_extensions/monkey_patches/#{patch}"
end

View File

@ -0,0 +1,152 @@
require 'sass/script/node'
require 'sass/script/literal'
require 'sass/script/funcall'
module Compass
module BrowserSupport
extend self
ASPECTS = %w(webkit moz o ms svg pie css2)
SIMPLE_FUNCTIONS = {
"image" => %w(), # No browsers implement this yet.
"cross-fade" => %w() # No browsers implement this yet.
}
# Adds support for one or more aspects for the given simple function
# Example:
#
# Compass::BrowserSupport.add_support("image", "moz", "webkit")
# # => Adds support for moz and webkit to the image() function.
#
# This function can be called one or more times in a compass configuration
# file in order to add support for new, simple browser functions without
# waiting for a new compass release.
def add_support(function, *aspects)
aspects.each do |aspect|
unless ASPECTS.include?(aspect)
Compass::Util.compass_warn "Unknown support aspect: #{aspect}"
next
end
unless supports?(function, aspect)
SIMPLE_FUNCTIONS[function.to_s] ||= []
SIMPLE_FUNCTIONS[function.to_s] << aspect.to_s
end
end
end
# Removes support for one or more aspects for the given simple function
# Example:
#
# Compass::BrowserSupport.remove_support("image", "o", "ms")
# # => Adds support for moz and webkit to the image() function.
#
# This function can be called one or more times in a compass configuration
# file in order to remove support for simple functions that no longer need to
# a prefix without waiting for a new compass release.
def remove_support(function, *aspects)
aspects.each do |aspect|
unless ASPECTS.include?(aspect)
Compass::Util.compass_warn "Unknown support aspect: #{aspect}"
next
end
SIMPLE_FUNCTIONS[function.to_s].reject!{|a| a == aspect.to_s}
end
end
def supports?(function, aspect)
SIMPLE_FUNCTIONS.has_key?(function.to_s) && SIMPLE_FUNCTIONS[function.to_s].include?(aspect.to_s)
end
def has_aspect?(function)
SIMPLE_FUNCTIONS.has_key?(function.to_s) && SIMPLE_FUNCTIONS[function.to_s].size > 0
end
end
end
module Sass::Script
module HasSimpleCrossBrowserFunctionSupport
def supports?(aspect)
return true if Compass::BrowserSupport.supports?(name, aspect)
children.any? {|child| child.respond_to?(:supports?) && child.supports?(aspect) }
end
def has_aspect?(children = nil)
children ||= self.children
return true if Compass::BrowserSupport.has_aspect?(name)
children.any? {|child| child.respond_to?(:has_aspect?) && child.has_aspect? }
end
end
class CrossBrowserFunctionCall < Literal
attr_accessor :name, :args
include HasSimpleCrossBrowserFunctionSupport
def initialize(name, args)
self.name = name
self.args = args
end
def children
args
end
def inspect
to_s
end
def to_s(options = self.options)
s = "#{name}(#{args.join(", ")})"
end
%w(webkit moz o ms svg pie css2).each do |prefix|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def to_#{prefix}(options = self.options)
prefixed_args = args.map do |arg|
arg.respond_to?(:to_#{prefix}) ? arg.to_#{prefix}(options) : arg
end
prefixed_name = if Compass::BrowserSupport.supports?(name, "#{prefix}")
"-#{prefix}-\#{name}"
else
name
end
contents = prefixed_args.join(', ')
if contents.size > 0
opts(Sass::Script::String.new("\#{prefixed_name}(\#{contents})"))
else
opts(Sass::Script::String.new(""))
end
end
RUBY
end
end
class Funcall < Node
include HasSimpleCrossBrowserFunctionSupport
alias sass_to_literal to_literal
def to_literal(args)
if has_aspect?(args)
CrossBrowserFunctionCall.new(name, args)
else
sass_to_literal(args)
end
end
end
class List < Literal
def supports?(aspect)
children.any? {|child| child.respond_to?(:supports?) && child.supports?(aspect) }
end
def has_aspect?
children.any? {|child| child.respond_to?(:has_aspect?) && child.has_aspect? }
end
end
end

View File

@ -96,6 +96,21 @@
background-image: -moz-radial-gradient(#dddddd, #aaaaaa 100px);
background-image: radial-gradient(#dddddd, #aaaaaa 100px); }
.image-fallback {
background-image: image(-webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)), url('/images/4x6.png?busted=true'), #cc0000);
background-image: image(-moz-radial-gradient(#dddddd, #aaaaaa 100px), url('/images/4x6.png?busted=true'), #cc0000);
background-image: image(radial-gradient(#dddddd, #aaaaaa 100px), url('/images/4x6.png?busted=true'), #cc0000); }
.cross-fade {
background-image: cross-fade(-webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)), url('/images/4x6.png?busted=true'));
background-image: cross-fade(-moz-radial-gradient(#dddddd, #aaaaaa 100px), url('/images/4x6.png?busted=true'));
background-image: cross-fade(radial-gradient(#dddddd, #aaaaaa 100px), url('/images/4x6.png?busted=true')); }
.unknown-function-wrapper {
background: foo(-webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)));
background: foo(-moz-radial-gradient(#dddddd, #aaaaaa 100px));
background: foo(radial-gradient(#dddddd, #aaaaaa 100px)); }
.linear-1 {
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa));
background-image: -moz-linear-gradient(top, #dddddd, #aaaaaa);

View File

@ -1,4 +1,3 @@
@charset "UTF-8";
.pie-element, .bordered, .gradient {
behavior: url('/tmp/PIE.htc');
position: relative; }

View File

@ -58,6 +58,15 @@ $experimental-support-for-svg: false
.bg-radial-gradient-no-position
+background-image(radial-gradient(#ddd, #aaa 100px))
.image-fallback
+background-image(image(radial-gradient(#ddd, #aaa 100px), image-url("4x6.png"), #c00))
.cross-fade
+background-image(cross-fade(radial-gradient(#ddd, #aaa 100px), image-url("4x6.png")))
.unknown-function-wrapper
+background(foo(radial-gradient(#ddd, #aaa 100px)))
.linear-1
+linear-gradient(color-stops(#dddddd, #aaaaaa))