servo/ports/geckolib/glue.rs
author Emilio Cobos Álvarez <emilio@crisal.io>
Tue, 21 Aug 2018 17:34:21 +0200
changeset 432762 ab61273bea17b941baca4f3f85b6ca0362d56e22
parent 431382 4ef0f163fdeb9afeddd87b37bfd987298c038542
child 433528 5449572186472bfc8ed0a48e6b08e6bc4e2424a6
permissions -rw-r--r--
Bug 1485044: Remove useless StyleDisplay conversion. r=heycam Differential Revision: https://phabricator.services.mozilla.com/D3896

/* 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::{ParseErrorKind, Parser, ParserInput, SourceLocation};
use cssparser::ToCss as ParserToCss;
use malloc_size_of::MallocSizeOfOps;
use nsstring::{nsCString, nsStringRepr};
use selectors::{NthIndexCache, SelectorList};
use selectors::matching::{MatchingContext, MatchingMode, matches_selector};
use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
use smallvec::SmallVec;
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::fmt::Write;
use std::iter;
use std::mem;
use std::os::raw::c_void;
use std::ptr;
use style::applicable_declarations::ApplicableDeclarationBlock;
use style::author_styles::AuthorStyles;
use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
use style::context::ThreadLocalStyleContext;
use style::counter_style;
use style::data::{ElementStyles, self};
use style::dom::{ShowSubtreeData, TDocument, TElement, TNode};
use style::driver;
use style::element_state::{DocumentState, ElementState};
use style::error_reporting::{ContextualParseError, ParseErrorReporter};
use style::font_metrics::{FontMetricsProvider, get_metrics_provider_for_product};
use style::gecko::data::{GeckoStyleSheet, PerDocumentStyleData, PerDocumentStyleDataImpl};
use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData, STYLE_THREAD_POOL};
use style::gecko::restyle_damage::GeckoRestyleDamage;
use style::gecko::selector_parser::{NonTSPseudoClass, PseudoElement};
use style::gecko::traversal::RecalcStyleOnly;
use style::gecko::wrapper::{GeckoElement, GeckoNode};
use style::gecko_bindings::bindings;
use style::gecko_bindings::bindings::{RawGeckoElementBorrowed, RawGeckoElementBorrowedOrNull, RawGeckoNodeBorrowed};
use style::gecko_bindings::bindings::{RawGeckoKeyframeListBorrowed, RawGeckoKeyframeListBorrowedMut};
use style::gecko_bindings::bindings::{RawServoAuthorStyles, RawServoAuthorStylesBorrowed};
use style::gecko_bindings::bindings::{RawServoAuthorStylesBorrowedMut, RawServoAuthorStylesOwned};
use style::gecko_bindings::bindings::{RawServoCounterStyleRule, RawServoCounterStyleRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoDeclarationBlockBorrowed, RawServoDeclarationBlockStrong};
use style::gecko_bindings::bindings::{RawServoFontFaceRuleBorrowed, RawServoFontFaceRuleStrong};
use style::gecko_bindings::bindings::{RawServoFontFeatureValuesRule, RawServoFontFeatureValuesRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoImportRule, RawServoImportRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoKeyframe, RawServoKeyframeBorrowed, RawServoKeyframeStrong};
use style::gecko_bindings::bindings::{RawServoKeyframesRule, RawServoKeyframesRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoMediaListBorrowed, RawServoMediaListStrong};
use style::gecko_bindings::bindings::{RawServoMediaRule, RawServoMediaRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoMozDocumentRule, RawServoMozDocumentRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoNamespaceRule, RawServoNamespaceRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoPageRule, RawServoPageRuleBorrowed};
use style::gecko_bindings::bindings::{RawServoSelectorListBorrowed, RawServoSelectorListOwned};
use style::gecko_bindings::bindings::{RawServoSourceSizeListBorrowedOrNull, RawServoSourceSizeListOwned};
use style::gecko_bindings::bindings::{RawServoStyleSetBorrowed, RawServoStyleSetBorrowedOrNull, RawServoStyleSetOwned};
use style::gecko_bindings::bindings::{RawServoStyleSheetContentsBorrowed, ServoComputedDataBorrowed};
use style::gecko_bindings::bindings::{RawServoStyleSheetContentsStrong, ComputedStyleBorrowed};
use style::gecko_bindings::bindings::{RawServoSupportsRule, RawServoSupportsRuleBorrowed};
use style::gecko_bindings::bindings::{ServoCssRulesBorrowed, ServoCssRulesStrong};
use style::gecko_bindings::bindings::{nsACString, nsAString, nsCSSPropertyIDSetBorrowedMut};
use style::gecko_bindings::bindings::ComputedStyleBorrowedOrNull;
use style::gecko_bindings::bindings::Gecko_AddPropertyToSet;
use style::gecko_bindings::bindings::Gecko_AppendPropertyValuePair;
use style::gecko_bindings::bindings::Gecko_ConstructFontFeatureValueSet;
use style::gecko_bindings::bindings::Gecko_GetOrCreateFinalKeyframe;
use style::gecko_bindings::bindings::Gecko_GetOrCreateInitialKeyframe;
use style::gecko_bindings::bindings::Gecko_GetOrCreateKeyframeAtStart;
use style::gecko_bindings::bindings::Gecko_HaveSeenPtr;
use style::gecko_bindings::bindings::Gecko_NewNoneTransform;
use style::gecko_bindings::bindings::RawGeckoAnimationPropertySegmentBorrowed;
use style::gecko_bindings::bindings::RawGeckoCSSPropertyIDListBorrowed;
use style::gecko_bindings::bindings::RawGeckoComputedKeyframeValuesListBorrowedMut;
use style::gecko_bindings::bindings::RawGeckoComputedTimingBorrowed;
use style::gecko_bindings::bindings::RawGeckoFontFaceRuleListBorrowedMut;
use style::gecko_bindings::bindings::RawGeckoServoAnimationValueListBorrowedMut;
use style::gecko_bindings::bindings::RawGeckoServoStyleRuleListBorrowedMut;
use style::gecko_bindings::bindings::RawServoAnimationValueBorrowed;
use style::gecko_bindings::bindings::RawServoAnimationValueBorrowedOrNull;
use style::gecko_bindings::bindings::RawServoAnimationValueMapBorrowedMut;
use style::gecko_bindings::bindings::RawServoAnimationValueStrong;
use style::gecko_bindings::bindings::RawServoAnimationValueTableBorrowed;
use style::gecko_bindings::bindings::RawServoDeclarationBlockBorrowedOrNull;
use style::gecko_bindings::bindings::RawServoStyleRuleBorrowed;
use style::gecko_bindings::bindings::RawServoStyleSet;
use style::gecko_bindings::bindings::nsCSSValueBorrowedMut;
use style::gecko_bindings::bindings::nsTArrayBorrowed_uintptr_t;
use style::gecko_bindings::bindings::nsTimingFunctionBorrowed;
use style::gecko_bindings::bindings::nsTimingFunctionBorrowedMut;
use style::gecko_bindings::structs;
use style::gecko_bindings::structs::{CallerType, CSSPseudoElementType, CompositeOperation};
use style::gecko_bindings::structs::{DeclarationBlockMutationClosure, Loader, LoaderReusableStyleSheets};
use style::gecko_bindings::structs::{RawServoStyleRule, ComputedStyleStrong, RustString};
use style::gecko_bindings::structs::{SheetParsingMode, nsAtom, nsCSSPropertyID};
use style::gecko_bindings::structs::{StyleSheet as DomStyleSheet, SheetLoadData, SheetLoadDataHolder};
use style::gecko_bindings::structs::{nsCSSFontDesc, nsCSSCounterDesc};
use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint, PropertyValuePair};
use style::gecko_bindings::structs::AtomArray;
use style::gecko_bindings::structs::IterationCompositeOperation;
use style::gecko_bindings::structs::MallocSizeOf as GeckoMallocSizeOf;
use style::gecko_bindings::structs::OriginFlags;
use style::gecko_bindings::structs::OriginFlags_Author;
use style::gecko_bindings::structs::OriginFlags_User;
use style::gecko_bindings::structs::OriginFlags_UserAgent;
use style::gecko_bindings::structs::RawGeckoGfxMatrix4x4;
use style::gecko_bindings::structs::RawGeckoPresContextOwned;
use style::gecko_bindings::structs::RawServoFontFaceRule;
use style::gecko_bindings::structs::RawServoSelectorList;
use style::gecko_bindings::structs::RawServoSourceSizeList;
use style::gecko_bindings::structs::SeenPtrs;
use style::gecko_bindings::structs::ServoElementSnapshotTable;
use style::gecko_bindings::structs::ServoStyleSetSizes;
use style::gecko_bindings::structs::ServoTraversalFlags;
use style::gecko_bindings::structs::StyleRuleInclusion;
use style::gecko_bindings::structs::URLExtraData;
use style::gecko_bindings::structs::gfxFontFeatureValueSet;
use style::gecko_bindings::structs::nsCSSValueSharedList;
use style::gecko_bindings::structs::nsCompatibility;
use style::gecko_bindings::structs::nsIDocument;
use style::gecko_bindings::structs::nsStyleTransformMatrix::MatrixTransformOperator;
use style::gecko_bindings::structs::nsTArray;
use style::gecko_bindings::structs::nsresult;
use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI};
use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
use style::gecko_bindings::sugar::refptr::RefPtr;
use style::gecko_properties;
use style::invalidation::element::restyle_hints;
use style::media_queries::MediaList;
use style::parser::{Parse, ParserContext, self};
use style::properties::{ComputedValues, Importance};
use style::properties::{LonghandId, LonghandIdSet, PropertyDeclarationBlock, PropertyId};
use style::properties::{PropertyDeclarationId, ShorthandId};
use style::properties::{SourcePropertyDeclaration, StyleBuilder};
use style::properties::{parse_one_declaration_into, parse_style_attribute};
use style::properties::animated_properties::AnimationValue;
use style::properties::animated_properties::compare_property_priority;
use style::rule_cache::RuleCacheConditions;
use style::rule_tree::{CascadeLevel, StrongRuleNode};
use style::selector_parser::{PseudoElementCascadeType, SelectorImpl};
use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
use style::string_cache::{Atom, WeakAtom};
use style::style_adjuster::StyleAdjuster;
use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, CounterStyleRule};
use style::stylesheets::{DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule};
use style::stylesheets::{KeyframesRule, MediaRule, NamespaceRule, Origin, OriginSet, PageRule};
use style::stylesheets::{StyleRule, StylesheetContents, SupportsRule, UrlExtraData};
use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
use style::stylesheets::import_rule::ImportSheet;
use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue};
use style::stylesheets::supports_rule::parse_condition_or_declaration;
use style::stylist::{add_size_of_ua_cache, AuthorStylesEnabled, RuleInclusion, Stylist};
use style::thread_state;
use style::timer::Timer;
use style::traversal::DomTraversal;
use style::traversal::resolve_style;
use style::traversal_flags::{self, TraversalFlags};
use style::values::{CustomIdent, KeyframesName};
use style::values::animated::{Animate, Procedure, ToAnimatedZero};
use style::values::computed::{Context, ToComputedValue};
use style::values::distance::ComputeSquaredDistance;
use style::values::generics::rect::Rect;
use style::values::specified;
use style::values::specified::gecko::{IntersectionObserverRootMargin, PixelOrPercentage};
use style::values::specified::source_size_list::SourceSizeList;
use style_traits::{CssType, CssWriter, ParsingMode, StyleParseErrorKind, ToCss};
use super::error_reporter::ErrorReporter;
use super::stylesheet_loader::{AsyncStylesheetParser, StylesheetLoader};

trait ClosureHelper {
    fn invoke(&self);
}

impl ClosureHelper for DeclarationBlockMutationClosure {
    #[inline]
    fn invoke(&self) {
        if let Some(function) = self.function.as_ref() {
            unsafe { function(self.data) };
        }
    }
}

/*
 * For Gecko->Servo function calls, we need to redeclare the same signature that was declared in
 * the C header in Gecko. In order to catch accidental mismatches, we run rust-bindgen against
 * those signatures as well, giving us a second declaration of all the Servo_* functions in this
 * crate. If there's a mismatch, LLVM will assert and abort, which is a rather awful thing to
 * depend on but good enough for our purposes.
 */

// A dummy url data for where we don't pass url data in.
// We need to get rid of this sooner than later.
static mut DUMMY_URL_DATA: *mut URLExtraData = 0 as *mut _;

#[no_mangle]
pub unsafe extern "C" fn Servo_Initialize(dummy_url_data: *mut URLExtraData) {
    use style::gecko_bindings::sugar::origin_flags;

    // Pretend that we're a Servo Layout thread, to make some assertions happy.
    thread_state::initialize(thread_state::ThreadState::LAYOUT);

    // Perform some debug-only runtime assertions.
    restyle_hints::assert_restyle_hints_match();
    origin_flags::assert_flags_match();
    parser::assert_parsing_mode_match();
    traversal_flags::assert_traversal_flags_match();
    specified::font::assert_variant_east_asian_matches();
    specified::font::assert_variant_ligatures_matches();
    specified::box_::assert_touch_action_matches();

    DUMMY_URL_DATA = dummy_url_data;
}

#[no_mangle]
pub extern "C" fn Servo_InitializeCooperativeThread() {
    // Pretend that we're a Servo Layout thread to make some assertions happy.
    thread_state::initialize(thread_state::ThreadState::LAYOUT);
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Shutdown() {
    DUMMY_URL_DATA = ptr::null_mut();
    Stylist::shutdown();
}

#[inline(always)]
unsafe fn dummy_url_data() -> &'static UrlExtraData {
    UrlExtraData::from_ptr_ref(&DUMMY_URL_DATA)
}

#[allow(dead_code)]
fn is_main_thread() -> bool {
    unsafe { bindings::Gecko_IsMainThread() }
}

#[allow(dead_code)]
fn is_in_servo_traversal() -> bool {
    unsafe { bindings::Gecko_IsInServoTraversal() }
}

fn create_shared_context<'a>(
    global_style_data: &GlobalStyleData,
    guard: &'a SharedRwLockReadGuard,
    per_doc_data: &'a PerDocumentStyleDataImpl,
    traversal_flags: TraversalFlags,
    snapshot_map: &'a ServoElementSnapshotTable,
) -> SharedStyleContext<'a> {
    SharedStyleContext {
        stylist: &per_doc_data.stylist,
        visited_styles_enabled: per_doc_data.visited_styles_enabled(),
        options: global_style_data.options.clone(),
        guards: StylesheetGuards::same(guard),
        timer: Timer::new(),
        traversal_flags,
        snapshot_map,
    }
}

fn traverse_subtree(
    element: GeckoElement,
    global_style_data: &GlobalStyleData,
    per_doc_data: &PerDocumentStyleDataImpl,
    guard: &SharedRwLockReadGuard,
    traversal_flags: TraversalFlags,
    snapshots: &ServoElementSnapshotTable,
) {
    let shared_style_context = create_shared_context(
        &global_style_data,
        &guard,
        &per_doc_data,
        traversal_flags,
        snapshots,
    );

    let token = RecalcStyleOnly::pre_traverse(
        element,
        &shared_style_context,
    );

    if !token.should_traverse() {
        return;
    }

    debug!("Traversing subtree from {:?}", element);

    let thread_pool_holder = &*STYLE_THREAD_POOL;
    let thread_pool = if traversal_flags.contains(TraversalFlags::ParallelTraversal) {
        thread_pool_holder.style_thread_pool.as_ref()
    } else {
        None
    };

    let traversal = RecalcStyleOnly::new(shared_style_context);
    driver::traverse_dom(&traversal, token, thread_pool);
}

/// Traverses the subtree rooted at `root` for restyling.
///
/// Returns whether the root was restyled. Whether anything else was restyled or
/// not can be inferred from the dirty bits in the rest of the tree.
#[no_mangle]
pub extern "C" fn Servo_TraverseSubtree(
    root: RawGeckoElementBorrowed,
    raw_data: RawServoStyleSetBorrowed,
    snapshots: *const ServoElementSnapshotTable,
    raw_flags: ServoTraversalFlags
) -> bool {
    let traversal_flags = TraversalFlags::from_bits_truncate(raw_flags);
    debug_assert!(!snapshots.is_null());

    let element = GeckoElement(root);

    debug!("Servo_TraverseSubtree (flags={:?})", traversal_flags);
    debug!("{:?}", ShowSubtreeData(element.as_node()));

    if cfg!(debug_assertions) {
        if let Some(parent) = element.traversal_parent() {
            let data =
                parent.borrow_data().expect("Styling element with unstyled parent");
            assert!(
                !data.styles.is_display_none(),
                "Styling element with display: none parent"
            );
        }
    }

    let needs_animation_only_restyle =
        element.has_animation_only_dirty_descendants() ||
        element.has_animation_restyle_hints();

    let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
    debug_assert!(!per_doc_data.stylist.stylesheets_have_changed());

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();

    let was_initial_style = element.get_data().is_none();

    if needs_animation_only_restyle {
        debug!("Servo_TraverseSubtree doing animation-only restyle (aodd={})",
               element.has_animation_only_dirty_descendants());
        traverse_subtree(
            element,
            &global_style_data,
            &per_doc_data,
            &guard,
            traversal_flags | TraversalFlags::AnimationOnly,
            unsafe { &*snapshots },
        );
    }

    traverse_subtree(
        element,
        &global_style_data,
        &per_doc_data,
        &guard,
        traversal_flags,
        unsafe { &*snapshots },
    );

    debug!("Servo_TraverseSubtree complete (dd={}, aodd={}, lfcd={}, lfc={}, data={:?})",
           element.has_dirty_descendants(),
           element.has_animation_only_dirty_descendants(),
           element.descendants_need_frames(),
           element.needs_frame(),
           element.borrow_data().unwrap());

    if was_initial_style {
        debug_assert!(!element.borrow_data().unwrap().contains_restyle_data());
        false
    } else {
        let element_was_restyled =
            element.borrow_data().unwrap().contains_restyle_data();
        element_was_restyled
    }
}

/// Checks whether the rule tree has crossed its threshold for unused nodes, and
/// if so, frees them.
#[no_mangle]
pub extern "C" fn Servo_MaybeGCRuleTree(raw_data: RawServoStyleSetBorrowed) {
    let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    unsafe {
        per_doc_data.stylist.rule_tree().maybe_gc();
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_Interpolate(
    from: RawServoAnimationValueBorrowed,
    to: RawServoAnimationValueBorrowed,
    progress: f64,
) -> RawServoAnimationValueStrong {
    let from_value = AnimationValue::as_arc(&from);
    let to_value = AnimationValue::as_arc(&to);
    if let Ok(value) = from_value.animate(to_value, Procedure::Interpolate { progress }) {
        Arc::new(value).into_strong()
    } else {
        RawServoAnimationValueStrong::null()
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_IsInterpolable(
    from: RawServoAnimationValueBorrowed,
    to: RawServoAnimationValueBorrowed,
) -> bool {
    let from_value = AnimationValue::as_arc(&from);
    let to_value = AnimationValue::as_arc(&to);
    from_value.animate(to_value, Procedure::Interpolate { progress: 0.5 }).is_ok()
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_Add(
    a: RawServoAnimationValueBorrowed,
    b: RawServoAnimationValueBorrowed,
) -> RawServoAnimationValueStrong {
    let a_value = AnimationValue::as_arc(&a);
    let b_value = AnimationValue::as_arc(&b);
    if let Ok(value) = a_value.animate(b_value, Procedure::Add) {
        Arc::new(value).into_strong()
    } else {
        RawServoAnimationValueStrong::null()
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_Accumulate(
    a: RawServoAnimationValueBorrowed,
    b: RawServoAnimationValueBorrowed,
    count: u64,
) -> RawServoAnimationValueStrong {
    let a_value = AnimationValue::as_arc(&a);
    let b_value = AnimationValue::as_arc(&b);
    if let Ok(value) = a_value.animate(b_value, Procedure::Accumulate { count }) {
        Arc::new(value).into_strong()
    } else {
        RawServoAnimationValueStrong::null()
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_GetZeroValue(
    value_to_match: RawServoAnimationValueBorrowed,
) -> RawServoAnimationValueStrong {
    let value_to_match = AnimationValue::as_arc(&value_to_match);
    if let Ok(zero_value) = value_to_match.to_animated_zero() {
        Arc::new(zero_value).into_strong()
    } else {
        RawServoAnimationValueStrong::null()
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValues_ComputeDistance(
    from: RawServoAnimationValueBorrowed,
    to: RawServoAnimationValueBorrowed,
) -> f64 {
    let from_value = AnimationValue::as_arc(&from);
    let to_value = AnimationValue::as_arc(&to);
    // If compute_squared_distance() failed, this function will return negative value
    // in order to check whether we support the specified paced animation values.
    from_value.compute_squared_distance(to_value).map(|d| d.sqrt()).unwrap_or(-1.0)
}

/// Compute one of the endpoints for the interpolation interval, compositing it with the
/// underlying value if needed.
/// An None returned value means, "Just use endpoint_value as-is."
/// It is the responsibility of the caller to ensure that |underlying_value| is provided
/// when it will be used.
fn composite_endpoint(
    endpoint_value: Option<&RawOffsetArc<AnimationValue>>,
    composite: CompositeOperation,
    underlying_value: Option<&AnimationValue>,
) -> Option<AnimationValue> {
    match endpoint_value {
        Some(endpoint_value) => {
            match composite {
                CompositeOperation::Add => {
                    underlying_value
                        .expect("We should have an underlying_value")
                        .animate(endpoint_value, Procedure::Add).ok()
                },
                CompositeOperation::Accumulate => {
                    underlying_value
                        .expect("We should have an underlying value")
                        .animate(endpoint_value, Procedure::Accumulate { count: 1 })
                        .ok()
                },
                _ => None,
            }
        },
        None => underlying_value.map(|v| v.clone()),
    }
}

/// Accumulate one of the endpoints of the animation interval.
/// A returned value of None means, "Just use endpoint_value as-is."
fn accumulate_endpoint(
    endpoint_value: Option<&RawOffsetArc<AnimationValue>>,
    composited_value: Option<AnimationValue>,
    last_value: &AnimationValue,
    current_iteration: u64
) -> Option<AnimationValue> {
    debug_assert!(endpoint_value.is_some() || composited_value.is_some(),
                  "Should have a suitable value to use");

    let count = current_iteration;
    match composited_value {
        Some(endpoint) => {
            last_value
                .animate(&endpoint, Procedure::Accumulate { count })
                .ok()
                .or(Some(endpoint))
        },
        None => {
            last_value
                .animate(endpoint_value.unwrap(), Procedure::Accumulate { count })
                .ok()
        },
    }
}

/// Compose the animation segment. We composite it with the underlying_value and last_value if
/// needed.
/// The caller is responsible for providing an underlying value and last value
/// in all situations where there are needed.
fn compose_animation_segment(
    segment: RawGeckoAnimationPropertySegmentBorrowed,
    underlying_value: Option<&AnimationValue>,
    last_value: Option<&AnimationValue>,
    iteration_composite: IterationCompositeOperation,
    current_iteration: u64,
    total_progress: f64,
    segment_progress: f64,
) -> AnimationValue {
    // Extract keyframe values.
    let raw_from_value;
    let keyframe_from_value = if !segment.mFromValue.mServo.mRawPtr.is_null() {
        raw_from_value = unsafe { &*segment.mFromValue.mServo.mRawPtr };
        Some(AnimationValue::as_arc(&raw_from_value))
    } else {
        None
    };

    let raw_to_value;
    let keyframe_to_value = if !segment.mToValue.mServo.mRawPtr.is_null() {
        raw_to_value = unsafe { &*segment.mToValue.mServo.mRawPtr };
        Some(AnimationValue::as_arc(&raw_to_value))
    } else {
        None
    };

    let mut composited_from_value = composite_endpoint(keyframe_from_value,
                                                       segment.mFromComposite,
                                                       underlying_value);
    let mut composited_to_value = composite_endpoint(keyframe_to_value,
                                                     segment.mToComposite,
                                                     underlying_value);

    debug_assert!(keyframe_from_value.is_some() || composited_from_value.is_some(),
                  "Should have a suitable from value to use");
    debug_assert!(keyframe_to_value.is_some() || composited_to_value.is_some(),
                  "Should have a suitable to value to use");

    // Apply iteration composite behavior.
    if iteration_composite == IterationCompositeOperation::Accumulate && current_iteration > 0 {
        let last_value = last_value.unwrap_or_else(|| {
            underlying_value.expect("Should have a valid underlying value")
        });

        composited_from_value = accumulate_endpoint(keyframe_from_value,
                                                    composited_from_value,
                                                    last_value,
                                                    current_iteration);
        composited_to_value = accumulate_endpoint(keyframe_to_value,
                                                  composited_to_value,
                                                  last_value,
                                                  current_iteration);
    }

    // Use the composited value if there is one, otherwise, use the original keyframe value.
    let from = composited_from_value.as_ref().unwrap_or_else(|| keyframe_from_value.unwrap());
    let to   = composited_to_value.as_ref().unwrap_or_else(|| keyframe_to_value.unwrap());

    if segment.mToKey == segment.mFromKey {
        return if total_progress < 0. { from.clone() } else { to.clone() };
    }

    match from.animate(to, Procedure::Interpolate { progress: segment_progress }) {
        Ok(value) => value,
        _ => if segment_progress < 0.5 { from.clone() } else { to.clone() },
    }
}

#[no_mangle]
pub extern "C" fn Servo_ComposeAnimationSegment(
    segment: RawGeckoAnimationPropertySegmentBorrowed,
    underlying_value: RawServoAnimationValueBorrowedOrNull,
    last_value: RawServoAnimationValueBorrowedOrNull,
    iteration_composite: IterationCompositeOperation,
    progress: f64,
    current_iteration: u64
) -> RawServoAnimationValueStrong {
    let underlying_value = AnimationValue::arc_from_borrowed(&underlying_value).map(|v| &**v);
    let last_value = AnimationValue::arc_from_borrowed(&last_value).map(|v| &**v);
    let result = compose_animation_segment(
        segment,
        underlying_value,
        last_value,
        iteration_composite,
        current_iteration,
        progress,
        progress,
    );
    Arc::new(result).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_AnimationCompose(
    raw_value_map: RawServoAnimationValueMapBorrowedMut,
    base_values: RawServoAnimationValueTableBorrowed,
    css_property: nsCSSPropertyID,
    segment: RawGeckoAnimationPropertySegmentBorrowed,
    last_segment: RawGeckoAnimationPropertySegmentBorrowed,
    computed_timing: RawGeckoComputedTimingBorrowed,
    iteration_composite: IterationCompositeOperation,
) {
    use style::gecko_bindings::bindings::Gecko_AnimationGetBaseStyle;
    use style::gecko_bindings::bindings::Gecko_GetPositionInSegment;
    use style::gecko_bindings::bindings::Gecko_GetProgressFromComputedTiming;
    use style::properties::animated_properties::AnimationValueMap;

    let property = match LonghandId::from_nscsspropertyid(css_property) {
        Ok(longhand) if longhand.is_animatable() => longhand,
        _ => return,
    };
    let value_map = AnimationValueMap::from_ffi_mut(raw_value_map);

    // We will need an underlying value if either of the endpoints is null...
    let need_underlying_value = segment.mFromValue.mServo.mRawPtr.is_null() ||
                                segment.mToValue.mServo.mRawPtr.is_null() ||
                                // ... or if they have a non-replace composite mode ...
                                segment.mFromComposite != CompositeOperation::Replace ||
                                segment.mToComposite != CompositeOperation::Replace ||
                                // ... or if we accumulate onto the last value and it is null.
                                (iteration_composite == IterationCompositeOperation::Accumulate &&
                                 computed_timing.mCurrentIteration > 0 &&
                                 last_segment.mToValue.mServo.mRawPtr.is_null());

    // If either of the segment endpoints are null, get the underlying value to
    // use from the current value in the values map (set by a lower-priority
    // effect), or, if there is no current value, look up the cached base value
    // for this property.
    let underlying_value = if need_underlying_value {
        let previous_composed_value = value_map.get(&property).cloned();
        previous_composed_value.or_else(|| {
            let raw_base_style = unsafe { Gecko_AnimationGetBaseStyle(base_values, css_property) };
            AnimationValue::arc_from_borrowed(&raw_base_style).map(|v| &**v).cloned()
        })
    } else {
        None
    };

    if need_underlying_value && underlying_value.is_none() {
        warn!("Underlying value should be valid when we expect to use it");
        return;
    }

    let raw_last_value;
    let last_value = if !last_segment.mToValue.mServo.mRawPtr.is_null() {
        raw_last_value = unsafe { &*last_segment.mToValue.mServo.mRawPtr };
        Some(&**AnimationValue::as_arc(&raw_last_value))
    } else {
        None
    };

    let progress = unsafe { Gecko_GetProgressFromComputedTiming(computed_timing) };
    let position = if segment.mToKey == segment.mFromKey {
        // Note: compose_animation_segment doesn't use this value
        // if segment.mFromKey == segment.mToKey, so assigning |progress| directly is fine.
        progress
    } else {
        unsafe { Gecko_GetPositionInSegment(segment, progress, computed_timing.mBeforeFlag) }
    };

    let result = compose_animation_segment(
        segment,
        underlying_value.as_ref(),
        last_value,
        iteration_composite,
        computed_timing.mCurrentIteration,
        progress,
        position,
    );
    value_map.insert(property, result);
}

macro_rules! get_property_id_from_nscsspropertyid {
    ($property_id: ident, $ret: expr) => {{
        match PropertyId::from_nscsspropertyid($property_id) {
            Ok(property_id) => property_id,
            Err(()) => { return $ret; }
        }
    }}
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_Serialize(
    value: RawServoAnimationValueBorrowed,
    property: nsCSSPropertyID,
    buffer: *mut nsAString,
) {
    let uncomputed_value = AnimationValue::as_arc(&value).uncompute();
    let buffer = unsafe { buffer.as_mut().unwrap() };
    let rv = PropertyDeclarationBlock::with_one(uncomputed_value, Importance::Normal)
        .single_value_to_css(&get_property_id_from_nscsspropertyid!(property, ()), buffer,
                             None, None /* No extra custom properties */);
    debug_assert!(rv.is_ok());
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_GetOpacity(
    value: RawServoAnimationValueBorrowed,
) -> f32 {
    let value = AnimationValue::as_arc(&value);
    if let AnimationValue::Opacity(opacity) = **value {
        opacity
    } else {
        panic!("The AnimationValue should be Opacity");
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_Opacity(
    opacity: f32
) -> RawServoAnimationValueStrong {
    Arc::new(AnimationValue::Opacity(opacity)).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_GetTransform(
    value: RawServoAnimationValueBorrowed,
    list: *mut structs::RefPtr<nsCSSValueSharedList>
) {
    let value = AnimationValue::as_arc(&value);
    if let AnimationValue::Transform(ref servo_list) = **value {
        let list = unsafe { &mut *list };
        if servo_list.0.is_empty() {
            unsafe {
                list.set_move(RefPtr::from_addrefed(Gecko_NewNoneTransform()));
            }
        } else {
            gecko_properties::convert_transform(&servo_list.0, list);
        }
    } else {
        panic!("The AnimationValue should be transform");
    }
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_Transform(
    list: *const nsCSSValueSharedList
) -> RawServoAnimationValueStrong {
    let list = unsafe { (&*list).mHead.as_ref() };
    let transform = gecko_properties::clone_transform_from_list(list);
    Arc::new(AnimationValue::Transform(transform)).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_DeepEqual(
    this: RawServoAnimationValueBorrowed,
    other: RawServoAnimationValueBorrowed,
) -> bool {
    let this_value = AnimationValue::as_arc(&this);
    let other_value = AnimationValue::as_arc(&other);
    this_value == other_value
}

#[no_mangle]
pub extern "C" fn Servo_AnimationValue_Uncompute(
    value: RawServoAnimationValueBorrowed,
) -> RawServoDeclarationBlockStrong {
    let value = AnimationValue::as_arc(&value);
    let global_style_data = &*GLOBAL_STYLE_DATA;
    Arc::new(global_style_data.shared_lock.wrap(
        PropertyDeclarationBlock::with_one(value.uncompute(), Importance::Normal))).into_strong()
}

// Return the ComputedValues by a base ComputedValues and the rules.
fn resolve_rules_for_element_with_context<'a>(
    element: GeckoElement<'a>,
    mut context: StyleContext<GeckoElement<'a>>,
    rules: StrongRuleNode
) -> Arc<ComputedValues> {
    use style::style_resolver::{PseudoElementResolution, StyleResolverForElement};

    // This currently ignores visited styles, which seems acceptable, as
    // existing browsers don't appear to animate visited styles.
    let inputs =
        CascadeInputs {
            rules: Some(rules),
            visited_rules: None,
        };

    // Actually `PseudoElementResolution` doesn't matter.
    StyleResolverForElement::new(element,
                                 &mut context,
                                 RuleInclusion::All,
                                 PseudoElementResolution::IfApplicable)
        .cascade_style_and_visited_with_default_parents(inputs).0
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(
    raw_style_set: RawServoStyleSetBorrowed,
    element: RawGeckoElementBorrowed,
    computed_values: ComputedStyleBorrowed,
    snapshots: *const ServoElementSnapshotTable,
) -> ComputedStyleStrong {
    debug_assert!(!snapshots.is_null());
    let computed_values = unsafe { ArcBorrow::from_ref(computed_values) };

    let rules = match computed_values.rules {
        None => return computed_values.clone_arc().into(),
        Some(ref rules) => rules,
    };

    let doc_data = PerDocumentStyleData::from_ffi(raw_style_set).borrow();
    let without_animations_rules = doc_data.stylist.rule_tree().remove_animation_rules(rules);
    if without_animations_rules == *rules {
        return computed_values.clone_arc().into();
    }

    let element = GeckoElement(element);

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let shared = create_shared_context(&global_style_data,
                                       &guard,
                                       &doc_data,
                                       TraversalFlags::empty(),
                                       unsafe { &*snapshots });
    let mut tlc = ThreadLocalStyleContext::new(&shared);
    let context = StyleContext {
        shared: &shared,
        thread_local: &mut tlc,
    };

    resolve_rules_for_element_with_context(element, context, without_animations_rules).into()
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_GetComputedValuesByAddingAnimation(
    raw_style_set: RawServoStyleSetBorrowed,
    element: RawGeckoElementBorrowed,
    computed_values: ComputedStyleBorrowed,
    snapshots: *const ServoElementSnapshotTable,
    animation_value: RawServoAnimationValueBorrowed,
) -> ComputedStyleStrong {
    debug_assert!(!snapshots.is_null());
    let computed_values = unsafe { ArcBorrow::from_ref(computed_values) };
    let rules = match computed_values.rules {
        None => return ComputedStyleStrong::null(),
        Some(ref rules) => rules,
    };

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let uncomputed_value = AnimationValue::as_arc(&animation_value).uncompute();
    let doc_data = PerDocumentStyleData::from_ffi(raw_style_set).borrow();

    let with_animations_rules = {
        let guards = StylesheetGuards::same(&guard);
        let declarations =
            Arc::new(global_style_data.shared_lock.wrap(
                PropertyDeclarationBlock::with_one(uncomputed_value, Importance::Normal)));
        doc_data.stylist
            .rule_tree()
            .add_animation_rules_at_transition_level(rules, declarations, &guards)
    };

    let element = GeckoElement(element);
    if element.borrow_data().is_none() {
        return ComputedStyleStrong::null();
    }

    let shared = create_shared_context(&global_style_data,
                                       &guard,
                                       &doc_data,
                                       TraversalFlags::empty(),
                                       unsafe { &*snapshots });
    let mut tlc: ThreadLocalStyleContext<GeckoElement> = ThreadLocalStyleContext::new(&shared);
    let context = StyleContext {
        shared: &shared,
        thread_local: &mut tlc,
    };

    resolve_rules_for_element_with_context(element, context, with_animations_rules).into()
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue(
    computed_values: ComputedStyleBorrowed,
    property_id: nsCSSPropertyID,
) -> RawServoAnimationValueStrong {
    let property = match LonghandId::from_nscsspropertyid(property_id) {
        Ok(longhand) => longhand,
        Err(()) => return Strong::null(),
    };
    match AnimationValue::from_computed_values(property, &computed_values) {
        Some(v) => Arc::new(v).into_strong(),
        None => Strong::null(),
    }
}

#[no_mangle]
pub extern "C" fn Servo_ResolveLogicalProperty(
    property_id: nsCSSPropertyID,
    style: ComputedStyleBorrowed,
) -> nsCSSPropertyID {
    let longhand =
        LonghandId::from_nscsspropertyid(property_id)
            .expect("There are no logical shorthands (yet)");

    longhand.to_physical(style.writing_mode).to_nscsspropertyid()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_LookupEnabledForAllContent(
    prop: *const nsACString,
) -> nsCSSPropertyID {
    match PropertyId::parse_enabled_for_all_content((*prop).as_str_unchecked()) {
        Ok(p) => p.to_nscsspropertyid_resolving_aliases(),
        Err(..) => nsCSSPropertyID::eCSSProperty_UNKNOWN,
    }
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_GetName(
    prop: nsCSSPropertyID,
    out_length: *mut u32,
) -> *const u8 {
    use style::properties::NonCustomPropertyId;

    let (ptr, len) = match NonCustomPropertyId::from_nscsspropertyid(prop) {
        Ok(p) => {
            let name = p.name();
            (name.as_bytes().as_ptr(), name.len())
        }
        Err(..) => (ptr::null(), 0),
    };

    *out_length = len as u32;
    ptr
}

macro_rules! parse_enabled_property_name {
    ($prop_name:ident, $found:ident, $default:expr) => {{
        let prop_name = $prop_name.as_ref().unwrap().as_str_unchecked();
        match PropertyId::parse_enabled_for_all_content(prop_name) {
            Ok(p) => {
                *$found = true;
                p
            }
            Err(..) => {
                *$found = false;
                return $default;
            }
        }
    }}
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_IsShorthand(
    prop_name: *const nsACString,
    found: *mut bool
) -> bool {
    let prop_id = parse_enabled_property_name!(prop_name, found, false);
    prop_id.is_shorthand()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_IsInherited(
    prop_name: *const nsACString,
) -> bool {
    let prop_name = prop_name.as_ref().unwrap().as_str_unchecked();
    let prop_id = match PropertyId::parse_enabled_for_all_content(prop_name) {
        Ok(id) => id,
        Err(_) => return false,
    };
    let longhand_id = match prop_id {
        PropertyId::Custom(_) => return true,
        PropertyId::Longhand(id) |
        PropertyId::LonghandAlias(id, _) => id,
        PropertyId::Shorthand(id) |
        PropertyId::ShorthandAlias(id, _) => id.longhands().next().unwrap(),
    };
    longhand_id.inherited()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_SupportsType(
    prop_name: *const nsACString,
    ty: u32,
    found: *mut bool,
) -> bool {
    let prop_id = parse_enabled_property_name!(prop_name, found, false);
    // This should match the constants in InspectorUtils.
    // (Let's don't bother importing InspectorUtilsBinding into bindings
    // because it is not used anywhere else, and issue here would be
    // caught by the property-db test anyway.)
    let ty = match ty {
        1 => CssType::COLOR,
        2 => CssType::GRADIENT,
        3 => CssType::TIMING_FUNCTION,
        _ => unreachable!("unknown CSS type {}", ty),
    };
    prop_id.supports_type(ty)
}

#[no_mangle]
pub unsafe extern "C" fn Servo_Property_GetCSSValuesForProperty(
    prop_name: *const nsACString,
    found: *mut bool,
    result: *mut nsTArray<nsStringRepr>,
) {
    let prop_id = parse_enabled_property_name!(prop_name, found, ());
    // Use B-tree set for unique and sorted result.
    let mut values = BTreeSet::<&'static str>::new();
    prop_id.collect_property_completion_keywords(&mut |list| values.extend(list.iter()));

    let mut extras = vec![];
    if values.contains("transparent") {
        // This is a special value devtools use to avoid inserting the
        // long list of color keywords. We need to prepend it to values.
        extras.push("COLOR");
    }

    let result = result.as_mut().unwrap();
    let len = extras.len() + values.len();
    bindings::Gecko_ResizeTArrayForStrings(result, len as u32);

    for (src, dest) in extras.iter().chain(values.iter()).zip(result.iter_mut()) {
        dest.write_str(src).unwrap();
    }
}

#[no_mangle]
pub extern "C" fn Servo_Property_IsAnimatable(property: nsCSSPropertyID) -> bool {
    use style::properties::animated_properties;
    animated_properties::nscsspropertyid_is_animatable(property)
}

#[no_mangle]
pub extern "C" fn Servo_Property_IsTransitionable(property: nsCSSPropertyID) -> bool {
    use style::properties::animated_properties;
    animated_properties::nscsspropertyid_is_transitionable(property)
}

#[no_mangle]
pub extern "C" fn Servo_Property_IsDiscreteAnimatable(property: nsCSSPropertyID) -> bool {
    match LonghandId::from_nscsspropertyid(property) {
        Ok(longhand) => longhand.is_discrete_animatable(),
        Err(()) => return false,
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleWorkerThreadCount() -> u32 {
    STYLE_THREAD_POOL.num_threads as u32
}

#[no_mangle]
pub extern "C" fn Servo_Element_ClearData(element: RawGeckoElementBorrowed) {
    unsafe { GeckoElement(element).clear_data() };
}

#[no_mangle]
pub extern "C" fn Servo_Element_SizeOfExcludingThisAndCVs(
    malloc_size_of: GeckoMallocSizeOf,
    malloc_enclosing_size_of: GeckoMallocSizeOf,
    seen_ptrs: *mut SeenPtrs,
    element: RawGeckoElementBorrowed,
) -> usize {
    let element = GeckoElement(element);
    let borrow = element.borrow_data();
    if let Some(data) = borrow {
        let have_seen_ptr = move |ptr| { unsafe { Gecko_HaveSeenPtr(seen_ptrs, ptr) } };
        let mut ops = MallocSizeOfOps::new(
            malloc_size_of.unwrap(),
            Some(malloc_enclosing_size_of.unwrap()),
            Some(Box::new(have_seen_ptr)),
        );
        (*data).size_of_excluding_cvs(&mut ops)
    } else {
        0
    }
}

#[no_mangle]
pub extern "C" fn Servo_Element_HasPrimaryComputedValues(element: RawGeckoElementBorrowed) -> bool
{
    let element = GeckoElement(element);
    let data = element.borrow_data().expect("Looking for CVs on unstyled element");
    data.has_styles()
}

#[no_mangle]
pub extern "C" fn Servo_Element_GetPrimaryComputedValues(
    element: RawGeckoElementBorrowed,
) -> ComputedStyleStrong {
    let element = GeckoElement(element);
    let data = element.borrow_data().expect("Getting CVs on unstyled element");
    data.styles.primary().clone().into()
}

#[no_mangle]
pub extern "C" fn Servo_Element_HasPseudoComputedValues(
    element: RawGeckoElementBorrowed,
    index: usize,
) -> bool {
    let element = GeckoElement(element);
    let data = element.borrow_data().expect("Looking for CVs on unstyled element");
    data.styles.pseudos.as_array()[index].is_some()
}

#[no_mangle]
pub extern "C" fn Servo_Element_GetPseudoComputedValues(
    element: RawGeckoElementBorrowed,
    index: usize,
) -> ComputedStyleStrong {
    let element = GeckoElement(element);
    let data = element.borrow_data().expect("Getting CVs that aren't present");
    data.styles.pseudos.as_array()[index].as_ref().expect("Getting CVs that aren't present")
        .clone().into()
}

#[no_mangle]
pub extern "C" fn Servo_Element_IsDisplayNone(
    element: RawGeckoElementBorrowed,
) -> bool {
    let element = GeckoElement(element);
    let data = element.get_data()
        .expect("Invoking Servo_Element_IsDisplayNone on unstyled element");

    // This function is hot, so we bypass the AtomicRefCell.
    //
    // It would be nice to also assert that we're not in the servo traversal,
    // but this function is called at various intermediate checkpoints when
    // managing the traversal on the Gecko side.
    debug_assert!(is_main_thread());
    unsafe { &*data.as_ptr() }.styles.is_display_none()
}

#[no_mangle]
pub extern "C" fn Servo_Element_IsDisplayContents(
    element: RawGeckoElementBorrowed,
) -> bool {
    let element = GeckoElement(element);
    let data = element.get_data()
        .expect("Invoking Servo_Element_IsDisplayContents on unstyled element");

    debug_assert!(is_main_thread());
    unsafe { &*data.as_ptr() }.styles.primary().get_box().clone_display().is_contents()
}

#[no_mangle]
pub extern "C" fn Servo_Element_IsPrimaryStyleReusedViaRuleNode(element: RawGeckoElementBorrowed) -> bool {
    let element = GeckoElement(element);
    let data = element.borrow_data()
                      .expect("Invoking Servo_Element_IsPrimaryStyleReusedViaRuleNode on unstyled element");
    data.flags.contains(data::ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE)
}

fn mode_to_origin(mode: SheetParsingMode) -> Origin {
    match mode {
        SheetParsingMode::eAuthorSheetFeatures => Origin::Author,
        SheetParsingMode::eUserSheetFeatures => Origin::User,
        SheetParsingMode::eAgentSheetFeatures => Origin::UserAgent,
        SheetParsingMode::eSafeAgentSheetFeatures => Origin::UserAgent,
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_Empty(mode: SheetParsingMode) -> RawServoStyleSheetContentsStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let origin = mode_to_origin(mode);
    let shared_lock = &global_style_data.shared_lock;
    Arc::new(
        StylesheetContents::from_str(
            "",
            unsafe { dummy_url_data() }.clone(),
            origin,
            shared_lock,
            /* loader = */ None,
            None,
            QuirksMode::NoQuirks,
            0
        )
    ).into_strong()
}

/// Note: The load_data corresponds to this sheet, and is passed as the parent
/// load data for child sheet loads. It may be null for certain cases where we
/// know we won't have child loads.
#[no_mangle]
pub extern "C" fn Servo_StyleSheet_FromUTF8Bytes(
    loader: *mut Loader,
    stylesheet: *mut DomStyleSheet,
    load_data: *mut SheetLoadData,
    bytes: *const nsACString,
    mode: SheetParsingMode,
    extra_data: *mut URLExtraData,
    line_number_offset: u32,
    quirks_mode: nsCompatibility,
    reusable_sheets: *mut LoaderReusableStyleSheets,
) -> RawServoStyleSheetContentsStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let input: &str = unsafe { (*bytes).as_str_unchecked() };

    let reporter = ErrorReporter::new(stylesheet, loader, extra_data);
    let url_data = unsafe { UrlExtraData::from_ptr_ref(&extra_data) };
    let loader = if loader.is_null() {
        None
    } else {
        Some(StylesheetLoader::new(loader, stylesheet, load_data, reusable_sheets))
    };

    // FIXME(emilio): loader.as_ref() doesn't typecheck for some reason?
    let loader: Option<&StyleStylesheetLoader> = match loader {
        None => None,
        Some(ref s) => Some(s),
    };


    Arc::new(StylesheetContents::from_str(
        input,
        url_data.clone(),
        mode_to_origin(mode),
        &global_style_data.shared_lock,
        loader,
        reporter.as_ref().map(|r| r as &ParseErrorReporter),
        quirks_mode.into(),
        line_number_offset,
    )).into_strong()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_StyleSheet_FromUTF8BytesAsync(
    load_data: *mut SheetLoadDataHolder,
    extra_data: *mut URLExtraData,
    bytes: *const nsACString,
    mode: SheetParsingMode,
    line_number_offset: u32,
    quirks_mode: nsCompatibility,
) {
    let load_data = RefPtr::new(load_data);
    let extra_data = UrlExtraData(RefPtr::new(extra_data));

    let mut sheet_bytes = nsCString::new();
    sheet_bytes.assign(&*bytes);
    let async_parser = AsyncStylesheetParser::new(
        load_data,
        extra_data,
        sheet_bytes,
        mode_to_origin(mode),
        quirks_mode.into(),
        line_number_offset
    );

    if let Some(thread_pool) = STYLE_THREAD_POOL.style_thread_pool.as_ref() {
        thread_pool.spawn(|| {
            async_parser.parse();
        });
    } else {
        async_parser.parse();
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_AppendStyleSheet(
    raw_data: RawServoStyleSetBorrowed,
    sheet: *const DomStyleSheet,
) {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let data = &mut *data;
    let guard = global_style_data.shared_lock.read();
    let sheet = unsafe { GeckoStyleSheet::new(sheet) };
    data.stylist.append_stylesheet(sheet, &guard);
}

#[no_mangle]
pub extern "C" fn Servo_AuthorStyles_Create() -> *mut RawServoAuthorStyles {
    Box::into_raw(Box::new(AuthorStyles::<GeckoStyleSheet>::new())) as *mut _
}

#[no_mangle]
pub extern "C" fn Servo_AuthorStyles_Drop(
    styles: RawServoAuthorStylesOwned,
) {
    let _ = styles.into_box::<AuthorStyles<_>>();
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_AppendStyleSheet(
    styles: RawServoAuthorStylesBorrowedMut,
    sheet: *const DomStyleSheet,
) {
    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(styles);

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let sheet = GeckoStyleSheet::new(sheet);
    styles.stylesheets.append_stylesheet(None, sheet, &guard);
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_InsertStyleSheetBefore(
    styles: RawServoAuthorStylesBorrowedMut,
    sheet: *const DomStyleSheet,
    before_sheet: *const DomStyleSheet,
) {
    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(styles);

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    styles.stylesheets.insert_stylesheet_before(
        None,
        GeckoStyleSheet::new(sheet),
        GeckoStyleSheet::new(before_sheet),
        &guard,
    );
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_RemoveStyleSheet(
    styles: RawServoAuthorStylesBorrowedMut,
    sheet: *const DomStyleSheet,
) {
    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(styles);

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    styles.stylesheets.remove_stylesheet(
        None,
        GeckoStyleSheet::new(sheet),
        &guard,
    );
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_ForceDirty(
    styles: RawServoAuthorStylesBorrowedMut,
) {
    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(styles);
    styles.stylesheets.force_dirty();
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_Flush(
    styles: RawServoAuthorStylesBorrowedMut,
    document_set: RawServoStyleSetBorrowed,
) {
    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(styles);
    // Try to avoid the atomic borrow below if possible.
    if !styles.stylesheets.dirty() {
        return;
    }

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();

    let document_data =
        PerDocumentStyleData::from_ffi(document_set).borrow();

    let stylist = &document_data.stylist;

    // TODO(emilio): This is going to need an element or something to do proper
    // invalidation in Shadow roots.
    styles.flush::<GeckoElement>(
        stylist.device(),
        stylist.quirks_mode(),
        &guard,
    );
}

#[no_mangle]
pub unsafe extern "C" fn Servo_AuthorStyles_SizeOfIncludingThis(
    malloc_size_of: GeckoMallocSizeOf,
    malloc_enclosing_size_of: GeckoMallocSizeOf,
    styles: RawServoAuthorStylesBorrowed,
) -> usize {
    // We cannot `use` MallocSizeOf at the top level, otherwise the compiler
    // would complain in `Servo_StyleSheet_SizeOfIncludingThis` for `size_of`
    // there.
    use malloc_size_of::MallocSizeOf;
    let malloc_size_of = malloc_size_of.unwrap();
    let malloc_size_of_this = malloc_size_of(styles as *const RawServoAuthorStyles as *const c_void);

    let styles = AuthorStyles::<GeckoStyleSheet>::from_ffi(styles);
    let mut ops = MallocSizeOfOps::new(malloc_size_of,
                                       Some(malloc_enclosing_size_of.unwrap()),
                                       None);
    malloc_size_of_this + styles.size_of(&mut ops)
}

#[no_mangle]
pub unsafe extern "C" fn Servo_StyleSet_MediumFeaturesChanged(
    document_set: RawServoStyleSetBorrowed,
    non_document_styles: *mut nsTArray<RawServoAuthorStylesBorrowedMut>,
    may_affect_default_style: bool,
) -> structs::MediumFeaturesChangedResult {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();

    // NOTE(emilio): We don't actually need to flush the stylist here and ensure
    // it's up to date.
    //
    // In case it isn't we would trigger a rebuild + restyle as needed too.
    //
    // We need to ensure the default computed values are up to date though,
    // because those can influence the result of media query evaluation.
    let mut document_data =
        PerDocumentStyleData::from_ffi(document_set).borrow_mut();

    if may_affect_default_style {
        document_data.stylist.device_mut().reset_computed_values();
    }
    let guards = StylesheetGuards::same(&guard);

    let origins_in_which_rules_changed =
        document_data.stylist.media_features_change_changed_style(
            &guards,
            document_data.stylist.device(),
        );

    let affects_document_rules = !origins_in_which_rules_changed.is_empty();
    if affects_document_rules {
        document_data.stylist.force_stylesheet_origins_dirty(origins_in_which_rules_changed);
    }

    let mut affects_non_document_rules = false;
    for author_styles in &mut **non_document_styles {
        let author_styles =
            AuthorStyles::<GeckoStyleSheet>::from_ffi_mut(&mut *author_styles);
        let affected_style = author_styles.stylesheets.iter().any(|sheet| {
            !author_styles.data.media_feature_affected_matches(
                sheet,
                &guards.author,
                document_data.stylist.device(),
                document_data.stylist.quirks_mode(),
            )
        });
        if affected_style {
            affects_non_document_rules = true;
            author_styles.stylesheets.force_dirty();
        }
    }

    let uses_viewport_units =
        document_data.stylist.device().used_viewport_size();

    structs::MediumFeaturesChangedResult {
        mAffectsDocumentRules: affects_document_rules,
        mAffectsNonDocumentRules: affects_non_document_rules,
        mUsesViewportUnits: uses_viewport_units,
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_PrependStyleSheet(
    raw_data: RawServoStyleSetBorrowed,
    sheet: *const DomStyleSheet,
) {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let data = &mut *data;
    let guard = global_style_data.shared_lock.read();
    let sheet = unsafe { GeckoStyleSheet::new(sheet) };
    data.stylist.prepend_stylesheet(sheet, &guard);
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_InsertStyleSheetBefore(
    raw_data: RawServoStyleSetBorrowed,
    sheet: *const DomStyleSheet,
    before_sheet: *const DomStyleSheet
) {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let data = &mut *data;
    let guard = global_style_data.shared_lock.read();
    let sheet = unsafe { GeckoStyleSheet::new(sheet) };
    data.stylist.insert_stylesheet_before(
        sheet,
        unsafe { GeckoStyleSheet::new(before_sheet) },
        &guard,
    );
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_RemoveStyleSheet(
    raw_data: RawServoStyleSetBorrowed,
    sheet: *const DomStyleSheet
) {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let data = &mut *data;
    let guard = global_style_data.shared_lock.read();
    let sheet = unsafe { GeckoStyleSheet::new(sheet) };
    data.stylist.remove_stylesheet(sheet, &guard);
}

#[no_mangle]
pub unsafe extern "C" fn Servo_StyleSet_FlushStyleSheets(
    raw_data: RawServoStyleSetBorrowed,
    doc_element: RawGeckoElementBorrowedOrNull,
    snapshots: *const ServoElementSnapshotTable,
) {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let doc_element = doc_element.map(GeckoElement);

    let have_invalidations =
        data.flush_stylesheets(&guard, doc_element, snapshots.as_ref());

    if have_invalidations && doc_element.is_some() {
        // The invalidation machinery propagates the bits up, but we still need
        // to tell the Gecko restyle root machinery about it.
        bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.unwrap().0);
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_NoteStyleSheetsChanged(
    raw_data: RawServoStyleSetBorrowed,
    changed_origins: OriginFlags,
) {
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    data.stylist.force_stylesheet_origins_dirty(OriginSet::from(changed_origins));
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_SetAuthorStyleDisabled(
    raw_data: RawServoStyleSetBorrowed,
    author_style_disabled: bool,
) {
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let enabled =
        if author_style_disabled {
            AuthorStylesEnabled::No
        } else {
            AuthorStylesEnabled::Yes
        };
    data.stylist.set_author_styles_enabled(enabled);
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_HasRules(
    raw_contents: RawServoStyleSheetContentsBorrowed
) -> bool {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    !StylesheetContents::as_arc(&raw_contents)
        .rules.read_with(&guard).0.is_empty()
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_GetRules(
    sheet: RawServoStyleSheetContentsBorrowed
) -> ServoCssRulesStrong {
    StylesheetContents::as_arc(&sheet).rules.clone().into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_Clone(
    raw_sheet: RawServoStyleSheetContentsBorrowed,
    reference_sheet: *const DomStyleSheet,
) -> RawServoStyleSheetContentsStrong {
    use style::shared_lock::{DeepCloneParams, DeepCloneWithLock};
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let contents = StylesheetContents::as_arc(&raw_sheet);
    let params = DeepCloneParams { reference_sheet };

    Arc::new(contents.deep_clone_with_lock(
        &global_style_data.shared_lock,
        &guard,
        &params,
    )).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_SizeOfIncludingThis(
    malloc_size_of: GeckoMallocSizeOf,
    malloc_enclosing_size_of: GeckoMallocSizeOf,
    sheet: RawServoStyleSheetContentsBorrowed
) -> usize {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let mut ops = MallocSizeOfOps::new(malloc_size_of.unwrap(),
                                       Some(malloc_enclosing_size_of.unwrap()),
                                       None);
    StylesheetContents::as_arc(&sheet).size_of(&guard, &mut ops)
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_GetOrigin(
    sheet: RawServoStyleSheetContentsBorrowed,
) -> u8 {
    let origin = match StylesheetContents::as_arc(&sheet).origin {
        Origin::UserAgent => OriginFlags_UserAgent,
        Origin::User => OriginFlags_User,
        Origin::Author => OriginFlags_Author,
    };
    // We'd like to return `OriginFlags` here, but bindgen bitfield enums don't
    // work as return values with the Linux 32-bit ABI at the moment because
    // they wrap the value in a struct, so for now just unwrap it.
    origin.0
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_GetSourceMapURL(
    sheet: RawServoStyleSheetContentsBorrowed,
    result: *mut nsAString
) {
    let contents = StylesheetContents::as_arc(&sheet);
    let url_opt = contents.source_map_url.read();
    if let Some(ref url) = *url_opt {
        write!(unsafe { &mut *result }, "{}", url).unwrap();
    }
}

#[no_mangle]
pub extern "C" fn Servo_StyleSheet_GetSourceURL(
    sheet: RawServoStyleSheetContentsBorrowed,
    result: *mut nsAString
) {
    let contents = StylesheetContents::as_arc(&sheet);
    let url_opt = contents.source_url.read();
    if let Some(ref url) = *url_opt {
        write!(unsafe { &mut *result }, "{}", url).unwrap();
    }
}

fn read_locked_arc<T, R, F>(raw: &<Locked<T> as HasFFI>::FFIType, func: F) -> R
where
    Locked<T>: HasArcFFI,
    F: FnOnce(&T) -> R,
{
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    func(Locked::<T>::as_arc(&raw).read_with(&guard))
}

#[cfg(debug_assertions)]
unsafe fn read_locked_arc_unchecked<T, R, F>(raw: &<Locked<T> as HasFFI>::FFIType, func: F) -> R
    where Locked<T>: HasArcFFI, F: FnOnce(&T) -> R
{
    debug_assert!(is_main_thread() && !is_in_servo_traversal());
    read_locked_arc(raw, func)
}

#[cfg(not(debug_assertions))]
unsafe fn read_locked_arc_unchecked<T, R, F>(raw: &<Locked<T> as HasFFI>::FFIType, func: F) -> R
    where Locked<T>: HasArcFFI, F: FnOnce(&T) -> R
{
    func(Locked::<T>::as_arc(&raw).read_unchecked())
}

fn write_locked_arc<T, R, F>(raw: &<Locked<T> as HasFFI>::FFIType, func: F) -> R
    where Locked<T>: HasArcFFI, F: FnOnce(&mut T) -> R
{
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let mut guard = global_style_data.shared_lock.write();
    func(Locked::<T>::as_arc(&raw).write_with(&mut guard))
}

#[no_mangle]
pub extern "C" fn Servo_CssRules_ListTypes(
    rules: ServoCssRulesBorrowed,
    result: nsTArrayBorrowed_uintptr_t,
) {
    read_locked_arc(rules, |rules: &CssRules| {
        let iter = rules.0.iter().map(|rule| rule.rule_type() as usize);
        let (size, upper) = iter.size_hint();
        debug_assert_eq!(size, upper.unwrap());
        unsafe { result.set_len(size as u32) };
        result.iter_mut().zip(iter).fold((), |_, (r, v)| *r = v);
    })
}

#[no_mangle]
pub extern "C" fn Servo_CssRules_InsertRule(
    rules: ServoCssRulesBorrowed,
    contents: RawServoStyleSheetContentsBorrowed,
    rule: *const nsACString,
    index: u32,
    nested: bool,
    loader: *mut Loader,
    gecko_stylesheet: *mut DomStyleSheet,
    rule_type: *mut u16,
) -> nsresult {
    let loader = if loader.is_null() {
        None
    } else {
        Some(StylesheetLoader::new(loader, gecko_stylesheet, ptr::null_mut(), ptr::null_mut()))
    };
    let loader = loader.as_ref().map(|loader| loader as &StyleStylesheetLoader);
    let rule = unsafe { rule.as_ref().unwrap().as_str_unchecked() };

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let contents = StylesheetContents::as_arc(&contents);
    let result = Locked::<CssRules>::as_arc(&rules).insert_rule(
        &global_style_data.shared_lock,
        rule,
        contents,
        index as usize,
        nested,
        loader
    );

    match result {
        Ok(new_rule) => {
            *unsafe { rule_type.as_mut().unwrap() } = new_rule.rule_type() as u16;
            nsresult::NS_OK
        }
        Err(err) => err.into(),
    }
}

#[no_mangle]
pub extern "C" fn Servo_CssRules_DeleteRule(
    rules: ServoCssRulesBorrowed,
    index: u32
) -> nsresult {
    write_locked_arc(rules, |rules: &mut CssRules| {
        match rules.remove_rule(index as usize) {
            Ok(_) => nsresult::NS_OK,
            Err(err) => err.into()
        }
    })
}

macro_rules! impl_basic_rule_funcs_without_getter {
    { ($rule_type:ty, $raw_type:ty),
        debug: $debug:ident,
        to_css: $to_css:ident,
    } => {
        #[cfg(debug_assertions)]
        #[no_mangle]
        pub extern "C" fn $debug(rule: &$raw_type, result: *mut nsACString) {
            read_locked_arc(rule, |rule: &$rule_type| {
                write!(unsafe { result.as_mut().unwrap() }, "{:?}", *rule).unwrap();
            })
        }

        #[cfg(not(debug_assertions))]
        #[no_mangle]
        pub extern "C" fn $debug(_: &$raw_type, _: *mut nsACString) {
            unreachable!()
        }

        #[no_mangle]
        pub extern "C" fn $to_css(rule: &$raw_type, result: *mut nsAString) {
            let global_style_data = &*GLOBAL_STYLE_DATA;
            let guard = global_style_data.shared_lock.read();
            let rule = Locked::<$rule_type>::as_arc(&rule);
            rule.read_with(&guard).to_css(&guard, unsafe { result.as_mut().unwrap() }).unwrap();
        }
    }
}

macro_rules! impl_basic_rule_funcs {
    { ($name:ident, $rule_type:ty, $raw_type:ty),
        getter: $getter:ident,
        debug: $debug:ident,
        to_css: $to_css:ident,
    } => {
        #[no_mangle]
        pub extern "C" fn $getter(
            rules: ServoCssRulesBorrowed,
            index: u32,
            line: *mut u32,
            column: *mut u32,
        ) -> Strong<$raw_type> {
            let global_style_data = &*GLOBAL_STYLE_DATA;
            let guard = global_style_data.shared_lock.read();
            let rules = Locked::<CssRules>::as_arc(&rules).read_with(&guard);
            let index = index as usize;

            if index >= rules.0.len() {
                return Strong::null();
            }

            match rules.0[index] {
                CssRule::$name(ref rule) => {
                    let location = rule.read_with(&guard).source_location;
                    *unsafe { line.as_mut().unwrap() } = location.line as u32;
                    *unsafe { column.as_mut().unwrap() } = location.column as u32;
                    rule.clone().into_strong()
                },
                _ => {
                    Strong::null()
                }
            }
        }

        impl_basic_rule_funcs_without_getter! { ($rule_type, $raw_type),
            debug: $debug,
            to_css: $to_css,
        }
    }
}

macro_rules! impl_group_rule_funcs {
    { ($name:ident, $rule_type:ty, $raw_type:ty),
      get_rules: $get_rules:ident,
      $($basic:tt)+
    } => {
        impl_basic_rule_funcs! { ($name, $rule_type, $raw_type), $($basic)+ }

        #[no_mangle]
        pub extern "C" fn $get_rules(rule: &$raw_type) -> ServoCssRulesStrong {
            read_locked_arc(rule, |rule: &$rule_type| {
                rule.rules.clone().into_strong()
            })
        }
    }
}

impl_basic_rule_funcs! { (Style, StyleRule, RawServoStyleRule),
    getter: Servo_CssRules_GetStyleRuleAt,
    debug: Servo_StyleRule_Debug,
    to_css: Servo_StyleRule_GetCssText,
}

impl_basic_rule_funcs! { (Import, ImportRule, RawServoImportRule),
    getter: Servo_CssRules_GetImportRuleAt,
    debug: Servo_ImportRule_Debug,
    to_css: Servo_ImportRule_GetCssText,
}

impl_basic_rule_funcs_without_getter! { (Keyframe, RawServoKeyframe),
    debug: Servo_Keyframe_Debug,
    to_css: Servo_Keyframe_GetCssText,
}

impl_basic_rule_funcs! { (Keyframes, KeyframesRule, RawServoKeyframesRule),
    getter: Servo_CssRules_GetKeyframesRuleAt,
    debug: Servo_KeyframesRule_Debug,
    to_css: Servo_KeyframesRule_GetCssText,
}

impl_group_rule_funcs! { (Media, MediaRule, RawServoMediaRule),
    get_rules: Servo_MediaRule_GetRules,
    getter: Servo_CssRules_GetMediaRuleAt,
    debug: Servo_MediaRule_Debug,
    to_css: Servo_MediaRule_GetCssText,
}

impl_basic_rule_funcs! { (Namespace, NamespaceRule, RawServoNamespaceRule),
    getter: Servo_CssRules_GetNamespaceRuleAt,
    debug: Servo_NamespaceRule_Debug,
    to_css: Servo_NamespaceRule_GetCssText,
}

impl_basic_rule_funcs! { (Page, PageRule, RawServoPageRule),
    getter: Servo_CssRules_GetPageRuleAt,
    debug: Servo_PageRule_Debug,
    to_css: Servo_PageRule_GetCssText,
}

impl_group_rule_funcs! { (Supports, SupportsRule, RawServoSupportsRule),
    get_rules: Servo_SupportsRule_GetRules,
    getter: Servo_CssRules_GetSupportsRuleAt,
    debug: Servo_SupportsRule_Debug,
    to_css: Servo_SupportsRule_GetCssText,
}

impl_group_rule_funcs! { (Document, DocumentRule, RawServoMozDocumentRule),
    get_rules: Servo_MozDocumentRule_GetRules,
    getter: Servo_CssRules_GetMozDocumentRuleAt,
    debug: Servo_MozDocumentRule_Debug,
    to_css: Servo_MozDocumentRule_GetCssText,
}

impl_basic_rule_funcs! { (FontFeatureValues, FontFeatureValuesRule, RawServoFontFeatureValuesRule),
    getter: Servo_CssRules_GetFontFeatureValuesRuleAt,
    debug: Servo_FontFeatureValuesRule_Debug,
    to_css: Servo_FontFeatureValuesRule_GetCssText,
}

impl_basic_rule_funcs! { (FontFace, FontFaceRule, RawServoFontFaceRule),
    getter: Servo_CssRules_GetFontFaceRuleAt,
    debug: Servo_FontFaceRule_Debug,
    to_css: Servo_FontFaceRule_GetCssText,
}

impl_basic_rule_funcs! { (CounterStyle, CounterStyleRule, RawServoCounterStyleRule),
    getter: Servo_CssRules_GetCounterStyleRuleAt,
    debug: Servo_CounterStyleRule_Debug,
    to_css: Servo_CounterStyleRule_GetCssText,
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_GetStyle(rule: RawServoStyleRuleBorrowed) -> RawServoDeclarationBlockStrong {
    read_locked_arc(rule, |rule: &StyleRule| {
        rule.block.clone().into_strong()
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_SetStyle(rule: RawServoStyleRuleBorrowed,
                                           declarations: RawServoDeclarationBlockBorrowed) {
    let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
    write_locked_arc(rule, |rule: &mut StyleRule| {
        rule.block = declarations.clone_arc();
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_GetSelectorText(rule: RawServoStyleRuleBorrowed, result: *mut nsAString) {
    read_locked_arc(rule, |rule: &StyleRule| {
        rule.selectors.to_css(unsafe { result.as_mut().unwrap() }).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_GetSelectorTextAtIndex(
    rule: RawServoStyleRuleBorrowed,
    index: u32,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &StyleRule| {
        let index = index as usize;
        if index >= rule.selectors.0.len() {
            return;
        }
        rule.selectors.0[index].to_css(unsafe { result.as_mut().unwrap() }).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_GetSelectorCount(rule: RawServoStyleRuleBorrowed, count: *mut u32) {
    read_locked_arc(rule, |rule: &StyleRule| {
        *unsafe { count.as_mut().unwrap() } = rule.selectors.0.len() as u32;
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_GetSpecificityAtIndex(
    rule: RawServoStyleRuleBorrowed,
    index: u32,
    specificity: *mut u64
) {
    read_locked_arc(rule, |rule: &StyleRule| {
        let specificity =  unsafe { specificity.as_mut().unwrap() };
        let index = index as usize;
        if index >= rule.selectors.0.len() {
            *specificity = 0;
            return;
        }
        *specificity = rule.selectors.0[index].specificity() as u64;
    })
}

#[no_mangle]
pub extern "C" fn Servo_StyleRule_SelectorMatchesElement(
    rule: RawServoStyleRuleBorrowed,
    element: RawGeckoElementBorrowed,
    index: u32,
    pseudo_type: CSSPseudoElementType,
) -> bool {
    read_locked_arc(rule, |rule: &StyleRule| {
        let index = index as usize;
        if index >= rule.selectors.0.len() {
            return false;
        }

        let selector = &rule.selectors.0[index];
        let mut matching_mode = MatchingMode::Normal;

        match PseudoElement::from_pseudo_type(pseudo_type) {
            Some(pseudo) => {
                // We need to make sure that the requested pseudo element type
                // matches the selector pseudo element type before proceeding.
                match selector.pseudo_element() {
                    Some(selector_pseudo) if *selector_pseudo == pseudo => {
                        matching_mode = MatchingMode::ForStatelessPseudoElement
                    },
                    _ => return false,
                };
            },
            None => {
                // Do not attempt to match if a pseudo element is requested and
                // this is not a pseudo element selector, or vice versa.
                if selector.has_pseudo_element() {
                    return false;
                }
            },
        };

        let element = GeckoElement(element);
        let quirks_mode = element.as_node().owner_doc().quirks_mode();
        let mut ctx =
            MatchingContext::new(matching_mode, None, None, quirks_mode);
        matches_selector(selector, 0, None, &element, &mut ctx, &mut |_, _| {})
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_StyleRule_SetSelectorText(
    sheet: RawServoStyleSheetContentsBorrowed,
    rule: RawServoStyleRuleBorrowed,
    text: *const nsAString,
) -> bool {
    let value_str = (*text).to_string();

    write_locked_arc(rule, |rule: &mut StyleRule| {
        use style::selector_parser::SelectorParser;

        let contents = StylesheetContents::as_arc(&sheet);
        let namespaces = contents.namespaces.read();
        let url_data = contents.url_data.read();
        let parser = SelectorParser {
            stylesheet_origin: contents.origin,
            namespaces: &namespaces,
            url_data: Some(&url_data),
        };

        let mut parser_input = ParserInput::new(&value_str);
        match SelectorList::parse(&parser, &mut Parser::new(&mut parser_input)) {
            Ok(selectors) => {
                rule.selectors = selectors;
                true
            }
            Err(_) => false,
        }
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_SelectorList_Closest(
    element: RawGeckoElementBorrowed,
    selectors: RawServoSelectorListBorrowed,
) -> *const structs::RawGeckoElement {
    use std::borrow::Borrow;
    use style::dom_apis;

    let element = GeckoElement(element);
    let quirks_mode = element.as_node().owner_doc().quirks_mode();
    let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();

    dom_apis::element_closest(element, &selectors, quirks_mode)
        .map_or(ptr::null(), |e| e.0)
}

#[no_mangle]
pub unsafe extern "C" fn Servo_SelectorList_Matches(
    element: RawGeckoElementBorrowed,
    selectors: RawServoSelectorListBorrowed,
) -> bool {
    use std::borrow::Borrow;
    use style::dom_apis;

    let element = GeckoElement(element);
    let quirks_mode = element.as_node().owner_doc().quirks_mode();
    let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
    dom_apis::element_matches(
        &element,
        &selectors,
        quirks_mode,
    )
}

#[no_mangle]
pub unsafe extern "C" fn Servo_SelectorList_QueryFirst(
    node: RawGeckoNodeBorrowed,
    selectors: RawServoSelectorListBorrowed,
    may_use_invalidation: bool,
) -> *const structs::RawGeckoElement {
    use std::borrow::Borrow;
    use style::dom_apis::{self, MayUseInvalidation, QueryFirst};

    let node = GeckoNode(node);
    let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
    let mut result = None;

    let may_use_invalidation =
        if may_use_invalidation {
            MayUseInvalidation::Yes
        } else {
            MayUseInvalidation::No
        };

    dom_apis::query_selector::<GeckoElement, QueryFirst>(
        node,
        &selectors,
        &mut result,
        may_use_invalidation,
    );

    result.map_or(ptr::null(), |e| e.0)
}

#[no_mangle]
pub unsafe extern "C" fn Servo_SelectorList_QueryAll(
    node: RawGeckoNodeBorrowed,
    selectors: RawServoSelectorListBorrowed,
    content_list: *mut structs::nsSimpleContentList,
    may_use_invalidation: bool,
) {
    use std::borrow::Borrow;
    use style::dom_apis::{self, MayUseInvalidation, QueryAll};

    let node = GeckoNode(node);
    let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
    let mut result = SmallVec::new();

    let may_use_invalidation =
        if may_use_invalidation {
            MayUseInvalidation::Yes
        } else {
            MayUseInvalidation::No
        };

    dom_apis::query_selector::<GeckoElement, QueryAll>(
        node,
        &selectors,
        &mut result,
        may_use_invalidation,
    );

    if !result.is_empty() {
        // NOTE(emilio): This relies on a slice of GeckoElement having the same
        // memory representation than a slice of element pointers.
        bindings::Gecko_ContentList_AppendAll(
            content_list,
            result.as_ptr() as *mut *const _,
            result.len(),
        )
    }
}

#[no_mangle]
pub extern "C" fn Servo_ImportRule_GetHref(rule: RawServoImportRuleBorrowed, result: *mut nsAString) {
    read_locked_arc(rule, |rule: &ImportRule| {
        write!(unsafe { &mut *result }, "{}", rule.url.as_str()).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_ImportRule_GetSheet(
    rule: RawServoImportRuleBorrowed,
) -> *const DomStyleSheet {
    read_locked_arc(rule, |rule: &ImportRule| {
        rule.stylesheet.as_sheet().unwrap().raw() as *const DomStyleSheet
    })
}

#[no_mangle]
pub extern "C" fn Servo_ImportRule_SetSheet(
    rule: RawServoImportRuleBorrowed,
    sheet: *mut DomStyleSheet,
) {
    write_locked_arc(rule, |rule: &mut ImportRule| {
        let sheet = unsafe { GeckoStyleSheet::new(sheet) };
        rule.stylesheet = ImportSheet::new(sheet);
    })
}

#[no_mangle]
pub extern "C" fn Servo_Keyframe_GetKeyText(
    keyframe: RawServoKeyframeBorrowed,
    result: *mut nsAString
) {
    read_locked_arc(keyframe, |keyframe: &Keyframe| {
        keyframe.selector.to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap()
    })
}

#[no_mangle]
pub extern "C" fn Servo_Keyframe_SetKeyText(keyframe: RawServoKeyframeBorrowed, text: *const nsACString) -> bool {
    let text = unsafe { text.as_ref().unwrap().as_str_unchecked() };
    let mut input = ParserInput::new(&text);
    if let Ok(selector) = Parser::new(&mut input).parse_entirely(KeyframeSelector::parse) {
        write_locked_arc(keyframe, |keyframe: &mut Keyframe| {
            keyframe.selector = selector;
        });
        true
    } else {
        false
    }
}

#[no_mangle]
pub extern "C" fn Servo_Keyframe_GetStyle(keyframe: RawServoKeyframeBorrowed) -> RawServoDeclarationBlockStrong {
    read_locked_arc(keyframe, |keyframe: &Keyframe| keyframe.block.clone().into_strong())
}

#[no_mangle]
pub extern "C" fn Servo_Keyframe_SetStyle(keyframe: RawServoKeyframeBorrowed,
                                          declarations: RawServoDeclarationBlockBorrowed) {
    let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
    write_locked_arc(keyframe, |keyframe: &mut Keyframe| {
        keyframe.block = declarations.clone_arc();
    })
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_GetName(rule: RawServoKeyframesRuleBorrowed) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &KeyframesRule| rule.name.as_atom().as_ptr())
}

#[no_mangle]
pub unsafe extern "C" fn Servo_KeyframesRule_SetName(rule: RawServoKeyframesRuleBorrowed, name: *mut nsAtom) {
    write_locked_arc(rule, |rule: &mut KeyframesRule| {
        rule.name = KeyframesName::Ident(CustomIdent(Atom::from_addrefed(name)));
    })
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_GetCount(rule: RawServoKeyframesRuleBorrowed) -> u32 {
    read_locked_arc(rule, |rule: &KeyframesRule| rule.keyframes.len() as u32)
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_GetKeyframeAt(
    rule: RawServoKeyframesRuleBorrowed,
    index: u32,
    line: *mut u32,
    column: *mut u32,
) -> RawServoKeyframeStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let key = Locked::<KeyframesRule>::as_arc(&rule).read_with(&guard)
                  .keyframes[index as usize].clone();
    let location = key.read_with(&guard).source_location;
    *unsafe { line.as_mut().unwrap() } = location.line as u32;
    *unsafe { column.as_mut().unwrap() } = location.column as u32;
    key.into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_FindRule(
    rule: RawServoKeyframesRuleBorrowed,
    key: *const nsACString,
) -> u32 {
    let key = unsafe { key.as_ref().unwrap().as_str_unchecked() };
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    Locked::<KeyframesRule>::as_arc(&rule).read_with(&guard)
        .find_rule(&guard, key).map(|index| index as u32)
        .unwrap_or(u32::max_value())
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_AppendRule(
    rule: RawServoKeyframesRuleBorrowed,
    contents: RawServoStyleSheetContentsBorrowed,
    css: *const nsACString
) -> bool {
    let css = unsafe { css.as_ref().unwrap().as_str_unchecked() };
    let contents = StylesheetContents::as_arc(&contents);
    let global_style_data = &*GLOBAL_STYLE_DATA;

    match Keyframe::parse(css, &contents, &global_style_data.shared_lock) {
        Ok(keyframe) => {
            write_locked_arc(rule, |rule: &mut KeyframesRule| {
                rule.keyframes.push(keyframe);
            });
            true
        }
        Err(..) => false,
    }
}

#[no_mangle]
pub extern "C" fn Servo_KeyframesRule_DeleteRule(rule: RawServoKeyframesRuleBorrowed, index: u32) {
    write_locked_arc(rule, |rule: &mut KeyframesRule| {
        rule.keyframes.remove(index as usize);
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaRule_GetMedia(rule: RawServoMediaRuleBorrowed) -> RawServoMediaListStrong {
    read_locked_arc(rule, |rule: &MediaRule| {
        rule.media_queries.clone().into_strong()
    })
}

#[no_mangle]
pub extern "C" fn Servo_NamespaceRule_GetPrefix(rule: RawServoNamespaceRuleBorrowed) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &NamespaceRule| {
        rule.prefix.as_ref().unwrap_or(&atom!("")).as_ptr()
    })
}

#[no_mangle]
pub extern "C" fn Servo_NamespaceRule_GetURI(rule: RawServoNamespaceRuleBorrowed) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &NamespaceRule| rule.url.0.as_ptr())
}

#[no_mangle]
pub extern "C" fn Servo_PageRule_GetStyle(rule: RawServoPageRuleBorrowed) -> RawServoDeclarationBlockStrong {
    read_locked_arc(rule, |rule: &PageRule| {
        rule.block.clone().into_strong()
    })
}

#[no_mangle]
pub extern "C" fn Servo_PageRule_SetStyle(
    rule: RawServoPageRuleBorrowed,
    declarations: RawServoDeclarationBlockBorrowed,
) {
    let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
    write_locked_arc(rule, |rule: &mut PageRule| {
        rule.block = declarations.clone_arc();
    })
}

#[no_mangle]
pub extern "C" fn Servo_SupportsRule_GetConditionText(
    rule: RawServoSupportsRuleBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &SupportsRule| {
        rule.condition.to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_MozDocumentRule_GetConditionText(
    rule: RawServoMozDocumentRuleBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &DocumentRule| {
        rule.condition.to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_FontFeatureValuesRule_GetFontFamily(
    rule: RawServoFontFeatureValuesRuleBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &FontFeatureValuesRule| {
        rule.font_family_to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_FontFeatureValuesRule_GetValueText(
    rule: RawServoFontFeatureValuesRuleBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &FontFeatureValuesRule| {
        rule.value_to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
    })
}

#[no_mangle]
pub extern "C" fn Servo_FontFaceRule_CreateEmpty() -> RawServoFontFaceRuleStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    // XXX This is not great. We should split FontFace descriptor data
    // from the rule, so that we don't need to create the rule like this
    // and the descriptor data itself can be hold in UniquePtr from the
    // Gecko side. See bug 1450904.
    Arc::new(global_style_data.shared_lock.wrap(FontFaceRule::empty(SourceLocation {
        line: 0,
        column: 0,
    }))).into_strong()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_Clone(
    rule: RawServoFontFaceRuleBorrowed,
) -> RawServoFontFaceRuleStrong {
    let clone = read_locked_arc(rule, |rule: &FontFaceRule| rule.clone());
    let global_style_data = &*GLOBAL_STYLE_DATA;
    Arc::new(global_style_data.shared_lock.wrap(clone)).into_strong()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_GetSourceLocation(
    rule: RawServoFontFaceRuleBorrowed,
    line: *mut u32,
    column: *mut u32,
) {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        let location = rule.source_location;
        *line.as_mut().unwrap() = location.line as u32;
        *column.as_mut().unwrap() = location.column as u32;
    });
}

macro_rules! apply_font_desc_list {
    ($apply_macro:ident) => {
        $apply_macro! {
            valid: [
                eCSSFontDesc_Family => family,
                eCSSFontDesc_Style => style,
                eCSSFontDesc_Weight => weight,
                eCSSFontDesc_Stretch => stretch,
                eCSSFontDesc_Src => sources,
                eCSSFontDesc_UnicodeRange => unicode_range,
                eCSSFontDesc_FontFeatureSettings => feature_settings,
                eCSSFontDesc_FontVariationSettings => variation_settings,
                eCSSFontDesc_FontLanguageOverride => language_override,
                eCSSFontDesc_Display => display,
            ]
            invalid: [
                eCSSFontDesc_UNKNOWN,
                eCSSFontDesc_COUNT,
            ]
        }
    }
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_Length(
    rule: RawServoFontFaceRuleBorrowed,
) -> u32 {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        let mut result = 0;
        macro_rules! count_values {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                $(if rule.$field.is_some() {
                    result += 1;
                })*
            }
        }
        apply_font_desc_list!(count_values);
        result
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_IndexGetter(
    rule: RawServoFontFaceRuleBorrowed,
    index: u32,
) -> nsCSSFontDesc {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        let mut count = 0;
        macro_rules! lookup_index {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                $(if rule.$field.is_some() {
                    count += 1;
                    if count - 1 == index {
                        return nsCSSFontDesc::$v_enum_name;
                    }
                })*
            }
        }
        apply_font_desc_list!(lookup_index);
        return nsCSSFontDesc::eCSSFontDesc_UNKNOWN;
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_GetDeclCssText(
    rule: RawServoFontFaceRuleBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        rule.decl_to_css(result.as_mut().unwrap()).unwrap();
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_GetDescriptor(
    rule: RawServoFontFaceRuleBorrowed,
    desc: nsCSSFontDesc,
    result: nsCSSValueBorrowedMut,
) {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        macro_rules! to_css_value {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                match desc {
                    $(
                        nsCSSFontDesc::$v_enum_name => {
                            if let Some(ref value) = rule.$field {
                                result.set_from(value);
                            }
                        }
                    )*
                    $(
                        nsCSSFontDesc::$i_enum_name => {
                            debug_assert!(false, "not a valid font descriptor");
                        }
                    )*
                }
            }
        }
        apply_font_desc_list!(to_css_value)
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_GetDescriptorCssText(
    rule: RawServoFontFaceRuleBorrowed,
    desc: nsCSSFontDesc,
    result: *mut nsAString,
) {
    read_locked_arc(rule, |rule: &FontFaceRule| {
        let mut writer = CssWriter::new(result.as_mut().unwrap());
        macro_rules! to_css_text {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                match desc {
                    $(
                        nsCSSFontDesc::$v_enum_name => {
                            if let Some(ref value) = rule.$field {
                                value.to_css(&mut writer).unwrap();
                            }
                        }
                    )*
                    $(
                        nsCSSFontDesc::$i_enum_name => {
                            debug_assert!(false, "not a valid font descriptor");
                        }
                    )*
                }
            }
        }
        apply_font_desc_list!(to_css_text)
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_SetDescriptor(
    rule: RawServoFontFaceRuleBorrowed,
    desc: nsCSSFontDesc,
    value: *const nsACString,
    data: *mut URLExtraData,
) -> bool {
    let value = value.as_ref().unwrap().as_str_unchecked();
    let mut input = ParserInput::new(&value);
    let mut parser = Parser::new(&mut input);
    let url_data = UrlExtraData::from_ptr_ref(&data);
    let context = ParserContext::new(
        Origin::Author,
        url_data,
        Some(CssRuleType::FontFace),
        ParsingMode::DEFAULT,
        QuirksMode::NoQuirks,
        None,
    );

    write_locked_arc(rule, |rule: &mut FontFaceRule| {
        macro_rules! to_css_text {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                match desc {
                    $(
                        nsCSSFontDesc::$v_enum_name => {
                            if let Ok(value) = parser.parse_entirely(|i| Parse::parse(&context, i)) {
                                rule.$field = Some(value);
                                true
                            } else {
                                false
                            }
                        }
                    )*
                    $(
                        nsCSSFontDesc::$i_enum_name => {
                            debug_assert!(false, "not a valid font descriptor");
                            false
                        }
                    )*
                }
            }
        }
        apply_font_desc_list!(to_css_text)
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_FontFaceRule_ResetDescriptor(
    rule: RawServoFontFaceRuleBorrowed,
    desc: nsCSSFontDesc,
) {
    write_locked_arc(rule, |rule: &mut FontFaceRule| {
        macro_rules! reset_desc {
            (
                valid: [$($v_enum_name:ident => $field:ident,)*]
                invalid: [$($i_enum_name:ident,)*]
            ) => {
                match desc {
                    $(nsCSSFontDesc::$v_enum_name => rule.$field = None,)*
                    $(nsCSSFontDesc::$i_enum_name => debug_assert!(false, "not a valid font descriptor"),)*
                }
            }
        }
        apply_font_desc_list!(reset_desc)
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetName(
    rule: RawServoCounterStyleRuleBorrowed,
) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        rule.name().0.as_ptr()
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_SetName(
    rule: RawServoCounterStyleRuleBorrowed,
    value: *const nsACString,
) -> bool {
    let value = value.as_ref().unwrap().as_str_unchecked();
    let mut input = ParserInput::new(&value);
    let mut parser = Parser::new(&mut input);
    match parser.parse_entirely(counter_style::parse_counter_style_name_definition) {
        Ok(name) => {
            write_locked_arc(rule, |rule: &mut CounterStyleRule| rule.set_name(name));
            true
        }
        Err(_) => false,
    }
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetGeneration(
    rule: RawServoCounterStyleRuleBorrowed,
) -> u32 {
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        rule.generation()
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetSystem(
    rule: RawServoCounterStyleRuleBorrowed,
) -> u8 {
    use style::counter_style::System;
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        match *rule.resolved_system() {
            System::Cyclic => structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC,
            System::Numeric => structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC,
            System::Alphabetic => structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC,
            System::Symbolic => structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC,
            System::Additive => structs::NS_STYLE_COUNTER_SYSTEM_ADDITIVE,
            System::Fixed { .. } => structs::NS_STYLE_COUNTER_SYSTEM_FIXED,
            System::Extends(_) => structs::NS_STYLE_COUNTER_SYSTEM_EXTENDS,
        }
    }) as u8
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetExtended(
    rule: RawServoCounterStyleRuleBorrowed,
) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        match *rule.resolved_system() {
            counter_style::System::Extends(ref name) => name.0.as_ptr(),
            _ => {
                debug_assert!(false, "Not extends system");
                ptr::null_mut()
            }
        }
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetFixedFirstValue(
    rule: RawServoCounterStyleRuleBorrowed,
) -> i32 {
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        match *rule.resolved_system() {
            counter_style::System::Fixed { first_symbol_value } => {
                first_symbol_value.map_or(1, |v| v.value())
            }
            _ => {
                debug_assert!(false, "Not fixed system");
                0
            }
        }
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_CounterStyleRule_GetFallback(
    rule: RawServoCounterStyleRuleBorrowed,
) -> *mut nsAtom {
    read_locked_arc(rule, |rule: &CounterStyleRule| {
        rule.fallback().map_or(ptr::null_mut(), |i| i.0 .0.as_ptr())
    })
}

macro_rules! counter_style_descriptors {
    {
        valid: [
            $($desc:ident => $getter:ident / $setter:ident,)+
        ]
        invalid: [
            $($i_desc:ident,)+
        ]
    } => {
        #[no_mangle]
        pub unsafe extern "C" fn Servo_CounterStyleRule_GetDescriptor(
            rule: RawServoCounterStyleRuleBorrowed,
            desc: nsCSSCounterDesc,
            result: nsCSSValueBorrowedMut,
        ) {
            read_locked_arc(rule, |rule: &CounterStyleRule| {
                match desc {
                    $(nsCSSCounterDesc::$desc => {
                        if let Some(value) = rule.$getter() {
                            result.set_from(value);
                        }
                    })+
                    $(nsCSSCounterDesc::$i_desc => unreachable!(),)+
                }
            });
        }

        #[no_mangle]
        pub unsafe extern "C" fn Servo_CounterStyleRule_GetDescriptorCssText(
            rule: RawServoCounterStyleRuleBorrowed,
            desc: nsCSSCounterDesc,
            result: *mut nsAString,
        ) {
            let mut writer = CssWriter::new(result.as_mut().unwrap());
            read_locked_arc(rule, |rule: &CounterStyleRule| {
                match desc {
                    $(nsCSSCounterDesc::$desc => {
                        if let Some(value) = rule.$getter() {
                            value.to_css(&mut writer).unwrap();
                        }
                    })+
                    $(nsCSSCounterDesc::$i_desc => unreachable!(),)+
                }
            });
        }

        #[no_mangle]
        pub unsafe extern "C" fn Servo_CounterStyleRule_SetDescriptor(
            rule: RawServoCounterStyleRuleBorrowed,
            desc: nsCSSCounterDesc,
            value: *const nsACString,
        ) -> bool {
            let value = value.as_ref().unwrap().as_str_unchecked();
            let mut input = ParserInput::new(&value);
            let mut parser = Parser::new(&mut input);
            let url_data = dummy_url_data();
            let context = ParserContext::new(
                Origin::Author,
                url_data,
                Some(CssRuleType::CounterStyle),
                ParsingMode::DEFAULT,
                QuirksMode::NoQuirks,
                None,
            );

            write_locked_arc(rule, |rule: &mut CounterStyleRule| {
                match desc {
                    $(nsCSSCounterDesc::$desc => {
                        match parser.parse_entirely(|i| Parse::parse(&context, i)) {
                            Ok(value) => rule.$setter(value),
                            Err(_) => false,
                        }
                    })+
                    $(nsCSSCounterDesc::$i_desc => unreachable!(),)+
                }
            })
        }
    }
}

counter_style_descriptors! {
    valid: [
        eCSSCounterDesc_System => system / set_system,
        eCSSCounterDesc_Symbols => symbols / set_symbols,
        eCSSCounterDesc_AdditiveSymbols => additive_symbols / set_additive_symbols,
        eCSSCounterDesc_Negative => negative / set_negative,
        eCSSCounterDesc_Prefix => prefix / set_prefix,
        eCSSCounterDesc_Suffix => suffix / set_suffix,
        eCSSCounterDesc_Range => range / set_range,
        eCSSCounterDesc_Pad => pad / set_pad,
        eCSSCounterDesc_Fallback => fallback / set_fallback,
        eCSSCounterDesc_SpeakAs => speak_as / set_speak_as,
    ]
    invalid: [
        eCSSCounterDesc_UNKNOWN,
        eCSSCounterDesc_COUNT,
    ]
}

#[no_mangle]
pub unsafe extern "C" fn Servo_ComputedValues_GetForAnonymousBox(
    parent_style_or_null: ComputedStyleBorrowedOrNull,
    pseudo_tag: *mut nsAtom,
    raw_data: RawServoStyleSetBorrowed,
) -> ComputedStyleStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let guards = StylesheetGuards::same(&guard);
    let data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let atom = Atom::from_raw(pseudo_tag);
    let pseudo = PseudoElement::from_anon_box_atom(&atom)
        .expect("Not an anon box pseudo?");

    let metrics = get_metrics_provider_for_product();

    // If the pseudo element is PageContent, we should append the precomputed
    // pseudo element declerations with specified page rules.
    let page_decls = match pseudo {
        PseudoElement::PageContent => {
            let mut declarations = vec![];
            let iter = data.stylist.iter_extra_data_origins_rev();
            for (data, origin) in iter {
                let level = match origin {
                    Origin::UserAgent => CascadeLevel::UANormal,
                    Origin::User => CascadeLevel::UserNormal,
                    Origin::Author => CascadeLevel::SameTreeAuthorNormal,
                };
                for rule in data.pages.iter() {
                    declarations.push(ApplicableDeclarationBlock::from_declarations(
                        rule.read_with(level.guard(&guards)).block.clone(),
                        level
                    ));
                }
            }
            Some(declarations)
        },
        _ => None,
    };

    let rule_node = data.stylist.rule_node_for_precomputed_pseudo(
        &guards,
        &pseudo,
        page_decls,
    );

    data.stylist.precomputed_values_for_pseudo_with_rule_node::<GeckoElement>(
        &guards,
        &pseudo,
        parent_style_or_null.map(|x| &*x),
        &metrics,
        rule_node
    ).into()
}

#[no_mangle]
pub extern "C" fn Servo_ResolvePseudoStyle(
    element: RawGeckoElementBorrowed,
    pseudo_type: CSSPseudoElementType,
    is_probe: bool,
    inherited_style: ComputedStyleBorrowedOrNull,
    raw_data: RawServoStyleSetBorrowed,
) -> ComputedStyleStrong {
    let element = GeckoElement(element);
    let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();

    debug!("Servo_ResolvePseudoStyle: {:?} {:?}, is_probe: {}",
           element, PseudoElement::from_pseudo_type(pseudo_type), is_probe);

    let data = element.borrow_data();

    let data = match data.as_ref() {
        Some(data) if data.has_styles() => data,
        _ => {
            // FIXME(bholley, emilio): Assert against this.
            //
            // Known offender is nsMathMLmoFrame::MarkIntrinsicISizesDirty,
            // which goes and does a bunch of work involving style resolution.
            //
            // Bug 1403865 tracks fixing it, and potentially adding an assert
            // here instead.
            warn!("Calling Servo_ResolvePseudoStyle on unstyled element");
            return if is_probe {
                Strong::null()
            } else {
                doc_data.default_computed_values().clone().into()
            };
        }
    };

    let pseudo = PseudoElement::from_pseudo_type(pseudo_type)
                    .expect("ResolvePseudoStyle with a non-pseudo?");

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let style = get_pseudo_style(
        &guard,
        element,
        &pseudo,
        RuleInclusion::All,
        &data.styles,
        inherited_style,
        &*doc_data,
        is_probe,
        /* matching_func = */ None,
    );

    match style {
        Some(s) => s.into(),
        None => {
            debug_assert!(is_probe);
            Strong::null()
        }
    }
}

fn debug_atom_array(atoms: &AtomArray) -> String {
    let mut result = String::from("[");
    for atom in atoms.iter() {
        if atom.mRawPtr.is_null() {
            result += "(null), ";
        } else {
            let atom = unsafe { WeakAtom::new(atom.mRawPtr) };
            write!(result, "{}, ", atom).unwrap();
        }
    }
    result.push(']');
    result
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_ResolveXULTreePseudoStyle(
    element: RawGeckoElementBorrowed,
    pseudo_tag: *mut nsAtom,
    inherited_style: ComputedStyleBorrowed,
    input_word: *const AtomArray,
    raw_data: RawServoStyleSetBorrowed
) -> ComputedStyleStrong {
    let element = GeckoElement(element);
    let data = element.borrow_data()
        .expect("Calling ResolveXULTreePseudoStyle on unstyled element?");

    let pseudo = unsafe {
        Atom::with(pseudo_tag, |atom| {
            PseudoElement::from_tree_pseudo_atom(atom, Box::new([]))
        }).expect("ResolveXULTreePseudoStyle with a non-tree pseudo?")
    };
    let input_word = unsafe { input_word.as_ref().unwrap() };

    let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();

    debug!("ResolveXULTreePseudoStyle: {:?} {:?} {}",
           element, pseudo, debug_atom_array(input_word));

    let matching_fn = |pseudo: &PseudoElement| {
        let args = pseudo.tree_pseudo_args().expect("Not a tree pseudo-element?");
        args.iter().all(|atom| {
            input_word.iter().any(|item| atom.as_ptr() == item.mRawPtr)
        })
    };

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    get_pseudo_style(
        &guard,
        element,
        &pseudo,
        RuleInclusion::All,
        &data.styles,
        Some(inherited_style),
        &*doc_data,
        /* is_probe = */ false,
        Some(&matching_fn),
    ).unwrap().into()
}

#[no_mangle]
pub extern "C" fn Servo_SetExplicitStyle(
    element: RawGeckoElementBorrowed,
    style: ComputedStyleBorrowed,
) {
    let element = GeckoElement(element);
    debug!("Servo_SetExplicitStyle: {:?}", element);
    // We only support this API for initial styling. There's no reason it couldn't
    // work for other things, we just haven't had a reason to do so.
    debug_assert!(element.get_data().is_none());
    let mut data = unsafe { element.ensure_data() };
    data.styles.primary = Some(unsafe { ArcBorrow::from_ref(style) }.clone_arc());
}

#[no_mangle]
pub extern "C" fn Servo_HasAuthorSpecifiedRules(
    style: ComputedStyleBorrowed,
    element: RawGeckoElementBorrowed,
    pseudo_type: CSSPseudoElementType,
    rule_type_mask: u32,
    author_colors_allowed: bool,
) -> bool {
    let element = GeckoElement(element);
    let pseudo = PseudoElement::from_pseudo_type(pseudo_type);

    let guard = (*GLOBAL_STYLE_DATA).shared_lock.read();
    let guards = StylesheetGuards::same(&guard);

    style.rules().has_author_specified_rules(element,
                                             pseudo,
                                             &guards,
                                             rule_type_mask,
                                             author_colors_allowed)
}

fn get_pseudo_style(
    guard: &SharedRwLockReadGuard,
    element: GeckoElement,
    pseudo: &PseudoElement,
    rule_inclusion: RuleInclusion,
    styles: &ElementStyles,
    inherited_styles: Option<&ComputedValues>,
    doc_data: &PerDocumentStyleDataImpl,
    is_probe: bool,
    matching_func: Option<&Fn(&PseudoElement) -> bool>,
) -> Option<Arc<ComputedValues>> {
    let style = match pseudo.cascade_type() {
        PseudoElementCascadeType::Eager => {
            match *pseudo {
                PseudoElement::FirstLetter => {
                    styles.pseudos.get(&pseudo).map(|pseudo_styles| {
                        // inherited_styles can be None when doing lazy resolution
                        // (e.g. for computed style) or when probing.  In that case
                        // we just inherit from our element, which is what Gecko
                        // does in that situation.  What should actually happen in
                        // the computed style case is a bit unclear.
                        let inherited_styles =
                            inherited_styles.unwrap_or(styles.primary());
                        let guards = StylesheetGuards::same(guard);
                        let metrics = get_metrics_provider_for_product();
                        let inputs = CascadeInputs::new_from_style(pseudo_styles);
                        doc_data.stylist.compute_pseudo_element_style_with_inputs(
                            inputs,
                            pseudo,
                            &guards,
                            Some(inherited_styles),
                            &metrics,
                            Some(element),
                        )
                    })
                },
                _ => {
                    // Unfortunately, we can't assert that inherited_styles, if
                    // present, is pointer-equal to styles.primary(), or even
                    // equal in any meaningful way.  The way it can fail is as
                    // follows.  Say we append an element with a ::before,
                    // ::after, or ::first-line to a parent with a ::first-line,
                    // such that the element ends up on the first line of the
                    // parent (e.g. it's an inline-block in the case it has a
                    // ::first-line, or any container in the ::before/::after
                    // cases).  Then gecko will update its frame's style to
                    // inherit from the parent's ::first-line.  The next time we
                    // try to get the ::before/::after/::first-line style for
                    // the kid, we'll likely pass in the frame's style as
                    // inherited_styles, but that's not pointer-identical to
                    // styles.primary(), because it got reparented.
                    //
                    // Now in practice this turns out to be OK, because all the
                    // cases in which there's a mismatch go ahead and reparent
                    // styles again as needed to make sure the ::first-line
                    // affects all the things it should affect.  But it makes it
                    // impossible to assert anything about the two styles
                    // matching here, unfortunately.
                    styles.pseudos.get(&pseudo).cloned()
                },
            }
        }
        PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
        PseudoElementCascadeType::Lazy => {
            debug_assert!(inherited_styles.is_none() ||
                          ptr::eq(inherited_styles.unwrap(),
                                  &**styles.primary()));
            let base = if pseudo.inherits_from_default_values() {
                doc_data.default_computed_values()
            } else {
                styles.primary()
            };
            let guards = StylesheetGuards::same(guard);
            let metrics = get_metrics_provider_for_product();
            doc_data.stylist.lazily_compute_pseudo_element_style(
                &guards,
                element,
                &pseudo,
                rule_inclusion,
                base,
                is_probe,
                &metrics,
                matching_func,
            )
        },
    };

    if is_probe {
        return style;
    }

    Some(style.unwrap_or_else(|| {
        StyleBuilder::for_inheritance(
            doc_data.stylist.device(),
            Some(styles.primary()),
            Some(pseudo),
        ).build()
    }))
}

#[no_mangle]
pub unsafe extern "C" fn Servo_ComputedValues_Inherit(
    raw_data: RawServoStyleSetBorrowed,
    pseudo_tag: *mut nsAtom,
    parent_style_context: ComputedStyleBorrowedOrNull,
    target: structs::InheritTarget
) -> ComputedStyleStrong {
    let data = PerDocumentStyleData::from_ffi(raw_data).borrow();

    let for_text = target == structs::InheritTarget::Text;
    let atom = Atom::from_raw(pseudo_tag);
    let pseudo = PseudoElement::from_anon_box_atom(&atom)
        .expect("Not an anon-box? Gah!");

    let mut style = StyleBuilder::for_inheritance(
        data.stylist.device(),
        parent_style_context,
        Some(&pseudo)
    );

    if for_text {
        StyleAdjuster::new(&mut style).adjust_for_text();
    }

    style.build().into()
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ComputedStyleBorrowed) -> u8 {
    use style::properties::computed_value_flags::ComputedValueFlags;
    // FIXME(emilio): We could do this more efficiently I'm quite sure.
    let flags = values.flags;
    let mut result = 0;
    if flags.contains(ComputedValueFlags::IS_RELEVANT_LINK_VISITED) {
        result |= structs::ComputedStyleBit_RelevantLinkVisited;
    }
    if flags.contains(ComputedValueFlags::HAS_TEXT_DECORATION_LINES) {
        result |= structs::ComputedStyleBit_HasTextDecorationLines;
    }
    if flags.contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK) {
        result |= structs::ComputedStyleBit_SuppressLineBreak;
    }
    if flags.contains(ComputedValueFlags::IS_TEXT_COMBINED) {
        result |= structs::ComputedStyleBit_IsTextCombined;
    }
    if flags.contains(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE) {
        result |= structs::ComputedStyleBit_HasPseudoElementData;
    }
    result
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_SpecifiesAnimationsOrTransitions(
    values: ComputedStyleBorrowed,
) -> bool {
    let b = values.get_box();
    b.specifies_animations() || b.specifies_transitions()
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_EqualCustomProperties(
    first: ServoComputedDataBorrowed,
    second: ServoComputedDataBorrowed
) -> bool {
    first.custom_properties == second.custom_properties
}

#[no_mangle]
pub extern "C" fn Servo_ComputedValues_GetStyleRuleList(
    values: ComputedStyleBorrowed,
    rules: RawGeckoServoStyleRuleListBorrowedMut,
) {
    let rule_node = match values.rules {
        Some(ref r) => r,
        None => return,
    };

    let mut result = SmallVec::<[_; 10]>::new();
    for node in rule_node.self_and_ancestors() {
        let style_rule = match node.style_source().and_then(|x| x.as_rule()) {
            Some(rule) => rule,
            _ => continue,
        };

        // For the rules with any important declaration, we insert them into
        // rule tree twice, one for normal level and another for important
        // level. So, we skip the important one to keep the specificity order of
        // rules.
        if node.importance().important() {
            continue;
        }

        result.push(style_rule);
    }

    unsafe { rules.set_len(result.len() as u32) };
    for (ref src, ref mut dest) in result.into_iter().zip(rules.iter_mut()) {
        src.with_arc(|a| {
            a.with_raw_offset_arc(|arc| {
                **dest = *Locked::<StyleRule>::arc_as_borrowed(arc);
            })
        });
    }
}

/// See the comment in `Device` to see why it's ok to pass an owned reference to
/// the pres context (hint: the context outlives the StyleSet, that holds the
/// device alive).
#[no_mangle]
pub extern "C" fn Servo_StyleSet_Init(
    pres_context: RawGeckoPresContextOwned,
) -> *mut RawServoStyleSet {
    let data = Box::new(PerDocumentStyleData::new(pres_context));
    Box::into_raw(data) as *mut RawServoStyleSet
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_RebuildCachedData(raw_data: RawServoStyleSetBorrowed) {
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    data.stylist.device_mut().rebuild_cached_data();
}

#[no_mangle]
pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
    let _ = data.into_box::<PerDocumentStyleData>();
}

#[no_mangle]
pub unsafe extern "C" fn Servo_StyleSet_CompatModeChanged(raw_data: RawServoStyleSetBorrowed) {
    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
    let doc =
        &*data.stylist.device().pres_context().mDocument.raw::<nsIDocument>();
    data.stylist.set_quirks_mode(QuirksMode::from(doc.mCompatMode));
}

fn parse_property_into(
    declarations: &mut SourcePropertyDeclaration,
    property_id: PropertyId,
    value: *const nsACString,
    data: *mut URLExtraData,
    parsing_mode: structs::ParsingMode,
    quirks_mode: QuirksMode,
    reporter: Option<&ParseErrorReporter>,
) -> Result<(), ()> {
    use style_traits::ParsingMode;
    let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
    let url_data = unsafe { UrlExtraData::from_ptr_ref(&data) };
    let parsing_mode = ParsingMode::from_bits_truncate(parsing_mode);

    parse_one_declaration_into(
        declarations,
        property_id,
        value,
        url_data,
        reporter,
        parsing_mode,
        quirks_mode,
    )
}

#[no_mangle]
pub extern "C" fn Servo_ParseProperty(
    property: nsCSSPropertyID, value: *const nsACString,
    data: *mut URLExtraData,
    parsing_mode: structs::ParsingMode,
    quirks_mode: nsCompatibility,
    loader: *mut Loader,
) -> RawServoDeclarationBlockStrong {
    let id = get_property_id_from_nscsspropertyid!(property,
                                                   RawServoDeclarationBlockStrong::null());
    let mut declarations = SourcePropertyDeclaration::new();
    let reporter = ErrorReporter::new(ptr::null_mut(), loader, data);
    let result = parse_property_into(
        &mut declarations,
        id,
        value,
        data,
        parsing_mode,
        quirks_mode.into(),
        reporter.as_ref().map(|r| r as &ParseErrorReporter),
    );

    match result {
        Ok(()) => {
            let global_style_data = &*GLOBAL_STYLE_DATA;
            let mut block = PropertyDeclarationBlock::new();
            block.extend(
                declarations.drain(),
                Importance::Normal,
            );
            Arc::new(global_style_data.shared_lock.wrap(block)).into_strong()
        }
        Err(_) => RawServoDeclarationBlockStrong::null()
    }
}

#[no_mangle]
pub extern "C" fn Servo_ParseEasing(
    easing: *const nsAString,
    data: *mut URLExtraData,
    output: nsTimingFunctionBorrowedMut
) -> bool {
    use style::properties::longhands::transition_timing_function;

    // FIXME Dummy URL data would work fine here.
    let url_data = unsafe { UrlExtraData::from_ptr_ref(&data) };
    let context = ParserContext::new(
        Origin::Author,
        url_data,
        Some(CssRuleType::Style),
        ParsingMode::DEFAULT,
        QuirksMode::NoQuirks,
        None,
    );
    let easing = unsafe { (*easing).to_string() };
    let mut input = ParserInput::new(&easing);
    let mut parser = Parser::new(&mut input);
    let result =
        parser.parse_entirely(|p| transition_timing_function::single_value::parse(&context, p));
    match result {
        Ok(parsed_easing) => {
            *output = parsed_easing.into();
            true
        },
        Err(_) => false
    }
}

#[no_mangle]
pub extern "C" fn Servo_GetProperties_Overriding_Animation(
    element: RawGeckoElementBorrowed,
    list: RawGeckoCSSPropertyIDListBorrowed,
    set: nsCSSPropertyIDSetBorrowedMut,
) {
    let element = GeckoElement(element);
    let element_data = match element.borrow_data() {
        Some(data) => data,
        None => return
    };
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let guards = StylesheetGuards::same(&guard);
    let (overridden, custom) =
        element_data.styles.primary().rules().get_properties_overriding_animations(&guards);
    for p in list.iter() {
        match PropertyId::from_nscsspropertyid(*p) {
            Ok(property) => {
                if let PropertyId::Longhand(id) = property {
                    if overridden.contains(id) {
                        unsafe { Gecko_AddPropertyToSet(set, *p) };
                    }
                }
            },
            Err(_) => {
                if *p == nsCSSPropertyID::eCSSPropertyExtra_variable && custom {
                    unsafe { Gecko_AddPropertyToSet(set, *p) };
                }
            }
        }
    }
}

#[no_mangle]
pub extern "C" fn Servo_MatrixTransform_Operate(matrix_operator: MatrixTransformOperator,
                                                from: *const RawGeckoGfxMatrix4x4,
                                                to: *const RawGeckoGfxMatrix4x4,
                                                progress: f64,
                                                output: *mut RawGeckoGfxMatrix4x4) {
    use self::MatrixTransformOperator::{Accumulate, Interpolate};
    use style::values::computed::transform::Matrix3D;

    let from = Matrix3D::from(unsafe { from.as_ref() }.expect("not a valid 'from' matrix"));
    let to = Matrix3D::from(unsafe { to.as_ref() }.expect("not a valid 'to' matrix"));
    let result = match matrix_operator {
        Interpolate => from.animate(&to, Procedure::Interpolate { progress }),
        Accumulate => from.animate(&to, Procedure::Accumulate { count: progress as u64 }),
    };

    let output = unsafe { output.as_mut() }.expect("not a valid 'output' matrix");
    if let Ok(result) = result {
        *output = result.into();
    } else if progress < 0.5 {
        *output = from.clone().into();
    } else {
        *output = to.clone().into();
    }
}

#[no_mangle]
pub extern "C" fn Servo_ParseStyleAttribute(
    data: *const nsACString,
    raw_extra_data: *mut URLExtraData,
    quirks_mode: nsCompatibility,
    loader: *mut Loader,
) -> RawServoDeclarationBlockStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let value = unsafe { data.as_ref().unwrap().as_str_unchecked() };
    let reporter = ErrorReporter::new(ptr::null_mut(), loader, raw_extra_data);
    let url_data = unsafe { UrlExtraData::from_ptr_ref(&raw_extra_data) };
    Arc::new(global_style_data.shared_lock.wrap(
        parse_style_attribute(
            value,
            url_data,
            reporter.as_ref().map(|r| r as &ParseErrorReporter),
            quirks_mode.into(),
        )
    )).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_CreateEmpty() -> RawServoDeclarationBlockStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    Arc::new(global_style_data.shared_lock.wrap(PropertyDeclarationBlock::new())).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_Clone(
    declarations: RawServoDeclarationBlockBorrowed,
) -> RawServoDeclarationBlockStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
    Arc::new(global_style_data.shared_lock.wrap(
        declarations.read_with(&guard).clone()
    )).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_Equals(
    a: RawServoDeclarationBlockBorrowed,
    b: RawServoDeclarationBlockBorrowed,
) -> bool {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    *Locked::<PropertyDeclarationBlock>::as_arc(&a).read_with(&guard).declarations() ==
    *Locked::<PropertyDeclarationBlock>::as_arc(&b).read_with(&guard).declarations()
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_GetCssText(
    declarations: RawServoDeclarationBlockBorrowed,
    result: *mut nsAString,
) {
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.to_css(&mut *result).unwrap()
    })
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_SerializeOneValue(
    declarations: RawServoDeclarationBlockBorrowed,
    property_id: nsCSSPropertyID, buffer: *mut nsAString,
    computed_values: ComputedStyleBorrowedOrNull,
    custom_properties: RawServoDeclarationBlockBorrowedOrNull,
) {
    let property_id = get_property_id_from_nscsspropertyid!(property_id, ());

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();
    let decls = Locked::<PropertyDeclarationBlock>::as_arc(&declarations).read_with(&guard);

    let custom_properties = Locked::<PropertyDeclarationBlock>::arc_from_borrowed(&custom_properties);
    let custom_properties = custom_properties.map(|block| block.read_with(&guard));
    let buffer = unsafe { buffer.as_mut().unwrap() };
    let rv = decls.single_value_to_css(&property_id, buffer, computed_values, custom_properties);
    debug_assert!(rv.is_ok());
}

#[no_mangle]
pub unsafe extern "C" fn Servo_SerializeFontValueForCanvas(
    declarations: RawServoDeclarationBlockBorrowed,
    buffer: *mut nsAString,
) {
    use style::properties::shorthands::font;
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        let longhands = match font::LonghandsToSerialize::from_iter(decls.declarations().iter()) {
            Ok(l) => l,
            Err(()) => {
                warn!("Unexpected property!");
                return;
            }
        };

        let rv = longhands.to_css(&mut CssWriter::new(&mut *buffer));
        debug_assert!(rv.is_ok());
    })
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_Count(declarations: RawServoDeclarationBlockBorrowed) -> u32 {
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.declarations().len() as u32
    })
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_GetNthProperty(
    declarations: RawServoDeclarationBlockBorrowed,
    index: u32,
    result: *mut nsAString,
) -> bool {
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        if let Some(decl) = decls.declarations().get(index as usize) {
            let result = unsafe { result.as_mut().unwrap() };
            result.assign_str(&decl.id().name());
            true
        } else {
            false
        }
    })
}

macro_rules! get_property_id_from_property {
    ($property: ident, $ret: expr) => {{
        let property = $property.as_ref().unwrap().as_str_unchecked();
        match PropertyId::parse_enabled_for_all_content(property) {
            Ok(property_id) => property_id,
            Err(_) => return $ret,
        }
    }}
}

unsafe fn get_property_value(
    declarations: RawServoDeclarationBlockBorrowed,
    property_id: PropertyId,
    value: *mut nsAString,
) {
    // This callsite is hot enough that the lock acquisition shows up in profiles.
    // Using an unchecked read here improves our performance by ~10% on the
    // microbenchmark in bug 1355599.
    read_locked_arc_unchecked(declarations, |decls: &PropertyDeclarationBlock| {
        decls.property_value_to_css(&property_id, value.as_mut().unwrap()).unwrap();
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_GetPropertyValue(
    declarations: RawServoDeclarationBlockBorrowed,
    property: *const nsACString,
    value: *mut nsAString,
) {
    get_property_value(
        declarations,
        get_property_id_from_property!(property, ()),
        value,
    )
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_GetPropertyValueById(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
    value: *mut nsAString,
) {
    get_property_value(declarations, get_property_id_from_nscsspropertyid!(property, ()), value)
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_GetPropertyIsImportant(
    declarations: RawServoDeclarationBlockBorrowed,
    property: *const nsACString,
) -> bool {
    let property_id = get_property_id_from_property!(property, false);
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.property_priority(&property_id).important()
    })
}

fn set_property(
    declarations: RawServoDeclarationBlockBorrowed,
    property_id: PropertyId,
    value: *const nsACString,
    is_important: bool,
    data: *mut URLExtraData,
    parsing_mode: structs::ParsingMode,
    quirks_mode: QuirksMode,
    loader: *mut Loader,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    let mut source_declarations = SourcePropertyDeclaration::new();
    let reporter = ErrorReporter::new(ptr::null_mut(), loader, data);
    let result = parse_property_into(
        &mut source_declarations,
        property_id,
        value,
        data,
        parsing_mode,
        quirks_mode,
        reporter.as_ref().map(|r| r as &ParseErrorReporter),
    );

    if result.is_err() {
        return false;
    }

    let importance = if is_important { Importance::Important } else { Importance::Normal };
    let mut updates = Default::default();
    let will_change = read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.prepare_for_update(&source_declarations, importance, &mut updates)
    });
    if !will_change {
        return false;
    }

    before_change_closure.invoke();
    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
        decls.update(source_declarations.drain(), importance, &mut updates)
    });
    true
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_SetProperty(
    declarations: RawServoDeclarationBlockBorrowed,
    property: *const nsACString,
    value: *const nsACString,
    is_important: bool,
    data: *mut URLExtraData,
    parsing_mode: structs::ParsingMode,
    quirks_mode: nsCompatibility,
    loader: *mut Loader,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    set_property(
        declarations,
        get_property_id_from_property!(property, false),
        value,
        is_important,
        data,
        parsing_mode,
        quirks_mode.into(),
        loader,
        before_change_closure,
    )
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_SetPropertyToAnimationValue(
    declarations: RawServoDeclarationBlockBorrowed,
    animation_value: RawServoAnimationValueBorrowed,
) -> bool {
    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
        decls.push(
            AnimationValue::as_arc(&animation_value).uncompute(),
            Importance::Normal,
        )
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_SetPropertyById(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
    value: *const nsACString,
    is_important: bool,
    data: *mut URLExtraData,
    parsing_mode: structs::ParsingMode,
    quirks_mode: nsCompatibility,
    loader: *mut Loader,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    set_property(
        declarations,
        get_property_id_from_nscsspropertyid!(property, false),
        value,
        is_important,
        data,
        parsing_mode,
        quirks_mode.into(),
        loader,
        before_change_closure,
    )
}

fn remove_property(
    declarations: RawServoDeclarationBlockBorrowed,
    property_id: PropertyId,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    let first_declaration =
        read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
            decls.first_declaration_to_remove(&property_id)
        });

    let first_declaration = match first_declaration {
        Some(i) => i,
        None => return false,
    };

    before_change_closure.invoke();
    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
        decls.remove_property(&property_id, first_declaration)
    });

    true
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_RemoveProperty(
    declarations: RawServoDeclarationBlockBorrowed,
    property: *const nsACString,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    remove_property(
        declarations,
        get_property_id_from_property!(property, false),
        before_change_closure,
    )
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_RemovePropertyById(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
    before_change_closure: DeclarationBlockMutationClosure,
) -> bool {
    remove_property(
        declarations,
        get_property_id_from_nscsspropertyid!(property, false),
        before_change_closure,
    )
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_Create() -> RawServoMediaListStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    Arc::new(global_style_data.shared_lock.wrap(MediaList::empty())).into_strong()
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_DeepClone(list: RawServoMediaListBorrowed) -> RawServoMediaListStrong {
    let global_style_data = &*GLOBAL_STYLE_DATA;
    read_locked_arc(list, |list: &MediaList| {
        Arc::new(global_style_data.shared_lock.wrap(list.clone()))
            .into_strong()
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_Matches(
    list: RawServoMediaListBorrowed,
    raw_data: RawServoStyleSetBorrowed,
) -> bool {
    let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
    read_locked_arc(list, |list: &MediaList| {
        list.evaluate(per_doc_data.stylist.device(), per_doc_data.stylist.quirks_mode())
    })
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_HasCSSWideKeyword(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
) -> bool {
    let property_id = get_property_id_from_nscsspropertyid!(property, false);
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.has_css_wide_keyword(&property_id)
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_GetText(list: RawServoMediaListBorrowed, result: *mut nsAString) {
    read_locked_arc(list, |list: &MediaList| {
        list.to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_MediaList_SetText(
    list: RawServoMediaListBorrowed,
    text: *const nsACString,
    caller_type: CallerType,
) {
    let text = (*text).as_str_unchecked();

    let mut input = ParserInput::new(&text);
    let mut parser = Parser::new(&mut input);
    let url_data = dummy_url_data();

    // TODO(emilio): If the need for `CallerType` appears in more places,
    // consider adding an explicit member in `ParserContext` instead of doing
    // this (or adding a dummy "chrome://" url data).
    //
    // For media query parsing it's effectively the same, so for now...
    let origin = match caller_type {
        CallerType::System => Origin::UserAgent,
        CallerType::NonSystem => Origin::Author,
    };

    let context = ParserContext::new(
        origin,
        url_data,
        Some(CssRuleType::Media),
        ParsingMode::DEFAULT,
        QuirksMode::NoQuirks,
        // TODO(emilio): Looks like error reporting could be useful here?
        None,
    );

    write_locked_arc(list, |list: &mut MediaList| {
        *list = MediaList::parse(&context, &mut parser);
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_GetLength(list: RawServoMediaListBorrowed) -> u32 {
    read_locked_arc(list, |list: &MediaList| list.media_queries.len() as u32)
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_GetMediumAt(
    list: RawServoMediaListBorrowed,
    index: u32,
    result: *mut nsAString,
) -> bool {
    read_locked_arc(list, |list: &MediaList| {
        if let Some(media_query) = list.media_queries.get(index as usize) {
            media_query.to_css(&mut CssWriter::new(unsafe { result.as_mut().unwrap() })).unwrap();
            true
        } else {
            false
        }
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_AppendMedium(
    list: RawServoMediaListBorrowed,
    new_medium: *const nsACString,
) {
    let new_medium = unsafe { new_medium.as_ref().unwrap().as_str_unchecked() };
    let url_data = unsafe { dummy_url_data() };
    let context = ParserContext::new_for_cssom(
        url_data,
        Some(CssRuleType::Media),
        ParsingMode::DEFAULT,
        QuirksMode::NoQuirks,
        None,
    );
    write_locked_arc(list, |list: &mut MediaList| {
        list.append_medium(&context, new_medium);
    })
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_DeleteMedium(
    list: RawServoMediaListBorrowed,
    old_medium: *const nsACString,
) -> bool {
    let old_medium = unsafe { old_medium.as_ref().unwrap().as_str_unchecked() };
    let url_data = unsafe { dummy_url_data() };
    let context = ParserContext::new_for_cssom(
        url_data,
        Some(CssRuleType::Media),
        ParsingMode::DEFAULT,
        QuirksMode::NoQuirks,
        None,
    );
    write_locked_arc(list, |list: &mut MediaList| list.delete_medium(&context, old_medium))
}

#[no_mangle]
pub extern "C" fn Servo_MediaList_SizeOfIncludingThis(
    malloc_size_of: GeckoMallocSizeOf,
    malloc_enclosing_size_of: GeckoMallocSizeOf,
    list: RawServoMediaListBorrowed,
) -> usize {
    use malloc_size_of::MallocSizeOf;
    use malloc_size_of::MallocUnconditionalShallowSizeOf;

    let global_style_data = &*GLOBAL_STYLE_DATA;
    let guard = global_style_data.shared_lock.read();

    let mut ops = MallocSizeOfOps::new(
        malloc_size_of.unwrap(),
        Some(malloc_enclosing_size_of.unwrap()),
        None
    );

    Locked::<MediaList>::as_arc(&list).with_arc(|list| {
        let mut n = 0;
        n += list.unconditional_shallow_size_of(&mut ops);
        n += list.read_with(&guard).size_of(&mut ops);
        n
    })
}

macro_rules! get_longhand_from_id {
    ($id:expr) => {
        match PropertyId::from_nscsspropertyid($id) {
            Ok(PropertyId::Longhand(long)) => long,
            _ => {
                panic!("stylo: unknown presentation property with id");
            }
        }
    };
}

macro_rules! match_wrap_declared {
    ($longhand:ident, $($property:ident => $inner:expr,)*) => (
        match $longhand {
            $(
                LonghandId::$property => PropertyDeclaration::$property($inner),
            )*
            _ => {
                panic!("stylo: Don't know how to handle presentation property");
            }
        }
    )
}

#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_PropertyIsSet(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
) -> bool {
    read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
        decls.contains(get_longhand_from_id!(property))
    })
}

#[no_mangle]
pub unsafe extern "C" fn Servo_DeclarationBlock_SetIdentStringValue(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,
    value: *mut nsAtom,
) {
    use style::properties::{PropertyDeclaration, LonghandId};
    use style::properties::longhands::_x_lang::computed_value::T as Lang;

    let long = get_longhand_from_id!(property);
    let prop = match_wrap_declared! { long,
        XLang => Lang(Atom::from_raw(value)),
    };
    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
        decls.push(prop, Importance::Normal);
    })
}

#[no_mangle]
#[allow(unreachable_code)]
pub extern "C" fn Servo_DeclarationBlock_SetKeywordValue(
    declarations: RawServoDeclarationBlockBorrowed,
    property: nsCSSPropertyID,