mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Add 'src/components/script/style/' from commit 'dd8b178860
'
git-subtree-dir: src/components/script/style git-subtree-mainline:0c726b4581
git-subtree-split:dd8b178860
This commit is contained in:
commit
71ca10a50c
15 changed files with 2190 additions and 0 deletions
10
src/components/script/style/.gitignore
vendored
Normal file
10
src/components/script/style/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.dummy
|
||||||
|
*.pyc
|
||||||
|
*-test
|
||||||
|
Makefile
|
||||||
|
properties/mod.rs
|
373
src/components/script/style/LICENSE
Normal file
373
src/components/script/style/LICENSE
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
39
src/components/script/style/Makefile.in
Normal file
39
src/components/script/style/Makefile.in
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
VPATH=%VPATH%
|
||||||
|
CSSPARSER_DIR ?= ../rust-cssparser
|
||||||
|
|
||||||
|
CC ?= gcc
|
||||||
|
CXX ?= g++
|
||||||
|
CXXFLAGS ?=
|
||||||
|
AR ?= ar
|
||||||
|
RUSTC ?= rustc
|
||||||
|
RUSTFLAGS ?= -L $(CSSPARSER_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
PROPERTIES_RS=$(VPATH)/properties/mod.rs
|
||||||
|
RUST_SRC=$(shell find $(VPATH)/. -type f -name '*.rs') $(CSSPARSER_DIR)/libcssparser.dummy $(PROPERTIES_RS)
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: libservo-style.dummy
|
||||||
|
|
||||||
|
libservo-style.dummy: servo-style.rc $(RUST_SRC)
|
||||||
|
$(RUSTC) $(RUSTFLAGS) $<
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
servo-style-test: servo-style.rc $(RUST_SRC)
|
||||||
|
$(RUSTC) $(RUSTFLAGS) $< -o $@ --test
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: servo-style-test
|
||||||
|
./servo-style-test
|
||||||
|
|
||||||
|
.PHONY: check-debug
|
||||||
|
check-debug: servo-style-tests
|
||||||
|
echo -e "start\n break upcall_fail\n continue\n where\n continue" | gdb -q ./servo-style-tests
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f *.o *.a *.so *.dylib *.dll *.dummy *-test
|
||||||
|
|
||||||
|
|
||||||
|
$(PROPERTIES_RS): $(PROPERTIES_RS).mako
|
||||||
|
PYTHONPATH=$(VPATH)/Mako-0.8.1.zip python -c "from mako.template import Template; print(Template(filename='$<').render())" > $@
|
BIN
src/components/script/style/Mako-0.8.1.zip
Normal file
BIN
src/components/script/style/Mako-0.8.1.zip
Normal file
Binary file not shown.
10
src/components/script/style/README.md
Normal file
10
src/components/script/style/README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
servo-style
|
||||||
|
===========
|
||||||
|
|
||||||
|
Prototype replacement style system for Servo,
|
||||||
|
based on [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser)
|
||||||
|
instead of [NetSurf’s libcss](https://github.com/mozilla-servo/libcss).
|
||||||
|
|
||||||
|
This is meant to go into Servo’s `src/components/script/style` directory,
|
||||||
|
but is maintained here for now because it requires at least
|
||||||
|
Rust ecfc9a8 (2013-08-12).
|
4
src/components/script/style/configure
vendored
Executable file
4
src/components/script/style/configure
vendored
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SRCDIR="$(cd $(dirname $0) && pwd)"
|
||||||
|
sed "s#%VPATH%#${SRCDIR}#" ${SRCDIR}/Makefile.in > Makefile
|
26
src/components/script/style/errors.rs
Normal file
26
src/components/script/style/errors.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use cssparser::{SyntaxError, SourceLocation};
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ErrorLoggerIterator<I>(I);
|
||||||
|
|
||||||
|
impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator<I> {
|
||||||
|
fn next(&mut self) -> Option<T> {
|
||||||
|
for result in **self {
|
||||||
|
match result {
|
||||||
|
Ok(v) => return Some(v),
|
||||||
|
Err(error) => log_css_error(error.location, fmt!("%?", error.reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn log_css_error(location: SourceLocation, message: &str) {
|
||||||
|
// TODO eventually this will got into a "web console" or something.
|
||||||
|
info!("%u:%u %s", location.line, location.column, message)
|
||||||
|
}
|
124
src/components/script/style/media_queries.rs
Normal file
124
src/components/script/style/media_queries.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use cssparser::*;
|
||||||
|
use errors::{ErrorLoggerIterator, log_css_error};
|
||||||
|
use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
|
||||||
|
use namespaces::NamespaceMap;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct MediaRule {
|
||||||
|
media_queries: MediaQueryList,
|
||||||
|
rules: ~[CSSRule],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct MediaQueryList {
|
||||||
|
// "not all" is omitted from the list.
|
||||||
|
// An empty list never matches.
|
||||||
|
media_queries: ~[MediaQuery]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, this is a "Level 2 MQ", ie. a media type.
|
||||||
|
struct MediaQuery {
|
||||||
|
media_type: MediaQueryType,
|
||||||
|
// TODO: Level 3 MQ expressions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum MediaQueryType {
|
||||||
|
All, // Always true
|
||||||
|
MediaType(MediaType),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
|
pub enum MediaType {
|
||||||
|
Screen,
|
||||||
|
Print,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Device {
|
||||||
|
media_type: MediaType,
|
||||||
|
// TODO: Level 3 MQ data: viewport size, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_media_rule(rule: AtRule, parent_rules: &mut ~[CSSRule],
|
||||||
|
namespaces: &NamespaceMap) {
|
||||||
|
let media_queries = parse_media_query_list(rule.prelude);
|
||||||
|
let block = match rule.block {
|
||||||
|
Some(block) => block,
|
||||||
|
None => {
|
||||||
|
log_css_error(rule.location, "Invalid @media rule");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut rules = ~[];
|
||||||
|
for rule in ErrorLoggerIterator(parse_rule_list(block.move_iter())) {
|
||||||
|
match rule {
|
||||||
|
QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces),
|
||||||
|
AtRule(rule) => parse_nested_at_rule(
|
||||||
|
rule.name.to_ascii_lower(), rule, &mut rules, namespaces),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent_rules.push(CSSMediaRule(MediaRule {
|
||||||
|
media_queries: media_queries,
|
||||||
|
rules: rules,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
|
||||||
|
let iter = &mut input.skip_whitespace();
|
||||||
|
let mut next = iter.next();
|
||||||
|
if next.is_none() {
|
||||||
|
return MediaQueryList{ media_queries: ~[MediaQuery{media_type: All}] }
|
||||||
|
}
|
||||||
|
let mut queries = ~[];
|
||||||
|
loop {
|
||||||
|
let mq = match next {
|
||||||
|
Some(&Ident(ref value)) => {
|
||||||
|
match value.to_ascii_lower().as_slice() {
|
||||||
|
"screen" => Some(MediaQuery{ media_type: MediaType(Screen) }),
|
||||||
|
"print" => Some(MediaQuery{ media_type: MediaType(Print) }),
|
||||||
|
"all" => Some(MediaQuery{ media_type: All }),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
match iter.next() {
|
||||||
|
None => {
|
||||||
|
mq.map_move(|mq| queries.push(mq));
|
||||||
|
return MediaQueryList{ media_queries: queries }
|
||||||
|
},
|
||||||
|
Some(&Comma) => {
|
||||||
|
mq.map_move(|mq| queries.push(mq));
|
||||||
|
},
|
||||||
|
// Ingnore this comma-separated part
|
||||||
|
_ => loop {
|
||||||
|
match iter.next() {
|
||||||
|
Some(&Comma) => break,
|
||||||
|
None => return MediaQueryList{ media_queries: queries },
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
next = iter.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl MediaQueryList {
|
||||||
|
pub fn evaluate(&self, device: &Device) -> bool {
|
||||||
|
do self.media_queries.iter().any |mq| {
|
||||||
|
match mq.media_type {
|
||||||
|
MediaType(media_type) => media_type == device.media_type,
|
||||||
|
All => true,
|
||||||
|
}
|
||||||
|
// TODO: match Level 3 expressions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/components/script/style/namespaces.rs
Normal file
63
src/components/script/style/namespaces.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::hashmap::HashMap;
|
||||||
|
use cssparser::*;
|
||||||
|
use errors::log_css_error;
|
||||||
|
|
||||||
|
pub struct NamespaceMap {
|
||||||
|
default: Option<~str>, // Optional URL
|
||||||
|
prefix_map: HashMap<~str, ~str>, // prefix -> URL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl NamespaceMap {
|
||||||
|
pub fn new() -> NamespaceMap {
|
||||||
|
NamespaceMap { default: None, prefix_map: HashMap::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) {
|
||||||
|
let location = rule.location;
|
||||||
|
macro_rules! syntax_error(
|
||||||
|
() => {{
|
||||||
|
log_css_error(location, "Invalid @namespace rule");
|
||||||
|
return
|
||||||
|
}};
|
||||||
|
);
|
||||||
|
if rule.block.is_some() { syntax_error!() }
|
||||||
|
let mut prefix: Option<~str> = None;
|
||||||
|
let mut url: Option<~str> = None;
|
||||||
|
let mut iter = rule.prelude.move_skip_whitespace();
|
||||||
|
for component_value in iter {
|
||||||
|
match component_value {
|
||||||
|
Ident(value) => {
|
||||||
|
if prefix.is_some() { syntax_error!() }
|
||||||
|
prefix = Some(value);
|
||||||
|
},
|
||||||
|
URL(value) | String(value) => {
|
||||||
|
if url.is_some() { syntax_error!() }
|
||||||
|
url = Some(value);
|
||||||
|
break
|
||||||
|
},
|
||||||
|
_ => syntax_error!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.next().is_some() { syntax_error!() }
|
||||||
|
match (prefix, url) {
|
||||||
|
(Some(prefix), Some(url)) => {
|
||||||
|
if namespaces.prefix_map.swap(prefix, url).is_some() {
|
||||||
|
log_css_error(location, "Duplicate @namespace rule");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(None, Some(url)) => {
|
||||||
|
if namespaces.default.is_some() {
|
||||||
|
log_css_error(location, "Duplicate @namespace rule");
|
||||||
|
}
|
||||||
|
namespaces.default = Some(url);
|
||||||
|
},
|
||||||
|
_ => syntax_error!()
|
||||||
|
}
|
||||||
|
}
|
21
src/components/script/style/parsing_utils.rs
Normal file
21
src/components/script/style/parsing_utils.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use cssparser::*;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Option<&'a ComponentValue> {
|
||||||
|
let mut iter = input.skip_whitespace();
|
||||||
|
iter.next().filtered(|_| iter.next().is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_ident_lower(component_value: &ComponentValue) -> Option<~str> {
|
||||||
|
match component_value {
|
||||||
|
&Ident(ref value) => Some(value.to_ascii_lower()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
182
src/components/script/style/properties/common_types.rs
Normal file
182
src/components/script/style/properties/common_types.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
pub type Float = f64;
|
||||||
|
pub type Integer = i64;
|
||||||
|
|
||||||
|
|
||||||
|
pub mod specified {
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use cssparser::*;
|
||||||
|
use super::{Integer, Float};
|
||||||
|
pub use CSSColor = cssparser::Color;
|
||||||
|
|
||||||
|
pub enum Length {
|
||||||
|
Au(Integer), // application units
|
||||||
|
Em(Float),
|
||||||
|
Ex(Float),
|
||||||
|
// XXX uncomment when supported:
|
||||||
|
// Ch(Float),
|
||||||
|
// Rem(Float),
|
||||||
|
// Vw(Float),
|
||||||
|
// Vh(Float),
|
||||||
|
// Vmin(Float),
|
||||||
|
// Vmax(Float),
|
||||||
|
}
|
||||||
|
static AU_PER_PX: Float = 60.;
|
||||||
|
static AU_PER_IN: Float = AU_PER_PX * 96.;
|
||||||
|
static AU_PER_CM: Float = AU_PER_IN / 2.54;
|
||||||
|
static AU_PER_MM: Float = AU_PER_IN / 25.4;
|
||||||
|
static AU_PER_PT: Float = AU_PER_IN / 72.;
|
||||||
|
static AU_PER_PC: Float = AU_PER_PT * 12.;
|
||||||
|
impl Length {
|
||||||
|
#[inline]
|
||||||
|
fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Option<Length> {
|
||||||
|
match input {
|
||||||
|
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||||
|
=> Length::parse_dimension(value.value, unit.as_slice()),
|
||||||
|
&Number(ref value) if value.value == 0. => Some(Au(0)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn parse(input: &ComponentValue) -> Option<Length> {
|
||||||
|
Length::parse_internal(input, /* negative_ok = */ true)
|
||||||
|
}
|
||||||
|
pub fn parse_non_negative(input: &ComponentValue) -> Option<Length> {
|
||||||
|
Length::parse_internal(input, /* negative_ok = */ false)
|
||||||
|
}
|
||||||
|
pub fn parse_dimension(value: Float, unit: &str) -> Option<Length> {
|
||||||
|
match unit.to_ascii_lower().as_slice() {
|
||||||
|
"px" => Some(Length::from_px(value)),
|
||||||
|
"in" => Some(Au((value * AU_PER_IN) as Integer)),
|
||||||
|
"cm" => Some(Au((value * AU_PER_CM) as Integer)),
|
||||||
|
"mm" => Some(Au((value * AU_PER_MM) as Integer)),
|
||||||
|
"pt" => Some(Au((value * AU_PER_PT) as Integer)),
|
||||||
|
"pc" => Some(Au((value * AU_PER_PC) as Integer)),
|
||||||
|
"em" => Some(Em(value)),
|
||||||
|
"ex" => Some(Ex(value)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn from_px(px_value: Float) -> Length {
|
||||||
|
Au((px_value * AU_PER_PX) as Integer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LengthOrPercentage {
|
||||||
|
LP_Length(Length),
|
||||||
|
LP_Percentage(Float),
|
||||||
|
}
|
||||||
|
impl LengthOrPercentage {
|
||||||
|
fn parse_internal(input: &ComponentValue, negative_ok: bool)
|
||||||
|
-> Option<LengthOrPercentage> {
|
||||||
|
match input {
|
||||||
|
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||||
|
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LP_Length),
|
||||||
|
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
|
||||||
|
=> Some(LP_Percentage(value.value)),
|
||||||
|
&Number(ref value) if value.value == 0. => Some(LP_Length(Au(0))),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentage> {
|
||||||
|
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentage> {
|
||||||
|
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LengthOrPercentageOrAuto {
|
||||||
|
LPA_Length(Length),
|
||||||
|
LPA_Percentage(Float),
|
||||||
|
LPA_Auto,
|
||||||
|
}
|
||||||
|
impl LengthOrPercentageOrAuto {
|
||||||
|
fn parse_internal(input: &ComponentValue, negative_ok: bool)
|
||||||
|
-> Option<LengthOrPercentageOrAuto> {
|
||||||
|
match input {
|
||||||
|
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||||
|
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LPA_Length),
|
||||||
|
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
|
||||||
|
=> Some(LPA_Percentage(value.value)),
|
||||||
|
&Number(ref value) if value.value == 0. => Some(LPA_Length(Au(0))),
|
||||||
|
&Ident(ref value) if value.eq_ignore_ascii_case("auto") => Some(LPA_Auto),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
|
||||||
|
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
|
||||||
|
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod computed {
|
||||||
|
use cssparser;
|
||||||
|
pub use CSSColor = cssparser::Color;
|
||||||
|
pub use compute_CSSColor = std::util::id;
|
||||||
|
use super::*;
|
||||||
|
use super::super::longhands::font_weight;
|
||||||
|
pub struct Context {
|
||||||
|
current_color: cssparser::RGBA,
|
||||||
|
has_border_top: bool,
|
||||||
|
has_border_right: bool,
|
||||||
|
has_border_bottom: bool,
|
||||||
|
has_border_left: bool,
|
||||||
|
font_size: Length,
|
||||||
|
font_weight: font_weight::ComputedValue,
|
||||||
|
// TODO, as needed: root font size, viewport size, etc.
|
||||||
|
}
|
||||||
|
pub struct Length(Integer); // in application units
|
||||||
|
impl Length {
|
||||||
|
pub fn times(self, factor: Float) -> Length {
|
||||||
|
Length(((*self as Float) * factor) as Integer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_Length(value: specified::Length, context: &Context) -> Length {
|
||||||
|
match value {
|
||||||
|
specified::Au(value) => Length(value),
|
||||||
|
specified::Em(value) => context.font_size.times(value),
|
||||||
|
specified::Ex(value) => {
|
||||||
|
let x_height = 0.5; // TODO: find that from the font
|
||||||
|
context.font_size.times(value * x_height)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LengthOrPercentage {
|
||||||
|
LP_Length(Length),
|
||||||
|
LP_Percentage(Float),
|
||||||
|
}
|
||||||
|
pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context)
|
||||||
|
-> LengthOrPercentage {
|
||||||
|
match value {
|
||||||
|
specified::LP_Length(value) => LP_Length(compute_Length(value, context)),
|
||||||
|
specified::LP_Percentage(value) => LP_Percentage(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LengthOrPercentageOrAuto {
|
||||||
|
LPA_Length(Length),
|
||||||
|
LPA_Percentage(Float),
|
||||||
|
LPA_Auto,
|
||||||
|
}
|
||||||
|
pub fn compute_LengthOrPercentageOrAuto(value: specified::LengthOrPercentageOrAuto,
|
||||||
|
context: &Context) -> LengthOrPercentageOrAuto {
|
||||||
|
match value {
|
||||||
|
specified::LPA_Length(value) => LPA_Length(compute_Length(value, context)),
|
||||||
|
specified::LPA_Percentage(value) => LPA_Percentage(value),
|
||||||
|
specified::LPA_Auto => LPA_Auto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
687
src/components/script/style/properties/mod.rs.mako
Normal file
687
src/components/script/style/properties/mod.rs.mako
Normal file
|
@ -0,0 +1,687 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// This file is a Mako template: http://www.makotemplates.org/
|
||||||
|
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use errors::{ErrorLoggerIterator, log_css_error};
|
||||||
|
pub use std::iterator;
|
||||||
|
pub use cssparser::*;
|
||||||
|
pub use parsing_utils::*;
|
||||||
|
pub use self::common_types::*;
|
||||||
|
|
||||||
|
pub mod common_types;
|
||||||
|
|
||||||
|
|
||||||
|
<%!
|
||||||
|
|
||||||
|
def to_rust_ident(name):
|
||||||
|
name = name.replace("-", "_")
|
||||||
|
if name in ["static"]: # Rust keywords
|
||||||
|
name += "_"
|
||||||
|
return name
|
||||||
|
|
||||||
|
class Longhand(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.ident = to_rust_ident(name)
|
||||||
|
|
||||||
|
|
||||||
|
class Shorthand(object):
|
||||||
|
def __init__(self, name, sub_properties):
|
||||||
|
self.name = name
|
||||||
|
self.ident = to_rust_ident(name)
|
||||||
|
self.sub_properties = [Longhand(s) for s in sub_properties]
|
||||||
|
|
||||||
|
LONGHANDS = []
|
||||||
|
SHORTHANDS = []
|
||||||
|
INHERITED = set()
|
||||||
|
|
||||||
|
%>
|
||||||
|
|
||||||
|
pub mod longhands {
|
||||||
|
pub use super::*;
|
||||||
|
pub use std;
|
||||||
|
|
||||||
|
<%def name="longhand(name, inherited=False, no_super=False)">
|
||||||
|
<%
|
||||||
|
property = Longhand(name)
|
||||||
|
LONGHANDS.append(property)
|
||||||
|
if inherited:
|
||||||
|
INHERITED.add(name)
|
||||||
|
%>
|
||||||
|
pub mod ${property.ident} {
|
||||||
|
% if not no_super:
|
||||||
|
use super::*;
|
||||||
|
% endif
|
||||||
|
${caller.body()}
|
||||||
|
}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="single_component_value(name, inherited=False)">
|
||||||
|
<%self:longhand name="${name}" inherited="${inherited}">
|
||||||
|
${caller.body()}
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||||
|
one_component_value(input).chain(from_component_value)
|
||||||
|
}
|
||||||
|
</%self:longhand>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="single_keyword(name, values, inherited=False)">
|
||||||
|
<%self:single_component_value name="${name}" inherited="${inherited}">
|
||||||
|
// The computed value is the same as the specified value.
|
||||||
|
pub use to_computed_value = std::util::id;
|
||||||
|
pub enum SpecifiedValue {
|
||||||
|
% for value in values.split():
|
||||||
|
${to_rust_ident(value)},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
pub type ComputedValue = SpecifiedValue;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||||
|
${to_rust_ident(values.split()[0])}
|
||||||
|
}
|
||||||
|
pub fn from_component_value(v: &ComponentValue) -> Option<SpecifiedValue> {
|
||||||
|
do get_ident_lower(v).chain |keyword| {
|
||||||
|
match keyword.as_slice() {
|
||||||
|
% for value in values.split():
|
||||||
|
"${value}" => Some(${to_rust_ident(value)}),
|
||||||
|
% endfor
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:single_component_value>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="predefined_type(name, type, initial_value, parse_method='parse', inherited=False)">
|
||||||
|
<%self:longhand name="${name}" inherited="${inherited}">
|
||||||
|
pub use to_computed_value = super::super::common_types::computed::compute_${type};
|
||||||
|
pub type SpecifiedValue = specified::${type};
|
||||||
|
pub type ComputedValue = computed::${type};
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue { ${initial_value} }
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||||
|
one_component_value(input).chain(specified::${type}::${parse_method})
|
||||||
|
}
|
||||||
|
</%self:longhand>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
// CSS 2.1, Section 8 - Box model
|
||||||
|
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
|
||||||
|
"computed::LPA_Length(computed::Length(0))")}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
${predefined_type("padding-" + side, "LengthOrPercentage",
|
||||||
|
"computed::LP_Length(computed::Length(0))",
|
||||||
|
"parse_non_negative")}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
// dotted dashed double groove ridge insed outset hidden
|
||||||
|
${single_keyword("border-top-style", "none solid")}
|
||||||
|
% for side in ["right", "bottom", "left"]:
|
||||||
|
<%self:longhand name="border-${side}-style", no_super="True">
|
||||||
|
pub use super::border_top_style::*;
|
||||||
|
pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
|
||||||
|
pub type ComputedValue = super::border_top_style::ComputedValue;
|
||||||
|
</%self:longhand>
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
pub fn parse_border_width(component_value: &ComponentValue) -> Option<specified::Length> {
|
||||||
|
match component_value {
|
||||||
|
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
|
||||||
|
"thin" => Some(specified::Length::from_px(1.)),
|
||||||
|
"medium" => Some(specified::Length::from_px(3.)),
|
||||||
|
"thick" => Some(specified::Length::from_px(5.)),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
_ => specified::Length::parse_non_negative(component_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
<%self:longhand name="border-${side}-width">
|
||||||
|
pub type SpecifiedValue = specified::Length;
|
||||||
|
pub type ComputedValue = computed::Length;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||||
|
computed::Length(3 * 60) // medium
|
||||||
|
}
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||||
|
one_component_value(input).chain(parse_border_width)
|
||||||
|
}
|
||||||
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||||
|
-> ComputedValue {
|
||||||
|
if context.has_border_${side} { computed::compute_Length(value, context) }
|
||||||
|
else { computed::Length(0) }
|
||||||
|
}
|
||||||
|
</%self:longhand>
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
// CSS 2.1, Section 9 - Visual formatting model
|
||||||
|
|
||||||
|
// TODO: don't parse values we don't support
|
||||||
|
${single_keyword("display",
|
||||||
|
"inline block list-item inline-block none "
|
||||||
|
)}
|
||||||
|
// "table inline-table table-row-group table-header-group table-footer-group "
|
||||||
|
// "table-row table-column-group table-column table-cell table-caption"
|
||||||
|
|
||||||
|
${single_keyword("position", "static absolute relative fixed")}
|
||||||
|
${single_keyword("float", "none left right")}
|
||||||
|
${single_keyword("clear", "none left right both")}
|
||||||
|
|
||||||
|
// CSS 2.1, Section 10 - Visual formatting model details
|
||||||
|
|
||||||
|
${predefined_type("width", "LengthOrPercentageOrAuto",
|
||||||
|
"computed::LPA_Auto",
|
||||||
|
"parse_non_negative")}
|
||||||
|
${predefined_type("height", "LengthOrPercentageOrAuto",
|
||||||
|
"computed::LPA_Auto",
|
||||||
|
"parse_non_negative")}
|
||||||
|
|
||||||
|
<%self:single_component_value name="line-height">
|
||||||
|
pub enum SpecifiedValue {
|
||||||
|
SpecifiedNormal,
|
||||||
|
SpecifiedLength(specified::Length),
|
||||||
|
SpecifiedNumber(Float),
|
||||||
|
// percentage are the same as em.
|
||||||
|
}
|
||||||
|
/// normal | <number> | <length> | <percentage>
|
||||||
|
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||||
|
match input {
|
||||||
|
&ast::Number(ref value) if value.value >= 0.
|
||||||
|
=> Some(SpecifiedNumber(value.value)),
|
||||||
|
&ast::Percentage(ref value) if value.value >= 0.
|
||||||
|
=> Some(SpecifiedLength(specified::Em(value.value))),
|
||||||
|
&Dimension(ref value, ref unit) if value.value >= 0.
|
||||||
|
=> specified::Length::parse_dimension(value.value, unit.as_slice())
|
||||||
|
.map_move(SpecifiedLength),
|
||||||
|
&Ident(ref value) if value.eq_ignore_ascii_case("normal")
|
||||||
|
=> Some(SpecifiedNormal),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub enum ComputedValue {
|
||||||
|
Normal,
|
||||||
|
Length(computed::Length),
|
||||||
|
Number(Float),
|
||||||
|
}
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue { Normal }
|
||||||
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||||
|
-> ComputedValue {
|
||||||
|
match value {
|
||||||
|
SpecifiedNormal => Normal,
|
||||||
|
SpecifiedLength(value) => Length(computed::compute_Length(value, context)),
|
||||||
|
SpecifiedNumber(value) => Number(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:single_component_value>
|
||||||
|
|
||||||
|
// CSS 2.1, Section 11 - Visual effects
|
||||||
|
|
||||||
|
// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
|
||||||
|
|
||||||
|
// CSS 2.1, Section 13 - Paged media
|
||||||
|
|
||||||
|
// CSS 2.1, Section 14 - Colors and Backgrounds
|
||||||
|
|
||||||
|
${predefined_type("background-color", "CSSColor",
|
||||||
|
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
|
||||||
|
${predefined_type("color", "CSSColor",
|
||||||
|
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 1. }) /* black */",
|
||||||
|
inherited=True)}
|
||||||
|
|
||||||
|
// CSS 2.1, Section 15 - Fonts
|
||||||
|
|
||||||
|
<%self:longhand name="font-family" inherited="True">
|
||||||
|
pub use to_computed_value = std::util::id;
|
||||||
|
enum FontFamily {
|
||||||
|
FamilyName(~str),
|
||||||
|
// Generic
|
||||||
|
// Serif,
|
||||||
|
// SansSerif,
|
||||||
|
// Cursive,
|
||||||
|
// Fantasy,
|
||||||
|
// Monospace,
|
||||||
|
}
|
||||||
|
pub type SpecifiedValue = ~[FontFamily];
|
||||||
|
pub type ComputedValue = SpecifiedValue;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue { ~[FamilyName(~"serif")] }
|
||||||
|
/// <familiy-name>#
|
||||||
|
/// <familiy-name> = <string> | [ <ident>+ ]
|
||||||
|
/// TODO: <generic-familiy>
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||||
|
from_iter(input.skip_whitespace())
|
||||||
|
}
|
||||||
|
pub fn from_iter<'a>(mut iter: SkipWhitespaceIterator<'a>) -> Option<SpecifiedValue> {
|
||||||
|
let mut result = ~[];
|
||||||
|
macro_rules! add(
|
||||||
|
($value: expr) => {
|
||||||
|
{
|
||||||
|
result.push($value);
|
||||||
|
match iter.next() {
|
||||||
|
Some(&Comma) => (),
|
||||||
|
None => break 'outer,
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
'outer: loop {
|
||||||
|
match iter.next() {
|
||||||
|
// TODO: avoid copying strings?
|
||||||
|
Some(&String(ref value)) => add!(FamilyName(value.to_owned())),
|
||||||
|
Some(&Ident(ref value)) => {
|
||||||
|
let value = value.as_slice();
|
||||||
|
match value.to_ascii_lower().as_slice() {
|
||||||
|
// "serif" => add!(Serif),
|
||||||
|
// "sans-serif" => add!(SansSerif),
|
||||||
|
// "cursive" => add!(Cursive),
|
||||||
|
// "fantasy" => add!(Fantasy),
|
||||||
|
// "monospace" => add!(Monospace),
|
||||||
|
_ => {
|
||||||
|
let mut idents = ~[value];
|
||||||
|
loop {
|
||||||
|
match iter.next() {
|
||||||
|
Some(&Ident(ref value)) => idents.push(value.as_slice()),
|
||||||
|
Some(&Comma) => {
|
||||||
|
result.push(FamilyName(idents.connect(" ")));
|
||||||
|
break
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
result.push(FamilyName(idents.connect(" ")));
|
||||||
|
break 'outer
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
</%self:longhand>
|
||||||
|
|
||||||
|
|
||||||
|
${single_keyword("font-style", "normal italic oblique", inherited=True)}
|
||||||
|
${single_keyword("font-variant", "normal", inherited=True)} // Add small-caps when supported
|
||||||
|
|
||||||
|
<%self:single_component_value name="font-weight" inherited="True">
|
||||||
|
pub enum SpecifiedValue {
|
||||||
|
Bolder,
|
||||||
|
Lighther,
|
||||||
|
% for weight in range(100, 901, 100):
|
||||||
|
SpecifiedWeight${weight},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
||||||
|
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||||
|
match input {
|
||||||
|
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
|
||||||
|
"bold" => Some(SpecifiedWeight700),
|
||||||
|
"normal" => Some(SpecifiedWeight400),
|
||||||
|
"bolder" => Some(Bolder),
|
||||||
|
"lighter" => Some(Lighther),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
&Number(ref value) => match value.int_value {
|
||||||
|
Some(100) => Some(SpecifiedWeight100),
|
||||||
|
Some(200) => Some(SpecifiedWeight200),
|
||||||
|
Some(300) => Some(SpecifiedWeight300),
|
||||||
|
Some(400) => Some(SpecifiedWeight400),
|
||||||
|
Some(500) => Some(SpecifiedWeight500),
|
||||||
|
Some(600) => Some(SpecifiedWeight600),
|
||||||
|
Some(700) => Some(SpecifiedWeight700),
|
||||||
|
Some(800) => Some(SpecifiedWeight800),
|
||||||
|
Some(900) => Some(SpecifiedWeight900),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub enum ComputedValue {
|
||||||
|
% for weight in range(100, 901, 100):
|
||||||
|
Weight${weight},
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue { Weight400 } // normal
|
||||||
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||||
|
-> ComputedValue {
|
||||||
|
match value {
|
||||||
|
% for weight in range(100, 901, 100):
|
||||||
|
SpecifiedWeight${weight} => Weight${weight},
|
||||||
|
% endfor
|
||||||
|
Bolder => match context.font_weight {
|
||||||
|
Weight100 => Weight400,
|
||||||
|
Weight200 => Weight400,
|
||||||
|
Weight300 => Weight400,
|
||||||
|
Weight400 => Weight700,
|
||||||
|
Weight500 => Weight700,
|
||||||
|
Weight600 => Weight900,
|
||||||
|
Weight700 => Weight900,
|
||||||
|
Weight800 => Weight900,
|
||||||
|
Weight900 => Weight900,
|
||||||
|
},
|
||||||
|
Lighther => match context.font_weight {
|
||||||
|
Weight100 => Weight100,
|
||||||
|
Weight200 => Weight100,
|
||||||
|
Weight300 => Weight100,
|
||||||
|
Weight400 => Weight100,
|
||||||
|
Weight500 => Weight100,
|
||||||
|
Weight600 => Weight400,
|
||||||
|
Weight700 => Weight400,
|
||||||
|
Weight800 => Weight700,
|
||||||
|
Weight900 => Weight700,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:single_component_value>
|
||||||
|
|
||||||
|
<%self:single_component_value name="font-size" inherited="True">
|
||||||
|
pub use to_computed_value = super::super::common_types::computed::compute_Length;
|
||||||
|
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
|
||||||
|
pub type ComputedValue = computed::Length;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||||
|
computed::Length(16 * 60) // medium
|
||||||
|
}
|
||||||
|
/// <length> | <percentage>
|
||||||
|
/// TODO: support <absolute-size> and <relative-size>
|
||||||
|
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||||
|
do specified::LengthOrPercentage::parse_non_negative(input).map_move |value| {
|
||||||
|
match value {
|
||||||
|
specified::LP_Length(value) => value,
|
||||||
|
specified::LP_Percentage(value) => specified::Em(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:single_component_value>
|
||||||
|
|
||||||
|
// CSS 2.1, Section 16 - Text
|
||||||
|
|
||||||
|
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
|
||||||
|
${single_keyword("text-align", "left right center justify", inherited=True)}
|
||||||
|
|
||||||
|
<%self:longhand name="text-decoration">
|
||||||
|
pub use to_computed_value = std::util::id;
|
||||||
|
pub struct SpecifiedValue {
|
||||||
|
underline: bool,
|
||||||
|
overline: bool,
|
||||||
|
line_through: bool,
|
||||||
|
// 'blink' is accepted in the parser but ignored.
|
||||||
|
// Just not blinking the text is a conforming implementation per CSS 2.1.
|
||||||
|
}
|
||||||
|
pub type ComputedValue = SpecifiedValue;
|
||||||
|
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||||
|
SpecifiedValue { underline: false, overline: false, line_through: false } // none
|
||||||
|
}
|
||||||
|
/// none | [ underline || overline || line-through || blink ]
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||||
|
let mut result = SpecifiedValue {
|
||||||
|
underline: false, overline: false, line_through: false,
|
||||||
|
};
|
||||||
|
let mut blink = false;
|
||||||
|
let mut empty = true;
|
||||||
|
for component_value in input.skip_whitespace() {
|
||||||
|
match get_ident_lower(component_value) {
|
||||||
|
None => return None,
|
||||||
|
Some(keyword) => match keyword.as_slice() {
|
||||||
|
"underline" => if result.underline { return None }
|
||||||
|
else { empty = false; result.underline = true },
|
||||||
|
"overline" => if result.overline { return None }
|
||||||
|
else { empty = false; result.overline = true },
|
||||||
|
"line-through" => if result.line_through { return None }
|
||||||
|
else { empty = false; result.line_through = true },
|
||||||
|
"blink" => if blink { return None }
|
||||||
|
else { empty = false; blink = true },
|
||||||
|
"none" => return if empty { Some(result) } else { None },
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !empty { Some(result) } else { None }
|
||||||
|
}
|
||||||
|
</%self:longhand>
|
||||||
|
|
||||||
|
// CSS 2.1, Section 17 - Tables
|
||||||
|
|
||||||
|
// CSS 2.1, Section 18 - User interface
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub mod shorthands {
|
||||||
|
pub use super::*;
|
||||||
|
pub use super::longhands::*;
|
||||||
|
|
||||||
|
<%def name="shorthand(name, sub_properties)">
|
||||||
|
<%
|
||||||
|
shorthand = Shorthand(name, sub_properties.split())
|
||||||
|
SHORTHANDS.append(shorthand)
|
||||||
|
%>
|
||||||
|
pub mod ${shorthand.ident} {
|
||||||
|
use super::*;
|
||||||
|
struct Longhands {
|
||||||
|
% for sub_property in shorthand.sub_properties:
|
||||||
|
${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<Longhands> {
|
||||||
|
${caller.body()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)">
|
||||||
|
<%self:shorthand name="${name}" sub_properties="${
|
||||||
|
' '.join(sub_property_pattern % side
|
||||||
|
for side in ['top', 'right', 'bottom', 'left'])}">
|
||||||
|
let mut iter = input.skip_whitespace().map(${parser_function});
|
||||||
|
// zero or more than four values is invalid.
|
||||||
|
// one value sets them all
|
||||||
|
// two values set (top, bottom) and (left, right)
|
||||||
|
// three values set top, (left, right) and bottom
|
||||||
|
// four values set them in order
|
||||||
|
let top = iter.next().unwrap_or_default(None);
|
||||||
|
let right = iter.next().unwrap_or_default(top);
|
||||||
|
let bottom = iter.next().unwrap_or_default(top);
|
||||||
|
let left = iter.next().unwrap_or_default(right);
|
||||||
|
if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
|
||||||
|
&& iter.next().is_none() {
|
||||||
|
Some(Longhands {
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
${to_rust_ident(sub_property_pattern % side)}: ${side},
|
||||||
|
% endfor
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
</%self:shorthand>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: other background-* properties
|
||||||
|
<%self:shorthand name="background" sub_properties="background-color">
|
||||||
|
do one_component_value(input).chain(specified::CSSColor::parse).map_move |color| {
|
||||||
|
Longhands { background_color: Some(color) }
|
||||||
|
}
|
||||||
|
</%self:shorthand>
|
||||||
|
|
||||||
|
${four_sides_shorthand("border-color", "border-%s-color", "specified::CSSColor::parse")}
|
||||||
|
${four_sides_shorthand("border-style", "border-%s-style",
|
||||||
|
"border_top_style::from_component_value")}
|
||||||
|
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
|
||||||
|
|
||||||
|
pub fn parse_border(input: &[ComponentValue])
|
||||||
|
-> Option<(Option<specified::CSSColor>,
|
||||||
|
Option<border_top_style::SpecifiedValue>,
|
||||||
|
Option<specified::Length>)> {
|
||||||
|
let mut color = None;
|
||||||
|
let mut style = None;
|
||||||
|
let mut width = None;
|
||||||
|
let mut any = false;
|
||||||
|
for component_value in input.skip_whitespace() {
|
||||||
|
if color.is_none() {
|
||||||
|
match specified::CSSColor::parse(component_value) {
|
||||||
|
Some(c) => { color = Some(c); any = true; loop },
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if style.is_none() {
|
||||||
|
match border_top_style::from_component_value(component_value) {
|
||||||
|
Some(s) => { style = Some(s); any = true; loop },
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if width.is_none() {
|
||||||
|
match parse_border_width(component_value) {
|
||||||
|
Some(w) => { width = Some(w); any = true; loop },
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
if any { Some((color, style, width)) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
<%self:shorthand name="border-${side}" sub_properties="${' '.join(
|
||||||
|
'border-%s-%s' % (side, prop)
|
||||||
|
for prop in ['color', 'style', 'width']
|
||||||
|
)}">
|
||||||
|
do parse_border(input).map_move |(color, style, width)| {
|
||||||
|
Longhands {
|
||||||
|
% for prop in ["color", "style", "width"]:
|
||||||
|
${"border_%s_%s: %s," % (side, prop, prop)}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:shorthand>
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
<%self:shorthand name="border" sub_properties="${' '.join(
|
||||||
|
'border-%s-%s' % (side, prop)
|
||||||
|
for side in ['top', 'right', 'bottom', 'left']
|
||||||
|
for prop in ['color', 'style', 'width']
|
||||||
|
)}">
|
||||||
|
do parse_border(input).map_move |(color, style, width)| {
|
||||||
|
Longhands {
|
||||||
|
% for side in ["top", "right", "bottom", "left"]:
|
||||||
|
% for prop in ["color", "style", "width"]:
|
||||||
|
${"border_%s_%s: %s," % (side, prop, prop)}
|
||||||
|
% endfor
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%self:shorthand>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct PropertyDeclarationBlock {
|
||||||
|
important: ~[PropertyDeclaration],
|
||||||
|
normal: ~[PropertyDeclaration],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlock {
|
||||||
|
let mut important = ~[];
|
||||||
|
let mut normal = ~[];
|
||||||
|
for item in ErrorLoggerIterator(parse_declaration_list(input.move_iter())) {
|
||||||
|
match item {
|
||||||
|
Decl_AtRule(rule) => log_css_error(
|
||||||
|
rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)),
|
||||||
|
Declaration(Declaration{ location: l, name: n, value: v, important: i}) => {
|
||||||
|
let list = if i { &mut important } else { &mut normal };
|
||||||
|
if !PropertyDeclaration::parse(n, v, list) {
|
||||||
|
log_css_error(l, "Invalid property declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyDeclarationBlock { important: important, normal: normal }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum CSSWideKeyword {
|
||||||
|
Initial,
|
||||||
|
Inherit,
|
||||||
|
Unset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSSWideKeyword {
|
||||||
|
pub fn parse(input: &[ComponentValue]) -> Option<CSSWideKeyword> {
|
||||||
|
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
|
||||||
|
match keyword.as_slice() {
|
||||||
|
"initial" => Some(Initial),
|
||||||
|
"inherit" => Some(Inherit),
|
||||||
|
"unset" => Some(Unset),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DeclaredValue<T> {
|
||||||
|
SpecifiedValue(T),
|
||||||
|
CSSWideKeyword(CSSWideKeyword),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PropertyDeclaration {
|
||||||
|
% for property in LONGHANDS:
|
||||||
|
${property.ident}_declaration(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropertyDeclaration {
|
||||||
|
pub fn parse(name: &str, value: &[ComponentValue],
|
||||||
|
result_list: &mut ~[PropertyDeclaration]) -> bool {
|
||||||
|
match name.to_ascii_lower().as_slice() {
|
||||||
|
% for property in LONGHANDS:
|
||||||
|
"${property.name}" => result_list.push(${property.ident}_declaration(
|
||||||
|
match CSSWideKeyword::parse(value) {
|
||||||
|
Some(keyword) => CSSWideKeyword(keyword),
|
||||||
|
None => match longhands::${property.ident}::parse(value) {
|
||||||
|
Some(value) => SpecifiedValue(value),
|
||||||
|
None => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
% endfor
|
||||||
|
% for shorthand in SHORTHANDS:
|
||||||
|
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
|
||||||
|
Some(keyword) => {
|
||||||
|
% for sub_property in shorthand.sub_properties:
|
||||||
|
result_list.push(${sub_property.ident}_declaration(
|
||||||
|
CSSWideKeyword(keyword)
|
||||||
|
));
|
||||||
|
% endfor
|
||||||
|
},
|
||||||
|
None => match shorthands::${shorthand.ident}::parse(value) {
|
||||||
|
Some(result) => {
|
||||||
|
% for sub_property in shorthand.sub_properties:
|
||||||
|
result_list.push(${sub_property.ident}_declaration(
|
||||||
|
match result.${sub_property.ident} {
|
||||||
|
Some(value) => SpecifiedValue(value),
|
||||||
|
None => CSSWideKeyword(Initial),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
% endfor
|
||||||
|
},
|
||||||
|
None => return false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
% endfor
|
||||||
|
_ => return false, // Unknown property
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
482
src/components/script/style/selectors.rs
Normal file
482
src/components/script/style/selectors.rs
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::{vec, iterator};
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use cssparser::*;
|
||||||
|
use namespaces::NamespaceMap;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Selector {
|
||||||
|
compound_selectors: CompoundSelector,
|
||||||
|
pseudo_element: Option<PseudoElement>,
|
||||||
|
specificity: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31;
|
||||||
|
|
||||||
|
|
||||||
|
pub enum PseudoElement {
|
||||||
|
Before,
|
||||||
|
After,
|
||||||
|
FirstLine,
|
||||||
|
FirstLetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct CompoundSelector {
|
||||||
|
simple_selectors: ~[SimpleSelector],
|
||||||
|
next: Option<(~CompoundSelector, Combinator)>, // c.next is left of c
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Combinator {
|
||||||
|
Child, // >
|
||||||
|
Descendant, // space
|
||||||
|
NextSibling, // +
|
||||||
|
LaterSibling, // ~
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SimpleSelector {
|
||||||
|
IDSelector(~str),
|
||||||
|
ClassSelector(~str),
|
||||||
|
LocalNameSelector{lowercase_name: ~str, cased_name: ~str},
|
||||||
|
NamespaceSelector(~str),
|
||||||
|
|
||||||
|
// Attribute selectors
|
||||||
|
AttrExists(AttrSelector), // [foo]
|
||||||
|
AttrEqual(AttrSelector, ~str), // [foo=bar]
|
||||||
|
AttrIncludes(AttrSelector, ~str), // [foo~=bar]
|
||||||
|
AttrDashMatch(AttrSelector, ~str), // [foo|=bar]
|
||||||
|
AttrPrefixMatch(AttrSelector, ~str), // [foo^=bar]
|
||||||
|
AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar]
|
||||||
|
AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar]
|
||||||
|
|
||||||
|
// Pseudo-classes
|
||||||
|
Empty,
|
||||||
|
Root,
|
||||||
|
Lang(~str),
|
||||||
|
NthChild(i32, i32),
|
||||||
|
Negation(~[SimpleSelector]),
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttrSelector {
|
||||||
|
lowercase_name: ~str,
|
||||||
|
cased_name: ~str,
|
||||||
|
namespace: Option<~str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Iter = iterator::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>>;
|
||||||
|
|
||||||
|
|
||||||
|
// None means invalid selector
|
||||||
|
pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
|
-> Option<~[Selector]> {
|
||||||
|
let iter = &mut input.move_iter().peekable();
|
||||||
|
let first = match parse_selector(iter, namespaces) {
|
||||||
|
None => return None,
|
||||||
|
Some(result) => result
|
||||||
|
};
|
||||||
|
let mut results = ~[first];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
skip_whitespace(iter);
|
||||||
|
match iter.peek() {
|
||||||
|
None => break, // EOF
|
||||||
|
Some(&Comma) => (),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
match parse_selector(iter, namespaces) {
|
||||||
|
Some(selector) => results.push(selector),
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// None means invalid selector
|
||||||
|
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
|
-> Option<Selector> {
|
||||||
|
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
|
||||||
|
None => return None,
|
||||||
|
Some(result) => result
|
||||||
|
};
|
||||||
|
let mut compound = CompoundSelector{ simple_selectors: first, next: None };
|
||||||
|
let mut pseudo_element = pseudo_element;
|
||||||
|
|
||||||
|
while pseudo_element.is_none() {
|
||||||
|
let any_whitespace = skip_whitespace(iter);
|
||||||
|
let combinator = match iter.peek() {
|
||||||
|
None => break, // EOF
|
||||||
|
Some(&Delim('>')) => { iter.next(); Child },
|
||||||
|
Some(&Delim('+')) => { iter.next(); NextSibling },
|
||||||
|
Some(&Delim('~')) => { iter.next(); LaterSibling },
|
||||||
|
Some(_) => {
|
||||||
|
if any_whitespace { Descendant }
|
||||||
|
else { return None }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match parse_simple_selectors(iter, namespaces) {
|
||||||
|
None => return None,
|
||||||
|
Some((simple_selectors, pseudo)) => {
|
||||||
|
compound = CompoundSelector {
|
||||||
|
simple_selectors: simple_selectors,
|
||||||
|
next: Some((~compound, combinator))
|
||||||
|
};
|
||||||
|
pseudo_element = pseudo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let selector = Selector{
|
||||||
|
specificity: compute_specificity(&compound, &pseudo_element),
|
||||||
|
compound_selectors: compound,
|
||||||
|
pseudo_element: pseudo_element,
|
||||||
|
};
|
||||||
|
Some(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn compute_specificity(mut selector: &CompoundSelector,
|
||||||
|
pseudo_element: &Option<PseudoElement>) -> u32 {
|
||||||
|
struct Specificity {
|
||||||
|
id_selectors: u32,
|
||||||
|
class_like_selectors: u32,
|
||||||
|
element_selectors: u32,
|
||||||
|
}
|
||||||
|
let mut specificity = Specificity {
|
||||||
|
id_selectors: 0,
|
||||||
|
class_like_selectors: 0,
|
||||||
|
element_selectors: 0,
|
||||||
|
};
|
||||||
|
if pseudo_element.is_some() { specificity.element_selectors += 1 }
|
||||||
|
|
||||||
|
simple_selectors_specificity(selector.simple_selectors, &mut specificity);
|
||||||
|
loop {
|
||||||
|
match selector.next {
|
||||||
|
None => break,
|
||||||
|
Some((ref next_selector, _)) => {
|
||||||
|
selector = &**next_selector;
|
||||||
|
simple_selectors_specificity(selector.simple_selectors, &mut specificity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_selectors_specificity(simple_selectors: &[SimpleSelector],
|
||||||
|
specificity: &mut Specificity) {
|
||||||
|
for simple_selector in simple_selectors.iter() {
|
||||||
|
match simple_selector {
|
||||||
|
&LocalNameSelector{_} => specificity.element_selectors += 1,
|
||||||
|
&IDSelector(*) => specificity.id_selectors += 1,
|
||||||
|
&ClassSelector(*)
|
||||||
|
| &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*)
|
||||||
|
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
|
||||||
|
| &Empty | &Root | &Lang(*) | &NthChild(*)
|
||||||
|
=> specificity.class_like_selectors += 1,
|
||||||
|
&NamespaceSelector(*) => (),
|
||||||
|
&Negation(ref negated)
|
||||||
|
=> simple_selectors_specificity(negated.as_slice(), specificity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MAX_10BIT: u32 = (1u32 << 10) - 1;
|
||||||
|
specificity.id_selectors.min(&MAX_10BIT) << 20
|
||||||
|
| specificity.class_like_selectors.min(&MAX_10BIT) << 10
|
||||||
|
| specificity.id_selectors.min(&MAX_10BIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// None means invalid selector
|
||||||
|
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
|
-> Option<(~[SimpleSelector], Option<PseudoElement>)> {
|
||||||
|
let mut empty = true;
|
||||||
|
let mut simple_selectors = match parse_type_selector(iter, namespaces) {
|
||||||
|
None => return None, // invalid selector
|
||||||
|
Some(None) => ~[],
|
||||||
|
Some(Some(s)) => { empty = false; s }
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pseudo_element = None;
|
||||||
|
loop {
|
||||||
|
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false) {
|
||||||
|
None => return None, // invalid selector
|
||||||
|
Some(None) => break,
|
||||||
|
Some(Some(Left(s))) => simple_selectors.push(s),
|
||||||
|
Some(Some(Right(p))) => { pseudo_element = Some(p); break },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if empty { None } // An empty selector is invalid
|
||||||
|
else { Some((simple_selectors, pseudo_element)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// None means invalid selector
|
||||||
|
// Some(None) means no type selector
|
||||||
|
// Some(Some([...])) is a type selector. Might be empty for *|*
|
||||||
|
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||||
|
-> Option<Option<~[SimpleSelector]>> {
|
||||||
|
skip_whitespace(iter);
|
||||||
|
match parse_qualified_name(iter, /* allow_universal = */ true, namespaces) {
|
||||||
|
None => None, // invalid selector
|
||||||
|
Some(None) => Some(None),
|
||||||
|
Some(Some((namespace, local_name))) => {
|
||||||
|
let mut simple_selectors = ~[];
|
||||||
|
match namespace {
|
||||||
|
Some(url) => simple_selectors.push(NamespaceSelector(url)),
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
match local_name {
|
||||||
|
Some(name) => simple_selectors.push(LocalNameSelector{
|
||||||
|
lowercase_name: name.to_ascii_lower(),
|
||||||
|
cased_name: name,
|
||||||
|
}),
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
Some(Some(simple_selectors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse a simple selector other than a type selector
|
||||||
|
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
|
||||||
|
-> Option<Option<Either<SimpleSelector, PseudoElement>>> {
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&IDHash(_)) => match iter.next() {
|
||||||
|
Some(IDHash(id)) => Some(Some(Left(IDSelector(id)))),
|
||||||
|
_ => fail!("Implementation error, this should not happen."),
|
||||||
|
},
|
||||||
|
Some(&Delim('.')) => {
|
||||||
|
iter.next();
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(class)) => Some(Some(Left(ClassSelector(class)))),
|
||||||
|
_ => None, // invalid selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&SquareBracketBlock(_)) => match iter.next() {
|
||||||
|
Some(SquareBracketBlock(content))
|
||||||
|
=> match parse_attribute_selector(content, namespaces) {
|
||||||
|
None => None,
|
||||||
|
Some(simple_selector) => Some(Some(Left(simple_selector))),
|
||||||
|
},
|
||||||
|
_ => fail!("Implementation error, this should not happen."),
|
||||||
|
},
|
||||||
|
Some(&Delim(':')) => {
|
||||||
|
iter.next();
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(name)) => match parse_simple_pseudo_class(name) {
|
||||||
|
None => None,
|
||||||
|
Some(result) => Some(Some(result)),
|
||||||
|
},
|
||||||
|
Some(Function(name, arguments)) => match parse_functional_pseudo_class(
|
||||||
|
name, arguments, namespaces, inside_negation) {
|
||||||
|
None => None,
|
||||||
|
Some(simple_selector) => Some(Some(Left(simple_selector))),
|
||||||
|
},
|
||||||
|
Some(Delim(':')) => {
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(name)) => match parse_pseudo_element(name) {
|
||||||
|
Some(pseudo_element) => Some(Some(Right(pseudo_element))),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Some(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// None means invalid selector
|
||||||
|
// Some(None) means not a qualified name
|
||||||
|
// Some(Some((None, None)) means *|*
|
||||||
|
// Some(Some((Some(url), None)) means prefix|*
|
||||||
|
// Some(Some((None, Some(name)) means *|name
|
||||||
|
// Some(Some((Some(url), Some(name))) means prefix|name
|
||||||
|
// ... or equivalent
|
||||||
|
fn parse_qualified_name(iter: &mut Iter, allow_universal: bool, namespaces: &NamespaceMap)
|
||||||
|
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||||
|
#[inline]
|
||||||
|
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<~str>)
|
||||||
|
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||||
|
match namespaces.default {
|
||||||
|
None => Some(Some((None, local_name))),
|
||||||
|
Some(ref url) => Some(Some((Some(url.to_owned()), local_name))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn explicit_namespace(iter: &mut Iter, allow_universal: bool, namespace_url: Option<~str>)
|
||||||
|
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||||
|
assert!(iter.next() == Some(Delim('|')));
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&Delim('*')) if allow_universal => {
|
||||||
|
iter.next();
|
||||||
|
Some(Some((namespace_url, None)))
|
||||||
|
},
|
||||||
|
Some(&Ident(_)) => {
|
||||||
|
let local_name = get_next_ident(iter);
|
||||||
|
Some(Some((namespace_url, Some(local_name))))
|
||||||
|
},
|
||||||
|
_ => None, // invalid selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&Ident(_)) => {
|
||||||
|
let value = get_next_ident(iter);
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&Delim('|')) => default_namespace(namespaces, Some(value)),
|
||||||
|
_ => {
|
||||||
|
let namespace_url = match namespaces.prefix_map.find(&value) {
|
||||||
|
None => return None, // Undeclared namespace prefix: invalid selector
|
||||||
|
Some(ref url) => url.to_owned(),
|
||||||
|
};
|
||||||
|
explicit_namespace(iter, allow_universal, Some(namespace_url))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(&Delim('*')) => {
|
||||||
|
iter.next(); // Consume '*'
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&Delim('|')) => {
|
||||||
|
if allow_universal { default_namespace(namespaces, None) }
|
||||||
|
else { None }
|
||||||
|
},
|
||||||
|
_ => explicit_namespace(iter, allow_universal, None),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, Some(~"")),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
|
-> Option<SimpleSelector> {
|
||||||
|
let iter = &mut content.move_iter().peekable();
|
||||||
|
let attr = match parse_qualified_name(iter, /* allow_universal = */ false, namespaces) {
|
||||||
|
None => return None, // invalid selector
|
||||||
|
Some(None) => return None,
|
||||||
|
Some(Some((_, None))) => fail!("Implementation error, this should not happen."),
|
||||||
|
Some(Some((namespace, Some(local_name)))) => AttrSelector {
|
||||||
|
namespace: namespace,
|
||||||
|
lowercase_name: local_name.to_ascii_lower(),
|
||||||
|
cased_name: local_name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
skip_whitespace(iter);
|
||||||
|
macro_rules! get_value( () => {{
|
||||||
|
skip_whitespace(iter);
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(value)) | Some(String(value)) => value,
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}};)
|
||||||
|
let result = match iter.next() {
|
||||||
|
None => AttrExists(attr), // [foo]
|
||||||
|
Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar]
|
||||||
|
Some(IncludeMatch) => AttrIncludes(attr, get_value!()), // [foo~=bar]
|
||||||
|
Some(DashMatch) => AttrDashMatch(attr, get_value!()), // [foo|=bar]
|
||||||
|
Some(PrefixMatch) => AttrPrefixMatch(attr, get_value!()), // [foo^=bar]
|
||||||
|
Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar]
|
||||||
|
Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar]
|
||||||
|
_ => return None
|
||||||
|
};
|
||||||
|
skip_whitespace(iter);
|
||||||
|
if iter.next().is_none() { Some(result) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_simple_pseudo_class(name: ~str) -> Option<Either<SimpleSelector, PseudoElement>> {
|
||||||
|
match name.to_ascii_lower().as_slice() {
|
||||||
|
"root" => Some(Left(Root)),
|
||||||
|
"empty" => Some(Left(Empty)),
|
||||||
|
|
||||||
|
// Supported CSS 2.1 pseudo-elements only.
|
||||||
|
"before" => Some(Right(Before)),
|
||||||
|
"after" => Some(Right(After)),
|
||||||
|
"first-line" => Some(Right(FirstLine)),
|
||||||
|
"first-letter" => Some(Right(FirstLetter)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue],
|
||||||
|
namespaces: &NamespaceMap, inside_negation: bool)
|
||||||
|
-> Option<SimpleSelector> {
|
||||||
|
match name.to_ascii_lower().as_slice() {
|
||||||
|
"lang" => parse_lang(arguments),
|
||||||
|
"nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)),
|
||||||
|
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_pseudo_element(name: ~str) -> Option<PseudoElement> {
|
||||||
|
match name.to_ascii_lower().as_slice() {
|
||||||
|
// All supported pseudo-elements
|
||||||
|
"before" => Some(Before),
|
||||||
|
"after" => Some(After),
|
||||||
|
"first-line" => Some(FirstLine),
|
||||||
|
"first-letter" => Some(FirstLetter),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_lang(arguments: ~[ComponentValue]) -> Option<SimpleSelector> {
|
||||||
|
let mut iter = arguments.move_skip_whitespace();
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(value)) => {
|
||||||
|
if "" == value || iter.next().is_some() { None }
|
||||||
|
else { Some(Lang(value)) }
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Level 3: Parse ONE simple_selector
|
||||||
|
fn parse_negation(arguments: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||||
|
-> Option<SimpleSelector> {
|
||||||
|
let iter = &mut arguments.move_iter().peekable();
|
||||||
|
Some(Negation(match parse_type_selector(iter, namespaces) {
|
||||||
|
None => return None, // invalid selector
|
||||||
|
Some(Some(s)) => s,
|
||||||
|
Some(None) => {
|
||||||
|
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true) {
|
||||||
|
Some(Some(Left(s))) => ~[s],
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Assuming the next token is an ident, consume it and return its value
|
||||||
|
#[inline]
|
||||||
|
fn get_next_ident(iter: &mut Iter) -> ~str {
|
||||||
|
match iter.next() {
|
||||||
|
Some(Ident(value)) => value,
|
||||||
|
_ => fail!("Implementation error, this should not happen."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn skip_whitespace(iter: &mut Iter) -> bool {
|
||||||
|
let mut any_whitespace = false;
|
||||||
|
loop {
|
||||||
|
if iter.peek() != Some(&WhiteSpace) { return any_whitespace }
|
||||||
|
any_whitespace = true;
|
||||||
|
iter.next();
|
||||||
|
}
|
||||||
|
}
|
17
src/components/script/style/servo-style.rc
Normal file
17
src/components/script/style/servo-style.rc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#[link(name = "servo-style", vers = "0.1")];
|
||||||
|
#[crate_type = "lib"];
|
||||||
|
|
||||||
|
extern mod extra;
|
||||||
|
extern mod cssparser;
|
||||||
|
|
||||||
|
pub mod stylesheets;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod selectors;
|
||||||
|
pub mod properties;
|
||||||
|
pub mod namespaces;
|
||||||
|
pub mod media_queries;
|
||||||
|
pub mod parsing_utils;
|
152
src/components/script/style/stylesheets.rs
Normal file
152
src/components/script/style/stylesheets.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::iterator::Iterator;
|
||||||
|
use std::ascii::StrAsciiExt;
|
||||||
|
use cssparser::*;
|
||||||
|
use selectors;
|
||||||
|
use properties;
|
||||||
|
use errors::{ErrorLoggerIterator, log_css_error};
|
||||||
|
use namespaces::{NamespaceMap, parse_namespace_rule};
|
||||||
|
use media_queries::{MediaRule, parse_media_rule};
|
||||||
|
use media_queries;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Stylesheet {
|
||||||
|
rules: ~[CSSRule],
|
||||||
|
namespaces: NamespaceMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum CSSRule {
|
||||||
|
CSSStyleRule(StyleRule),
|
||||||
|
CSSMediaRule(MediaRule),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct StyleRule {
|
||||||
|
selectors: ~[selectors::Selector],
|
||||||
|
declarations: properties::PropertyDeclarationBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_stylesheet(css: &str) -> Stylesheet {
|
||||||
|
static STATE_CHARSET: uint = 1;
|
||||||
|
static STATE_IMPORTS: uint = 2;
|
||||||
|
static STATE_NAMESPACES: uint = 3;
|
||||||
|
static STATE_BODY: uint = 4;
|
||||||
|
let mut state: uint = STATE_CHARSET;
|
||||||
|
|
||||||
|
let mut rules = ~[];
|
||||||
|
let mut namespaces = NamespaceMap::new();
|
||||||
|
|
||||||
|
for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) {
|
||||||
|
let next_state; // Unitialized to force each branch to set it.
|
||||||
|
match rule {
|
||||||
|
QualifiedRule(rule) => {
|
||||||
|
next_state = STATE_BODY;
|
||||||
|
parse_style_rule(rule, &mut rules, &namespaces)
|
||||||
|
},
|
||||||
|
AtRule(rule) => {
|
||||||
|
let lower_name = rule.name.to_ascii_lower();
|
||||||
|
match lower_name.as_slice() {
|
||||||
|
"charset" => {
|
||||||
|
if state > STATE_CHARSET {
|
||||||
|
log_css_error(rule.location, "@charset must be the first rule")
|
||||||
|
}
|
||||||
|
// Valid @charset rules are just ignored
|
||||||
|
next_state = STATE_IMPORTS;
|
||||||
|
},
|
||||||
|
"import" => {
|
||||||
|
if state > STATE_IMPORTS {
|
||||||
|
next_state = state;
|
||||||
|
log_css_error(rule.location,
|
||||||
|
"@import must be before any rule but @charset")
|
||||||
|
} else {
|
||||||
|
next_state = STATE_IMPORTS;
|
||||||
|
log_css_error(rule.location, "@import is not supported yet") // TODO
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"namespace" => {
|
||||||
|
if state > STATE_NAMESPACES {
|
||||||
|
next_state = state;
|
||||||
|
log_css_error(
|
||||||
|
rule.location,
|
||||||
|
"@namespace must be before any rule but @charset and @import"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
next_state = STATE_NAMESPACES;
|
||||||
|
parse_namespace_rule(rule, &mut namespaces)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
next_state = STATE_BODY;
|
||||||
|
parse_nested_at_rule(lower_name, rule, &mut rules, &namespaces)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
state = next_state;
|
||||||
|
}
|
||||||
|
Stylesheet{ rules: rules, namespaces: namespaces }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut ~[CSSRule],
|
||||||
|
namespaces: &NamespaceMap) {
|
||||||
|
let QualifiedRule{location: location, prelude: prelude, block: block} = rule;
|
||||||
|
match selectors::parse_selector_list(prelude, namespaces) {
|
||||||
|
Some(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
|
||||||
|
selectors: selectors,
|
||||||
|
declarations: properties::parse_property_declaration_list(block)
|
||||||
|
})),
|
||||||
|
None => log_css_error(location, "Unsupported CSS selector."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// lower_name is passed explicitly to avoid computing it twice.
|
||||||
|
pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
|
||||||
|
parent_rules: &mut ~[CSSRule], namespaces: &NamespaceMap) {
|
||||||
|
match lower_name {
|
||||||
|
"media" => parse_media_rule(rule, parent_rules, namespaces),
|
||||||
|
_ => log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", lower_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Stylesheet {
|
||||||
|
fn iter_style_rules<'a>(&'a self, device: &'a media_queries::Device) -> StyleRuleIterator<'a> {
|
||||||
|
StyleRuleIterator { device: device, stack: ~[(self.rules.as_slice(), 0)] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StyleRuleIterator<'self> {
|
||||||
|
device: &'self media_queries::Device,
|
||||||
|
// FIXME: I couldn’t get this to borrow-check with a stack of VecIterator
|
||||||
|
stack: ~[(&'self [CSSRule], uint)],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> Iterator<&'self StyleRule> for StyleRuleIterator<'self> {
|
||||||
|
fn next(&mut self) -> Option<&'self StyleRule> {
|
||||||
|
loop {
|
||||||
|
match self.stack.pop_opt() {
|
||||||
|
None => return None,
|
||||||
|
Some((rule_list, i)) => {
|
||||||
|
if i + 1 < rule_list.len() {
|
||||||
|
self.stack.push((rule_list, i + 1))
|
||||||
|
}
|
||||||
|
match rule_list[i] {
|
||||||
|
CSSStyleRule(ref rule) => return Some(rule),
|
||||||
|
CSSMediaRule(ref rule) => {
|
||||||
|
if rule.media_queries.evaluate(self.device) {
|
||||||
|
self.stack.push((rule.rules.as_slice(), 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue