mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 14:54:25 +00:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
@@ -1005,6 +1005,7 @@ GK_ATOM(parameter, "parameter")
|
||||
GK_ATOM(parent, "parent")
|
||||
GK_ATOM(parentfocused, "parentfocused")
|
||||
GK_ATOM(parsetype, "parsetype")
|
||||
GK_ATOM(part, "part")
|
||||
GK_ATOM(password, "password")
|
||||
GK_ATOM(pattern, "pattern")
|
||||
GK_ATOM(patternSeparator, "pattern-separator")
|
||||
|
||||
@@ -26,7 +26,6 @@ support-files =
|
||||
image_yellow75.png
|
||||
imagebitmap_bug1239300.js
|
||||
imagebitmap_bug1239752.js
|
||||
imagebitmap_extensions.html
|
||||
imagebitmap_on_worker.js
|
||||
imagebitmap_structuredclone.js
|
||||
imagebitmap_structuredclone_iframe.html
|
||||
|
||||
@@ -823,16 +823,37 @@ nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// The approach in WalkAllShadowRootHostRules seems reasonable on the surface and reminds me of what is done with mScopedRoot elsewhere. But something important is missing, either here or in the code that would normally use it.
|
||||
|
||||
void
|
||||
nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
ElementDependentRuleProcessorData* aData)
|
||||
{
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
|
||||
WalkAllRules(aFunc, aData, true);
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = false;
|
||||
}
|
||||
{
|
||||
if (!mBoundContentSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool oldOnlyMatchHostPseudo = aData->mTreeMatchContext.mOnlyMatchHostPseudo;
|
||||
nsIContent* oldScopedRoot = aData->mTreeMatchContext.mScopedRoot;
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
|
||||
|
||||
for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIContent* boundContent = iter.Get()->GetKey();
|
||||
ShadowRoot* shadowRoot = boundContent->GetShadowRoot();
|
||||
if (!shadowRoot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsXBLBinding* binding = shadowRoot->GetAssociatedBinding();
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
aData->mTreeMatchContext.mScopedRoot = boundContent;
|
||||
binding->WalkRules(aFunc, aData);
|
||||
}
|
||||
|
||||
aData->mTreeMatchContext.mScopedRoot = oldScopedRoot;
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = oldOnlyMatchHostPseudo;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
|
||||
+16
-3
@@ -13,6 +13,7 @@
|
||||
#include "jsgc.h"
|
||||
|
||||
#include "gc/Heap.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Statistics.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
@@ -650,6 +651,13 @@ class GCRuntime
|
||||
void onOutOfMallocMemory();
|
||||
void onOutOfMallocMemory(const AutoLockGC& lock);
|
||||
|
||||
/* Idle-time GC notifications. */
|
||||
void notifyJSExecutionStart();
|
||||
void notifyJSExecutionEnd();
|
||||
IdleGCManager& idleGCMgr() {
|
||||
return idleGC;
|
||||
}
|
||||
|
||||
size_t maxMallocBytesAllocated() { return maxMallocBytes; }
|
||||
|
||||
uint64_t nextCellUniqueId() {
|
||||
@@ -966,6 +974,8 @@ class GCRuntime
|
||||
void sweepZones(FreeOp* fop, bool lastGC);
|
||||
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
||||
void startDecommit();
|
||||
bool sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas);
|
||||
void queueZonesForBackgroundSweep(ZoneList& zones);
|
||||
void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
|
||||
void assertBackgroundSweepingFinished();
|
||||
@@ -976,10 +986,10 @@ class GCRuntime
|
||||
void endCompactPhase(JS::gcreason::Reason reason);
|
||||
void sweepTypesAfterCompacting(Zone* zone);
|
||||
void sweepZoneAfterCompacting(Zone* zone);
|
||||
MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
[[nodiscard]] bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
void updateTypeDescrObjects(MovingTracer* trc, Zone* zone);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds);
|
||||
void updateAllCellPointers(MovingTracer* trc, Zone* zone);
|
||||
void updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock);
|
||||
void protectAndHoldArenas(Arena* arenaList);
|
||||
@@ -1019,6 +1029,9 @@ class GCRuntime
|
||||
GCSchedulingTunables tunables;
|
||||
GCSchedulingState schedulingState;
|
||||
|
||||
/* Idle-time garbage collection manager. */
|
||||
IdleGCManager idleGC;
|
||||
|
||||
MemProfiler mMemProfiler;
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* IDLE-TIME GARBAGE COLLECTION SYSTEM
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
* This system implements idle-time-only garbage collection, deferring GC work
|
||||
* to periods when the JavaScript engine is not actively processing code.
|
||||
* This improves perceived responsiveness by avoiding GC pauses during critical
|
||||
* execution windows.
|
||||
*
|
||||
* Architecture
|
||||
* -----------
|
||||
*
|
||||
* The system consists of several key components:
|
||||
*
|
||||
* 1. IdleGCManager (gc/IdleGC.h, gc/IdleGC.cpp)
|
||||
* - Tracks when the JS engine is executing vs. idle
|
||||
* - Maintains timestamp of last execution activity
|
||||
* - Checks if sufficient idle time has passed
|
||||
* - Configurable idle threshold (default: 100ms)
|
||||
* - Can determine which GC reasons should bypass idle checks
|
||||
*
|
||||
* 2. GCRuntime Integration (gc/GCRuntime.h)
|
||||
* - Contains an IdleGCManager instance
|
||||
* - Provides public methods: notifyJSExecutionStart/End()
|
||||
* - Modified checkIfGCAllowedInCurrentState() to check idle status
|
||||
*
|
||||
* 3. Activity Tracking (vm/Runtime.cpp)
|
||||
* - triggerActivityCallback() now notifies IdleGCManager
|
||||
* - Called when JS enters/exits request (execution boundary)
|
||||
* - Updated in jsapi.cpp's StartRequest/StopRequest functions
|
||||
*
|
||||
* 4. Public API (jsapi.h, jsapi.cpp)
|
||||
* - JS_SetIdleGCEnabled() / JS_IsIdleGCEnabled()
|
||||
* - JS_SetIdleGCThreshold() / JS_GetIdleGCThreshold()
|
||||
* - JS_GetIdleTimeSinceLastExecution()
|
||||
* - Allows embedders to configure idle GC behavior
|
||||
*
|
||||
* Behavior
|
||||
* --------
|
||||
*
|
||||
* Normal GC Trigger (Idle GC Enabled):
|
||||
*
|
||||
* 1. JS code executes → notifyJSExecutionStart() called
|
||||
* 2. JS code finishes → notifyJSExecutionEnd() called, timestamp recorded
|
||||
* 3. GC needed → checkIfGCAllowedInCurrentState() checks idle status
|
||||
* 4a. If idle >= threshold → GC proceeds normally
|
||||
* 4b. If still executing/idle < threshold → GC is deferred
|
||||
* 5. Once idle threshold is met, next GC request proceeds
|
||||
*
|
||||
* Critical GC Triggers (Always Proceed):
|
||||
*
|
||||
* The following GC reasons bypass idle checking:
|
||||
* - OUT_OF_MEMORY: Memory pressure conditions
|
||||
* - ALLOC_TRIGGER: Allocation threshold exceeded
|
||||
* - MALLOC_PRESSURE: Malloc pressure from OS
|
||||
* - EAGER_ALLOC_TRIGGER: Eager allocation trigger
|
||||
* - API: Explicit JS API calls
|
||||
* - DETERMINISTIC: Deterministic tests
|
||||
* - EVICT_NURSERY: Nursery eviction
|
||||
* - SHUTDOWN_CC, DESTROY_RUNTIME, LAST_DITCH: Shutdown GCs
|
||||
* - DESTROY_ZONE, COMPARTMENT_REVOKED: Zone/compartment destruction
|
||||
*
|
||||
* Configuration
|
||||
* -------------
|
||||
*
|
||||
* Idle GC Enabled (default: true):
|
||||
* - Enables idle-time-only GC mode
|
||||
* - Can be toggled dynamically via JS_SetIdleGCEnabled()
|
||||
*
|
||||
* Idle Threshold (default: 100ms):
|
||||
* - Minimum idle time before GC is permitted
|
||||
* - Configurable via JS_SetIdleGCThreshold(ms)
|
||||
* - Typical values: 50-200ms depending on application
|
||||
*
|
||||
* Integration Examples
|
||||
* --------------------
|
||||
*
|
||||
* Browser Integration:
|
||||
*
|
||||
* // When browser loads configuration
|
||||
* JS_SetIdleGCEnabled(cx, true);
|
||||
* JS_SetIdleGCThreshold(cx, 100); // 100ms idle threshold
|
||||
*
|
||||
* // Monitor idle GC effectiveness (optional)
|
||||
* uint64_t idleTime = JS_GetIdleTimeSinceLastExecution(cx);
|
||||
* if (idleTime > JS_GetIdleGCThreshold(cx)) {
|
||||
* // System is idle, GC would be allowed if triggered
|
||||
* }
|
||||
*
|
||||
* Disabling for Specific Scenarios:
|
||||
*
|
||||
* // During initialization when nothing is "idle" yet
|
||||
* JS_SetIdleGCEnabled(cx, false);
|
||||
* // ... do initial setup ...
|
||||
* JS_SetIdleGCEnabled(cx, true); // Re-enable for normal operation
|
||||
*
|
||||
* Performance Considerations
|
||||
* --------------------------
|
||||
*
|
||||
* Benefits:
|
||||
* - Reduced jank during active JS execution
|
||||
* - GC pauses moved to idle periods where users won't notice
|
||||
* - Especially effective for interactive applications
|
||||
* - Improves Time-to-Interactive and First Input Delay metrics
|
||||
*
|
||||
* Tradeoffs:
|
||||
* - May accumulate more garbage before collection
|
||||
* - Requires predictable idle periods (not suitable for all workloads)
|
||||
* - Critical memory pressure GCs still proceed immediately
|
||||
*
|
||||
* Tuning:
|
||||
* - Lower threshold (50ms) = more frequent GC, less memory overhead
|
||||
* - Higher threshold (200ms) = less GC overhead, more memory usage
|
||||
* - Optimal value depends on application's execution pattern
|
||||
*
|
||||
* Testing
|
||||
* -------
|
||||
*
|
||||
* Unit Tests:
|
||||
* // Test idle detection
|
||||
* JS_SetIdleGCThreshold(cx, 100);
|
||||
* // Simulate JS execution
|
||||
* cx->runtime()->gc.notifyJSExecutionStart();
|
||||
* // ... wait 50ms ...
|
||||
* MOZ_ASSERT(!cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
* cx->runtime()->gc.notifyJSExecutionEnd();
|
||||
* // ... wait 150ms ...
|
||||
* MOZ_ASSERT(cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
*
|
||||
* Integration Tests:
|
||||
* - Verify GC is deferred during active execution
|
||||
* - Verify GC proceeds after idle period
|
||||
* - Verify critical GC reasons bypass idle check
|
||||
* - Measure latency improvements
|
||||
*
|
||||
* Implementation Notes
|
||||
* --------------------
|
||||
*
|
||||
* Thread Safety:
|
||||
* - IdleGCManager uses mozilla::Atomic for thread-safe state
|
||||
* - TimeStamp operations are atomic
|
||||
* - No additional locking needed beyond existing GC locks
|
||||
*
|
||||
* Compatibility:
|
||||
* - Works with both incremental and non-incremental GC
|
||||
* - Compatible with generational GC
|
||||
* - Works with zone GC and full GC
|
||||
* - Respects existing GC suppression mechanisms
|
||||
*
|
||||
* Future Enhancements
|
||||
* -------------------
|
||||
*
|
||||
* Potential improvements:
|
||||
* - Adaptive idle threshold based on historical GC times
|
||||
* - Per-zone idle configuration
|
||||
* - Integration with browser rendering idle callback API
|
||||
* - Metrics/telemetry for idle GC effectiveness
|
||||
* - Machine learning-based prediction of idle periods
|
||||
* - Cooperative GC scheduling with other subsystems
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/IdleGC.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
IdleGCManager::IdleGCManager()
|
||||
: lastExecutionTime_(mozilla::TimeStamp::Now()),
|
||||
idleGCEnabled_(true),
|
||||
idleThresholdMs_(100), // 100ms default idle threshold
|
||||
isExecuting_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionStart()
|
||||
{
|
||||
isExecuting_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionEnd()
|
||||
{
|
||||
isExecuting_ = false;
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::isIdleEnough() const
|
||||
{
|
||||
if (!idleGCEnabled_) {
|
||||
return true; // If disabled, always consider idle
|
||||
}
|
||||
|
||||
if (isExecuting_) {
|
||||
return false; // Still executing, not idle
|
||||
}
|
||||
|
||||
uint64_t idleTime = idleTimeSinceLastExecution();
|
||||
return idleTime >= idleThresholdMs_;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IdleGCManager::idleTimeSinceLastExecution() const
|
||||
{
|
||||
mozilla::TimeStamp now = mozilla::TimeStamp::Now();
|
||||
mozilla::TimeDuration idle = now - lastExecutionTime_;
|
||||
return idle.ToMilliseconds();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::shouldBypassIdleCheck(JS::gcreason::Reason reason)
|
||||
{
|
||||
// These reasons indicate urgent GC needs that should bypass idle checking
|
||||
switch (reason) {
|
||||
// Allocation and memory pressure conditions.
|
||||
case JS::gcreason::ALLOC_TRIGGER:
|
||||
case JS::gcreason::EAGER_ALLOC_TRIGGER:
|
||||
case JS::gcreason::TOO_MUCH_MALLOC:
|
||||
case JS::gcreason::MEM_PRESSURE:
|
||||
case JS::gcreason::LAST_DITCH:
|
||||
|
||||
// Nursery/store-buffer pressure.
|
||||
case JS::gcreason::OUT_OF_NURSERY:
|
||||
case JS::gcreason::EVICT_NURSERY:
|
||||
case JS::gcreason::FULL_STORE_BUFFER:
|
||||
case JS::gcreason::SHARED_MEMORY_LIMIT:
|
||||
|
||||
// Explicit API calls
|
||||
case JS::gcreason::API:
|
||||
case JS::gcreason::ABORT_GC:
|
||||
|
||||
// Shutdown and finalization
|
||||
case JS::gcreason::SHUTDOWN_CC:
|
||||
case JS::gcreason::DESTROY_RUNTIME:
|
||||
case JS::gcreason::NSJSCONTEXT_DESTROY:
|
||||
case JS::gcreason::XPCONNECT_SHUTDOWN:
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::reset()
|
||||
{
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
isExecuting_ = false;
|
||||
}
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
@@ -0,0 +1,118 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
#ifndef gc_IdleGC_h
|
||||
#define gc_IdleGC_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "js/GCAPI.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
/*
|
||||
* Idle-Time Garbage Collection System
|
||||
* ====================================
|
||||
*
|
||||
* This system defers garbage collection to occur only during periods when
|
||||
* the browser is not actively processing JavaScript. This helps maintain
|
||||
* responsiveness by avoiding GC pauses during critical execution windows.
|
||||
*
|
||||
* When JavaScript execution is active, GC triggers are deferred. Once the
|
||||
* JS engine has been idle for a configurable threshold period, pending GC
|
||||
* work is performed immediately or incrementally as appropriate.
|
||||
*
|
||||
* Key characteristics:
|
||||
* - Tracks JavaScript activity via hooks in the execution engine
|
||||
* - Configurable idle time threshold (default: 100ms)
|
||||
* - Can be disabled per-zone or globally
|
||||
* - Works with both incremental and non-incremental GC modes
|
||||
* - Respects critical GC reasons that override idle checking
|
||||
*/
|
||||
|
||||
class IdleGCManager
|
||||
{
|
||||
public:
|
||||
// Initialize the idle GC manager
|
||||
IdleGCManager();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution begins. Marks the engine as active.
|
||||
*/
|
||||
void notifyJSExecutionStart();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution ends. Records the end time for
|
||||
* idle detection purposes.
|
||||
*/
|
||||
void notifyJSExecutionEnd();
|
||||
|
||||
/*
|
||||
* Check if the system has been idle for long enough to permit GC.
|
||||
* Returns true if sufficient idle time has passed since last JS execution.
|
||||
*/
|
||||
bool isIdleEnough() const;
|
||||
|
||||
/*
|
||||
* Get the amount of idle time since the last JS execution.
|
||||
* Returns time in milliseconds.
|
||||
*/
|
||||
uint64_t idleTimeSinceLastExecution() const;
|
||||
|
||||
/*
|
||||
* Set the idle threshold - minimum idle time before GC is permitted.
|
||||
* Time is in milliseconds. Default is 100ms.
|
||||
*/
|
||||
void setIdleThresholdMs(uint64_t thresholdMs) {
|
||||
idleThresholdMs_ = thresholdMs;
|
||||
}
|
||||
|
||||
uint64_t idleThresholdMs() const {
|
||||
return idleThresholdMs_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
*/
|
||||
void setIdleGCEnabled(bool enabled) {
|
||||
idleGCEnabled_ = enabled;
|
||||
}
|
||||
|
||||
bool isIdleGCEnabled() const {
|
||||
return idleGCEnabled_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a GC reason should bypass idle checking.
|
||||
* Critical reasons (OOM-like pressure, nursery pressure, explicit requests)
|
||||
* always proceed.
|
||||
*/
|
||||
static bool shouldBypassIdleCheck(JS::gcreason::Reason reason);
|
||||
|
||||
/*
|
||||
* Reset idle tracking state (used during GC or at shutdown).
|
||||
*/
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// Timestamp of the last JavaScript execution activity
|
||||
mozilla::TimeStamp lastExecutionTime_;
|
||||
|
||||
// Whether idle-time-only GC mode is enabled
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> idleGCEnabled_;
|
||||
|
||||
// Minimum idle time (in milliseconds) before GC is permitted
|
||||
mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> idleThresholdMs_;
|
||||
|
||||
// Whether the JS engine is currently executing
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> isExecuting_;
|
||||
};
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
#endif // gc_IdleGC_h
|
||||
@@ -1478,6 +1478,36 @@ JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem)
|
||||
JS_SetGCParameter(cx, config[i].key, config[i].value);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleGCEnabled(enabled);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().isIdleGCEnabled();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleThresholdMs(milliseconds);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleThresholdMs();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleTimeSinceLastExecution();
|
||||
}
|
||||
|
||||
|
||||
JS_PUBLIC_API(JSString*)
|
||||
JS_NewExternalString(JSContext* cx, const char16_t* chars, size_t length,
|
||||
|
||||
@@ -1719,6 +1719,40 @@ JS_GetGCParameter(JSContext* cx, JSGCParamKey key);
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem);
|
||||
|
||||
/*
|
||||
* Idle-time garbage collection control.
|
||||
* These functions allow control over when GC occurs - specifically, whether
|
||||
* GC should only run when the browser/application is not doing active JS work.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
* When enabled, GC is deferred until the JS engine has been idle for the
|
||||
* configured threshold period (default: 100ms).
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled);
|
||||
|
||||
extern JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Set the minimum idle time (in milliseconds) before GC is permitted.
|
||||
* Use this to configure how long the JS engine must be inactive before
|
||||
* pending GC work can proceed.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds);
|
||||
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Get the current idle time since the last JS execution.
|
||||
*/
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Create a new JSString whose chars member refers to external memory, i.e.,
|
||||
* memory requiring application-specific finalization.
|
||||
|
||||
+273
-57
@@ -214,6 +214,7 @@
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCTrace.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Memory.h"
|
||||
#include "gc/Policy.h"
|
||||
@@ -1570,7 +1571,7 @@ ArenaLists::prepareForIncrementalGC()
|
||||
{
|
||||
purge();
|
||||
for (auto i : AllAllocKinds()) {
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2117,7 +2118,7 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock)
|
||||
// Find the next arena to update.
|
||||
//
|
||||
// This iterates through the GC thing kinds filtered by shouldProcessKind(),
|
||||
// and then through thea arenas of that kind. All state is held in the
|
||||
// and then through the arenas of that kind. All state is held in the
|
||||
// object and we just return when we find an arena.
|
||||
|
||||
for (; kind < AllocKind::LIMIT; kind = nextAllocKind(kind)) {
|
||||
@@ -2207,21 +2208,12 @@ UpdatePointersTask::run()
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
static const size_t MinCellUpdateBackgroundTasks = 2;
|
||||
static const size_t MinCellUpdateBackgroundTasks = 1;
|
||||
static const size_t MaxCellUpdateBackgroundTasks = 8;
|
||||
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount()
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static bool
|
||||
CanUpdateKindInBackground(AllocKind kind) {
|
||||
CanUpdateKindInBackground(AllocKind kind)
|
||||
{
|
||||
// We try to update as many GC things in parallel as we can, but there are
|
||||
// kinds for which this might not be safe:
|
||||
// - we assume JSObjects that are foreground finalized are not safe to
|
||||
@@ -2233,6 +2225,34 @@ CanUpdateKindInBackground(AllocKind kind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CountBackgroundUpdateArenas(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
size_t arenaCount = 0;
|
||||
for (AllocKind kind : kinds) {
|
||||
MOZ_ASSERT(CanUpdateKindInBackground(kind));
|
||||
for (Arena* arena = zone->arenas.getFirstArena(kind); arena; arena = arena->next)
|
||||
arenaCount++;
|
||||
}
|
||||
return arenaCount;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
if (!CanUseExtraThreads() || kinds.isEmpty())
|
||||
return 0;
|
||||
|
||||
size_t arenaCount = CountBackgroundUpdateArenas(zone, kinds);
|
||||
if (arenaCount < UpdatePointersTask::MaxArenasToProcess * 2)
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
size_t workTaskCount = arenaCount / UpdatePointersTask::MaxArenasToProcess;
|
||||
targetTaskCount = Min(targetTaskCount, workTaskCount);
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static AllocKinds
|
||||
ForegroundUpdateKinds(AllocKinds kinds)
|
||||
{
|
||||
@@ -2253,10 +2273,15 @@ GCRuntime::updateTypeDescrObjects(MovingTracer* trc, Zone* zone)
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount)
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
AllocKinds fgKinds = bgTaskCount == 0 ? kinds : ForegroundUpdateKinds(kinds);
|
||||
MOZ_ASSERT(trc);
|
||||
|
||||
AllocKinds fgKinds = ForegroundUpdateKinds(kinds);
|
||||
AllocKinds bgKinds = kinds - fgKinds;
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount(zone, bgKinds);
|
||||
if (bgTaskCount == 0)
|
||||
fgKinds = kinds;
|
||||
|
||||
ArenasToUpdate fgArenas(zone, fgKinds);
|
||||
ArenasToUpdate bgArenas(zone, bgKinds);
|
||||
@@ -2351,15 +2376,13 @@ GCRuntime::updateAllCellPointers(MovingTracer* trc, Zone* zone)
|
||||
{
|
||||
AutoDisableProxyCheck noProxyCheck(rt); // These checks assert when run in parallel.
|
||||
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount();
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc, bgTaskCount);
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc);
|
||||
|
||||
// Update TypeDescrs before all other objects as typed objects access these
|
||||
// objects when we trace them.
|
||||
updateTypeDescrObjects(trc, zone);
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects, bgTaskCount);
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2665,6 +2688,17 @@ ArenaLists::backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty)
|
||||
lists->backgroundFinalizeState[thingKind] = BFS_DONE;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty)
|
||||
{
|
||||
for (auto kind : phase.kinds) {
|
||||
Arena* arenas = arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
backgroundFinalize(fop, arenas, empty);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::queueForegroundObjectsForSweep(FreeOp* fop)
|
||||
{
|
||||
@@ -3003,6 +3037,138 @@ js::gc::BackgroundDecommitTask::run()
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundFinalizeTask : public GCParallelTaskHelper<BackgroundFinalizeTask>
|
||||
{
|
||||
Zone* zone_;
|
||||
const FinalizePhase* phase_;
|
||||
Arena* emptyArenas_;
|
||||
|
||||
BackgroundFinalizeTask(const BackgroundFinalizeTask&) = delete;
|
||||
|
||||
public:
|
||||
BackgroundFinalizeTask(Zone* zone, const FinalizePhase* phase)
|
||||
: zone_(zone),
|
||||
phase_(phase),
|
||||
emptyArenas_(nullptr)
|
||||
{}
|
||||
|
||||
BackgroundFinalizeTask(BackgroundFinalizeTask&& other)
|
||||
: GCParallelTaskHelper(mozilla::Move(other)),
|
||||
zone_(other.zone_),
|
||||
phase_(other.phase_),
|
||||
emptyArenas_(other.emptyArenas_)
|
||||
{
|
||||
other.emptyArenas_ = nullptr;
|
||||
}
|
||||
|
||||
void run() {
|
||||
AutoSetThreadIsSweeping threadIsSweeping;
|
||||
finalize();
|
||||
}
|
||||
|
||||
void runAlreadySweeping() {
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(CurrentThreadIsGCSweeping());
|
||||
#endif
|
||||
finalize();
|
||||
}
|
||||
|
||||
private:
|
||||
void finalize() {
|
||||
FreeOp fop(nullptr);
|
||||
zone_->arenas.backgroundFinalizePhase(&fop, *phase_, &emptyArenas_);
|
||||
}
|
||||
|
||||
public:
|
||||
Arena* takeEmptyArenas() {
|
||||
Arena* empty = emptyArenas_;
|
||||
emptyArenas_ = nullptr;
|
||||
return empty;
|
||||
}
|
||||
};
|
||||
|
||||
using BackgroundFinalizeTaskVector =
|
||||
Vector<BackgroundFinalizeTask, 0, SystemAllocPolicy>;
|
||||
|
||||
static size_t
|
||||
IdleHelperThreadCount(const AutoLockHelperThreadState&)
|
||||
{
|
||||
if (!HelperThreadState().threads)
|
||||
return 0;
|
||||
|
||||
size_t idle = 0;
|
||||
for (const auto& thread : *HelperThreadState().threads) {
|
||||
if (thread.idle())
|
||||
idle++;
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
static void
|
||||
PrependArenaList(Arena** head, Arena* arenas)
|
||||
{
|
||||
if (!arenas)
|
||||
return;
|
||||
|
||||
Arena* tail = arenas;
|
||||
while (tail->next)
|
||||
tail = tail->next;
|
||||
tail->next = *head;
|
||||
*head = arenas;
|
||||
}
|
||||
|
||||
bool
|
||||
GCRuntime::sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas)
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return false;
|
||||
|
||||
size_t zoneCount = 0;
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zoneCount++;
|
||||
|
||||
if (zoneCount < 2)
|
||||
return false;
|
||||
|
||||
BackgroundFinalizeTaskVector tasks;
|
||||
if (!tasks.reserve(zoneCount))
|
||||
return false;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
tasks.infallibleEmplaceBack(zone, &phase);
|
||||
|
||||
size_t tasksStarted = 0;
|
||||
|
||||
{
|
||||
AutoLockHelperThreadState helperLock;
|
||||
|
||||
// sweepBackgroundThings() itself runs as a GC helper task. Do not queue
|
||||
// nested GC parallel tasks unless at least one other helper is idle.
|
||||
if (IdleHelperThreadCount(helperLock) == 0)
|
||||
return false;
|
||||
|
||||
for (; tasksStarted < tasks.length(); tasksStarted++) {
|
||||
if (!tasks[tasksStarted].startWithLockHeld(helperLock))
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
AutoUnlockHelperThreadState unlock(helperLock);
|
||||
for (size_t i = tasksStarted; i < tasks.length(); i++)
|
||||
tasks[i].runAlreadySweeping();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tasksStarted; i++)
|
||||
tasks[i].joinWithLockHeld(helperLock);
|
||||
}
|
||||
|
||||
for (auto& task : tasks)
|
||||
PrependArenaList(emptyArenas, task.takeEmptyArenas());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
{
|
||||
@@ -3015,14 +3181,12 @@ GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
Arena* emptyArenas = nullptr;
|
||||
FreeOp fop(nullptr);
|
||||
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone()) {
|
||||
for (auto kind : BackgroundFinalizePhases[phase].kinds) {
|
||||
Arena* arenas = zone->arenas.arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
|
||||
}
|
||||
}
|
||||
const FinalizePhase& finalizePhase = BackgroundFinalizePhases[phase];
|
||||
if (sweepBackgroundFinalizePhaseInParallel(zones, finalizePhase, &emptyArenas))
|
||||
continue;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zone->arenas.backgroundFinalizePhase(&fop, finalizePhase, &emptyArenas);
|
||||
}
|
||||
|
||||
AutoLockGC lock(rt);
|
||||
@@ -3581,14 +3745,14 @@ ShouldCollectZone(Zone* zone, JS::gcreason::Reason reason)
|
||||
// Normally we collect all scheduled zones.
|
||||
if (reason != JS::gcreason::COMPARTMENT_REVIVED)
|
||||
return zone->isGCScheduled();
|
||||
|
||||
|
||||
// If we are repeating a GC because we noticed dead compartments haven't
|
||||
// been collected, then only collect zones containing those compartments.
|
||||
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
|
||||
if (comp->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3654,7 +3818,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
* on. If the value of keepAtoms() changes between GC slices, then we'll
|
||||
* cancel the incremental GC. See IsIncrementalGCSafe.
|
||||
*/
|
||||
|
||||
|
||||
if (isFull && !rt->keepAtoms()) {
|
||||
Zone* atomsZone = rt->atomsCompartment(lock)->zone();
|
||||
if (atomsZone->isGCScheduled()) {
|
||||
@@ -3670,7 +3834,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
|
||||
/*
|
||||
* Ensure that after the start of a collection we don't allocate into any
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
*/
|
||||
if (isIncremental) {
|
||||
for (GCZonesIter zone(rt); !zone.done(); zone.next())
|
||||
@@ -3762,7 +3926,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
bufferGrayRoots();
|
||||
markCompartments();
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3801,9 +3965,9 @@ GCRuntime::markCompartments()
|
||||
*/
|
||||
|
||||
/* Propagate the maybeAlive flag via cross-compartment edges. */
|
||||
|
||||
|
||||
Vector<JSCompartment*, 0, js::SystemAllocPolicy> workList;
|
||||
|
||||
|
||||
for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
|
||||
if (comp->maybeAlive) {
|
||||
if (!workList.append(comp))
|
||||
@@ -3824,9 +3988,9 @@ GCRuntime::markCompartments()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Set scheduleForDestruction based on maybeAlive. */
|
||||
|
||||
|
||||
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
|
||||
MOZ_ASSERT(!comp->scheduledForDestruction);
|
||||
if (!comp->maybeAlive && !rt->isAtomsCompartment(comp))
|
||||
@@ -4429,7 +4593,9 @@ MAKE_GC_SWEEP_TASK(SweepBaseShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepInitialShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepObjectGroupsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepRegExpsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepMiscTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSavedStacksTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSelfHostingScriptSourceTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepNativeIteratorsTask);
|
||||
#undef MAKE_GC_SWEEP_TASK
|
||||
|
||||
/* virtual */ void
|
||||
@@ -4462,15 +4628,27 @@ SweepRegExpsTask::run()
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepMiscTask::run()
|
||||
SweepSavedStacksTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) {
|
||||
c->sweepSavedStacks();
|
||||
c->sweepSelfHostingScriptSource();
|
||||
c->sweepNativeIterators();
|
||||
}
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepSelfHostingScriptSourceTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepSelfHostingScriptSource();
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepNativeIteratorsTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepNativeIterators();
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase,
|
||||
AutoLockHelperThreadState& locked)
|
||||
@@ -4509,7 +4687,7 @@ PrepareWeakCacheTasks(JSRuntime* rt)
|
||||
WeakCacheTaskVector out;
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
for (JS::WeakCache<void*>* cache : zone->weakCaches_) {
|
||||
if (!out.append(SweepWeakCacheTask(rt, *cache))) {
|
||||
if (!out.emplaceBack(rt, *cache)) {
|
||||
SweepWeakCachesFromMainThread(rt);
|
||||
return WeakCacheTaskVector();
|
||||
}
|
||||
@@ -4551,7 +4729,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
SweepCCWrappersTask sweepCCWrappersTask(rt);
|
||||
SweepObjectGroupsTask sweepObjectGroupsTask(rt);
|
||||
SweepRegExpsTask sweepRegExpsTask(rt);
|
||||
SweepMiscTask sweepMiscTask(rt);
|
||||
SweepSavedStacksTask sweepSavedStacksTask(rt);
|
||||
SweepSelfHostingScriptSourceTask sweepSelfHostingScriptSourceTask(rt);
|
||||
SweepNativeIteratorsTask sweepNativeIteratorsTask(rt);
|
||||
WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
@@ -4599,7 +4779,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -4618,7 +4800,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
c->sweepJitCompartment(&fop);
|
||||
c->sweepTemplateObjects();
|
||||
}
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
|
||||
zone->sweepWeakMaps();
|
||||
|
||||
@@ -4678,7 +4859,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -5005,7 +5188,7 @@ GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess&
|
||||
|
||||
// Reset phase index.
|
||||
sweepPhaseIndex = 0;
|
||||
|
||||
|
||||
endSweepingZoneGroup();
|
||||
getNextZoneGroup();
|
||||
if (!currentZoneGroup)
|
||||
@@ -5107,6 +5290,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
|
||||
|
||||
Arena* relocatedArenas = nullptr;
|
||||
|
||||
while (!zonesToMaybeCompact.isEmpty()) {
|
||||
// TODO: JSScripts can move. If the sampler interrupts the GC in the
|
||||
// middle of relocating an arena, invalid JSScript pointers may be
|
||||
@@ -5558,7 +5742,7 @@ gc::AbortReason
|
||||
gc::IsIncrementalGCUnsafe(JSRuntime* rt)
|
||||
{
|
||||
MOZ_ASSERT(!rt->mainThread.suppressGC);
|
||||
|
||||
|
||||
if (rt->keepAtoms())
|
||||
return gc::AbortReason::KeepAtomsSet;
|
||||
|
||||
@@ -5579,7 +5763,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
else if (mode != JSGC_MODE_INCREMENTAL)
|
||||
unsafeReason = gc::AbortReason::ModeChange;
|
||||
}
|
||||
|
||||
|
||||
if (unsafeReason != AbortReason::None) {
|
||||
resetIncrementalGC(unsafeReason, lock);
|
||||
budget.makeUnlimited();
|
||||
@@ -5587,7 +5771,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (isTooMuchMalloc()) {
|
||||
budget.makeUnlimited();
|
||||
@@ -5827,6 +6011,12 @@ GCRuntime::checkCanCallAPI()
|
||||
bool
|
||||
GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (rt->mainThread.suppressGC)
|
||||
return false;
|
||||
|
||||
@@ -5835,6 +6025,12 @@ GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
if (rt->isBeingDestroyed() && !IsShutdownGC(reason))
|
||||
return false;
|
||||
|
||||
// Allow fast tab-close cleanup even when the runtime is otherwise busy.
|
||||
if (!isTabCloseReason(reason) &&
|
||||
!IdleGCManager::shouldBypassIdleCheck(reason) &&
|
||||
!idleGC.isIdleEnough())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5842,15 +6038,15 @@ bool
|
||||
GCRuntime::shouldRepeatForDeadZone(JS::gcreason::Reason reason)
|
||||
{
|
||||
MOZ_ASSERT_IF(reason == JS::gcreason::COMPARTMENT_REVIVED, !isIncremental);
|
||||
|
||||
|
||||
if (!isIncremental || isIncrementalGCInProgress())
|
||||
return false;
|
||||
|
||||
|
||||
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
|
||||
if (c->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5872,13 +6068,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
do {
|
||||
poked = false;
|
||||
bool wasReset = gcCycle(nonincrementalByAPI, budget, reason);
|
||||
|
||||
|
||||
bool repeatForDeadZone = false;
|
||||
if (poked && cleanUpEverything) {
|
||||
/* Need to re-schedule all zones for GC. */
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
|
||||
|
||||
|
||||
} else if (shouldRepeatForDeadZone(reason) && !wasReset) {
|
||||
/*
|
||||
* This code makes an extra effort to collect compartments that we
|
||||
@@ -5888,7 +6084,7 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeatForDeadZone = true;
|
||||
reason = JS::gcreason::COMPARTMENT_REVIVED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* If we reset an existing GC, we need to start a new one. Also, we
|
||||
@@ -5912,8 +6108,16 @@ js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
|
||||
SliceBudget
|
||||
GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (millis == 0) {
|
||||
if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
if (isTabCloseReason(reason))
|
||||
millis = 3;
|
||||
else if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
millis = defaultSliceBudget();
|
||||
else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
|
||||
millis = defaultSliceBudget() * IGC_MARK_SLICE_MULTIPLIER;
|
||||
@@ -6001,6 +6205,18 @@ GCRuntime::notifyDidPaint()
|
||||
interFrameGC = false;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionStart()
|
||||
{
|
||||
idleGC.notifyJSExecutionStart();
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionEnd()
|
||||
{
|
||||
idleGC.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
static bool
|
||||
ZonesSelected(JSRuntime* rt)
|
||||
{
|
||||
|
||||
@@ -804,6 +804,7 @@ class ArenaLists
|
||||
|
||||
bool foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sliceBudget,
|
||||
SortedArenaList& sweepList);
|
||||
void backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty);
|
||||
static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty);
|
||||
|
||||
// When finalizing arenas, whether to keep empty arenas on the list or
|
||||
|
||||
@@ -172,6 +172,7 @@ main_deunified_sources = [
|
||||
'gc/Allocator.cpp',
|
||||
'gc/Barrier.cpp',
|
||||
'gc/GCTrace.cpp',
|
||||
'gc/IdleGC.cpp',
|
||||
'gc/Iteration.cpp',
|
||||
'gc/Marking.cpp',
|
||||
'gc/Memory.cpp',
|
||||
|
||||
+12
-3
@@ -3239,6 +3239,15 @@ static const JSClass sandbox_class = {
|
||||
&sandbox_classOps
|
||||
};
|
||||
|
||||
enum GlobalAppSlot {
|
||||
GlobalAppSlotModuleMetadataHook,
|
||||
GlobalAppSlotModuleDynamicImportHook,
|
||||
GlobalAppSlotCount
|
||||
};
|
||||
|
||||
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
|
||||
"global application slots overflow");
|
||||
|
||||
static void
|
||||
SetStandardCompartmentOptions(JS::CompartmentOptions& options)
|
||||
{
|
||||
@@ -4067,7 +4076,7 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
||||
JS::SourceBufferHolder srcBuf(chars, scriptContents->length(),
|
||||
SourceBufferHolder::NoOwnership);
|
||||
JS::SourceBufferHolder::NoOwnership);
|
||||
|
||||
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
|
||||
if (!module)
|
||||
@@ -4278,7 +4287,7 @@ AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp)
|
||||
RootedString specifier(cx, args[1].toString());
|
||||
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
|
||||
|
||||
cx->setPendingException(args[3]);
|
||||
cx->setPendingException(args[3], nullptr);
|
||||
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
|
||||
}
|
||||
|
||||
@@ -8287,7 +8296,7 @@ main(int argc, char** argv, char** envp)
|
||||
|
||||
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
|
||||
JS::SetModuleDynamicImportHook(cx, ShellModuleDynamicImportHook);
|
||||
JS::SetModuleMetadataHook(cx, ShellModuleMetadataHook);
|
||||
JS::SetModuleMetadataHook(cx, CallModuleMetadataHook);
|
||||
|
||||
result = Shell(cx, &op, envp);
|
||||
|
||||
|
||||
+11
-10
@@ -478,7 +478,8 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
}
|
||||
|
||||
// Clean up any parse tasks which haven't been finished by the main thread.
|
||||
GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock);
|
||||
GlobalHelperThreadState::ParseTaskVector& finished =
|
||||
HelperThreadState().parseFinishedList(lock);
|
||||
while (true) {
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < finished.length(); i++) {
|
||||
@@ -486,7 +487,8 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
if (task->runtimeMatches(rt)) {
|
||||
found = true;
|
||||
AutoUnlockHelperThreadState unlock(lock);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(),
|
||||
task->kind, task);
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
@@ -967,8 +969,7 @@ GlobalHelperThreadState::maxGCParallelThreads() const
|
||||
bool
|
||||
GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
// Don't execute an wasm job if an earlier one failed.
|
||||
if (wasmWorklist(lock).empty() || numWasmFailedJobs)
|
||||
if (wasmWorklist(lock).empty())
|
||||
return false;
|
||||
|
||||
// Honor the maximum allowed threads to compile wasm jobs at once,
|
||||
@@ -1418,13 +1419,13 @@ HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
|
||||
success = wasm::CompileFunction(task);
|
||||
}
|
||||
|
||||
// On success, try to move work to the finished list.
|
||||
if (success)
|
||||
success = HelperThreadState().wasmFinishedList(locked).append(task);
|
||||
|
||||
// On failure, note the failure for harvesting by the parent.
|
||||
// Append the task to the finished queue owned by its module generator.
|
||||
if (!success)
|
||||
HelperThreadState().noteWasmFailure(locked);
|
||||
task->setFailed();
|
||||
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!task->finishedList()->append(task))
|
||||
oomUnsafe.crash("HelperThread::handleWasmWorkload");
|
||||
|
||||
// Notify the main thread in case it's waiting.
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
|
||||
|
||||
@@ -89,8 +89,8 @@ class GlobalHelperThreadState
|
||||
wasm::IonCompileTaskPtrVector wasmWorklist_, wasmFinishedList_;
|
||||
|
||||
public:
|
||||
// For now, only allow a single parallel wasm compilation to happen at a
|
||||
// time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
|
||||
// Helper-thread initiated wasm compilations are serialized to avoid the
|
||||
// deadlock scenario described in WasmGenerator.cpp.
|
||||
mozilla::Atomic<bool> wasmCompilationInProgress;
|
||||
|
||||
private:
|
||||
|
||||
@@ -673,6 +673,12 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
|
||||
void
|
||||
JSRuntime::triggerActivityCallback(bool active)
|
||||
{
|
||||
if (active) {
|
||||
gc.notifyJSExecutionStart();
|
||||
} else {
|
||||
gc.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
if (!activityCallback)
|
||||
return;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ ModuleGenerator::ModuleGenerator(ImportVector&& imports)
|
||||
lastPatchedCallsite_(0),
|
||||
startOfUnpatchedCallsites_(0),
|
||||
parallel_(false),
|
||||
parallelCompilationInProgressOnHelperThread_(false),
|
||||
outstanding_(0),
|
||||
activeFuncDef_(nullptr),
|
||||
startedFuncDefs_(false),
|
||||
@@ -71,18 +72,21 @@ ModuleGenerator::~ModuleGenerator()
|
||||
AutoLockHelperThreadState lock;
|
||||
while (true) {
|
||||
IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
|
||||
MOZ_ASSERT(outstanding_ >= worklist.length());
|
||||
outstanding_ -= worklist.length();
|
||||
worklist.clear();
|
||||
for (size_t i = worklist.length(); i > 0;) {
|
||||
if (worklist[i - 1]->finishedList() == &finishedTasks_) {
|
||||
HelperThreadState().remove(worklist, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
|
||||
MOZ_ASSERT(outstanding_ >= finished.length());
|
||||
outstanding_ -= finished.length();
|
||||
finished.clear();
|
||||
|
||||
uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock);
|
||||
MOZ_ASSERT(outstanding_ >= numFailed);
|
||||
outstanding_ -= numFailed;
|
||||
for (size_t i = finishedTasks_.length(); i > 0;) {
|
||||
HelperThreadState().remove(finishedTasks_, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
}
|
||||
|
||||
if (!outstanding_)
|
||||
break;
|
||||
@@ -91,8 +95,10 @@ ModuleGenerator::~ModuleGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
if (parallelCompilationInProgressOnHelperThread_) {
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!outstanding_);
|
||||
}
|
||||
@@ -208,12 +214,9 @@ ModuleGenerator::finishOutstandingTask()
|
||||
while (true) {
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
|
||||
if (HelperThreadState().wasmFailed(lock))
|
||||
return false;
|
||||
|
||||
if (!HelperThreadState().wasmFinishedList(lock).empty()) {
|
||||
if (!finishedTasks_.empty()) {
|
||||
outstanding_--;
|
||||
task = HelperThreadState().wasmFinishedList(lock).popCopy();
|
||||
task = finishedTasks_.popCopy();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -221,6 +224,9 @@ ModuleGenerator::finishOutstandingTask()
|
||||
}
|
||||
}
|
||||
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
return finishTask(task);
|
||||
}
|
||||
|
||||
@@ -365,6 +371,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
|
||||
bool
|
||||
ModuleGenerator::finishTask(IonCompileTask* task)
|
||||
{
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
const FuncBytes& func = task->func();
|
||||
FuncCompileResults& results = task->results();
|
||||
|
||||
@@ -856,31 +865,25 @@ ModuleGenerator::startFuncDefs()
|
||||
MOZ_ASSERT(!startedFuncDefs_);
|
||||
MOZ_ASSERT(!finishedFuncDefs_);
|
||||
|
||||
// The wasmCompilationInProgress atomic ensures that there is only one
|
||||
// parallel compilation in progress at a time. In the special case of
|
||||
// asm.js, where the ModuleGenerator itself can be on a helper thread, this
|
||||
// avoids the possibility of deadlock since at most 1 helper thread will be
|
||||
// blocking on other helper threads and there are always >1 helper threads.
|
||||
// With wasm, this restriction could be relaxed by moving the worklist state
|
||||
// out of HelperThreadState since each independent compilation needs its own
|
||||
// worklist pair. Alternatively, the deadlock could be avoided by having the
|
||||
// ModuleGenerator thread make progress (on compile tasks) instead of
|
||||
// blocking.
|
||||
// Helper-thread initiated wasm compilations stay serialized so that we do
|
||||
// not end up with multiple helper threads blocking on other helper threads.
|
||||
// Main-thread compilations can still overlap because they drain their own
|
||||
// finished-task queue and do not steal tasks from other generators.
|
||||
|
||||
GlobalHelperThreadState& threads = HelperThreadState();
|
||||
MOZ_ASSERT(threads.threadCount > 1);
|
||||
|
||||
uint32_t numTasks;
|
||||
if (CanUseExtraThreads() && threads.wasmCompilationInProgress.compareExchange(false, true)) {
|
||||
if (CanUseExtraThreads() && (!CurrentHelperThread() ||
|
||||
threads.wasmCompilationInProgress.compareExchange(false, true))) {
|
||||
#ifdef DEBUG
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
MOZ_ASSERT(!HelperThreadState().wasmFailed(lock));
|
||||
MOZ_ASSERT(HelperThreadState().wasmWorklist(lock).empty());
|
||||
MOZ_ASSERT(HelperThreadState().wasmFinishedList(lock).empty());
|
||||
}
|
||||
#endif
|
||||
parallel_ = true;
|
||||
parallelCompilationInProgressOnHelperThread_ = !!CurrentHelperThread();
|
||||
numTasks = 2 * threads.maxWasmCompilationThreads();
|
||||
} else {
|
||||
numTasks = 1;
|
||||
@@ -891,6 +894,9 @@ ModuleGenerator::startFuncDefs()
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
tasks_.infallibleEmplaceBack(*shared_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
for (auto& task : tasks_)
|
||||
task.setFinishedList(&finishedTasks_);
|
||||
|
||||
if (!freeTasks_.reserve(numTasks))
|
||||
return false;
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
|
||||
@@ -104,9 +104,11 @@ class MOZ_STACK_CLASS ModuleGenerator
|
||||
|
||||
// Parallel compilation
|
||||
bool parallel_;
|
||||
bool parallelCompilationInProgressOnHelperThread_;
|
||||
uint32_t outstanding_;
|
||||
IonCompileTaskVector tasks_;
|
||||
IonCompileTaskPtrVector freeTasks_;
|
||||
IonCompileTaskPtrVector finishedTasks_;
|
||||
|
||||
// Assertions
|
||||
DebugOnly<FunctionGenerator*> activeFuncDef_;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define wasm_ion_compile_h
|
||||
|
||||
#include "jit/MacroAssembler.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
|
||||
namespace js {
|
||||
@@ -106,6 +107,8 @@ class IonCompileTask
|
||||
UniqueFuncBytes func_;
|
||||
CompileMode mode_;
|
||||
Maybe<FuncCompileResults> results_;
|
||||
IonCompileTaskPtrVector* finishedList_;
|
||||
bool failed_;
|
||||
|
||||
IonCompileTask(const IonCompileTask&) = delete;
|
||||
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
||||
@@ -113,7 +116,26 @@ class IonCompileTask
|
||||
public:
|
||||
IonCompileTask(const ModuleGeneratorData& mg, size_t defaultChunkSize)
|
||||
: mg_(mg), lifo_(defaultChunkSize), func_(nullptr), mode_(CompileMode::None)
|
||||
, finishedList_(nullptr)
|
||||
, failed_(false)
|
||||
{}
|
||||
|
||||
void setFinishedList(IonCompileTaskPtrVector* finishedList) {
|
||||
finishedList_ = finishedList;
|
||||
}
|
||||
|
||||
IonCompileTaskPtrVector* finishedList() const {
|
||||
MOZ_ASSERT(finishedList_);
|
||||
return finishedList_;
|
||||
}
|
||||
|
||||
void setFailed() {
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
bool failed() const {
|
||||
return failed_;
|
||||
}
|
||||
LifoAlloc& lifo() {
|
||||
return lifo_;
|
||||
}
|
||||
@@ -143,6 +165,7 @@ class IonCompileTask
|
||||
results_.reset();
|
||||
lifo_.releaseAll();
|
||||
mode_ = CompileMode::None;
|
||||
failed_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1743,7 +1743,9 @@ nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
|
||||
}
|
||||
|
||||
static bool
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
|
||||
const nsStyleColor& aColor,
|
||||
mozilla::Side aSide)
|
||||
{
|
||||
if (aBorder.GetComputedBorder().Side(aSide) == 0)
|
||||
return true;
|
||||
@@ -1765,25 +1767,20 @@ IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
|
||||
return false;
|
||||
|
||||
StyleComplexColor color = aBorder.mBorderColor[aSide];
|
||||
// We don't know the foreground color here, so if it's being used
|
||||
// we must assume it might be transparent.
|
||||
if (!color.IsNumericColor()) {
|
||||
return false;
|
||||
}
|
||||
return NS_GET_A(color.mColor) == 255;
|
||||
nscolor color = aColor.CalcComplexColor(aBorder.mBorderColor[aSide]);
|
||||
return NS_GET_A(color) == 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all border edges are either missing or opaque.
|
||||
*/
|
||||
static bool
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder)
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder, const nsStyleColor& aColor)
|
||||
{
|
||||
if (aBorder.mBorderColors)
|
||||
return false;
|
||||
NS_FOR_CSS_SIDES(i) {
|
||||
if (!IsOpaqueBorderEdge(aBorder, i))
|
||||
if (!IsOpaqueBorderEdge(aBorder, aColor, i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1916,7 +1913,7 @@ nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
|
||||
}
|
||||
|
||||
bool isSolidBorder =
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder);
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder, *aForFrame->StyleColor());
|
||||
if (isSolidBorder && layerClip == StyleGeometryBox::Border) {
|
||||
// If we have rounded corners, we need to inflate the background
|
||||
// drawing area a bit to avoid seams between the border and
|
||||
|
||||
@@ -4652,7 +4652,10 @@ nsFrame::GetIntrinsicSize()
|
||||
/* virtual */ AspectRatio
|
||||
nsFrame::GetIntrinsicRatio()
|
||||
{
|
||||
return AspectRatio();
|
||||
const nsStylePosition* stylePos = StylePosition();
|
||||
return stylePos->mAspectRatio == 0.0f
|
||||
? AspectRatio()
|
||||
: AspectRatio(stylePos->mAspectRatio);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4721,9 +4724,16 @@ nsFrame::ComputeSize(nsRenderingContext* aRenderingContext,
|
||||
const LogicalSize& aPadding,
|
||||
ComputeSizeFlags aFlags)
|
||||
{
|
||||
MOZ_ASSERT(!GetIntrinsicRatio(),
|
||||
"Please override this method and call "
|
||||
"nsFrame::ComputeSizeWithIntrinsicDimensions instead.");
|
||||
AspectRatio intrinsicRatio = GetIntrinsicRatio();
|
||||
if (intrinsicRatio) {
|
||||
return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
|
||||
GetIntrinsicSize(),
|
||||
intrinsicRatio,
|
||||
aCBSize, aMargin,
|
||||
aBorder, aPadding,
|
||||
aFlags);
|
||||
}
|
||||
|
||||
LogicalSize result = ComputeAutoSize(aRenderingContext, aWM,
|
||||
aCBSize, aAvailableISize,
|
||||
aMargin, aBorder, aPadding,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid var(--color);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid currentcolor;
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -89,6 +89,8 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.te
|
||||
# Test for antialiasing gaps between background and border
|
||||
fuzzy-if(gtkWidget,1,9) fuzzy-if(winWidget&&!d2d,1,9) fuzzy-if(d2d,5,40) fuzzy-if(skiaContent,1,9) == curved-border-background-nogap.html curved-border-background-nogap-ref.html
|
||||
|
||||
== currentcolor-border-radius.html currentcolor-border-radius-ref.html
|
||||
|
||||
== color-layer-1a.html color-layer-1-ref.html
|
||||
|
||||
== corner-split.html corner-split-ref.svg # bug 1185636
|
||||
|
||||
+109
-11
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "nsCSSValue.h"
|
||||
#include "nsStyleCoord.h"
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
|
||||
namespace mozilla {
|
||||
@@ -46,6 +47,13 @@ namespace css {
|
||||
* result_type aValue1, float aValue2);
|
||||
*
|
||||
* result_type
|
||||
* MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
* result_type aValue1, result_type aValue2);
|
||||
*
|
||||
* result_type
|
||||
* MergeClamp(result_type aMin, result_type aCenter, result_type aMax);
|
||||
*
|
||||
* result_type
|
||||
* ComputeLeafValue(const input_type& aValue);
|
||||
*
|
||||
* float
|
||||
@@ -70,6 +78,8 @@ namespace css {
|
||||
* MergeAdditive for Plus and Minus
|
||||
* MergeMultiplicativeL for Times_L (number * value)
|
||||
* MergeMultiplicativeR for Times_R (value * number) and Divided
|
||||
* MergeMinMax for Min and Max
|
||||
* MergeClamp for Clamp
|
||||
*/
|
||||
template <class CalcOps>
|
||||
static typename CalcOps::result_type
|
||||
@@ -104,6 +114,25 @@ ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
float rhs = aOps.ComputeNumber(arr->Item(1));
|
||||
return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
|
||||
}
|
||||
case eCSSUnit_Calc_Min:
|
||||
case eCSSUnit_Calc_Max: {
|
||||
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(arr->Count() >= 1, "unexpected length");
|
||||
typename CalcOps::result_type result = ComputeCalc(arr->Item(0), aOps);
|
||||
for (uint32_t i = 1; i < arr->Count(); ++i) {
|
||||
typename CalcOps::result_type next = ComputeCalc(arr->Item(i), aOps);
|
||||
result = aOps.MergeMinMax(CalcOps::GetUnit(aValue), result, next);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case eCSSUnit_Calc_Clamp: {
|
||||
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(arr->Count() == 3, "unexpected length");
|
||||
typename CalcOps::result_type min = ComputeCalc(arr->Item(0), aOps);
|
||||
typename CalcOps::result_type center = ComputeCalc(arr->Item(1), aOps);
|
||||
typename CalcOps::result_type max = ComputeCalc(arr->Item(2), aOps);
|
||||
return aOps.MergeClamp(min, center, max);
|
||||
}
|
||||
default: {
|
||||
return aOps.ComputeLeafValue(aValue);
|
||||
}
|
||||
@@ -168,6 +197,23 @@ struct BasicCoordCalcOps
|
||||
}
|
||||
return NSCoordSaturatingMultiply(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
return std::min(aValue1, aValue2);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
return std::max(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
return std::max(aMin, std::min(aCenter, aMax));
|
||||
}
|
||||
};
|
||||
|
||||
struct BasicFloatCalcOps
|
||||
@@ -206,6 +252,23 @@ struct BasicFloatCalcOps
|
||||
"unexpected unit");
|
||||
return aValue1 / aValue2;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
return std::min(aValue1, aValue2);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
return std::max(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
return std::max(aMin, std::min(aCenter, aMax));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -255,8 +318,20 @@ template <class CalcOps>
|
||||
static void
|
||||
SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
{
|
||||
aOps.Append("calc(");
|
||||
nsCSSUnit unit = CalcOps::GetUnit(aValue);
|
||||
if (unit == eCSSUnit_Calc) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
nsCSSUnit childUnit = CalcOps::GetUnit(array->Item(0));
|
||||
if (childUnit == eCSSUnit_Calc_Min ||
|
||||
childUnit == eCSSUnit_Calc_Max ||
|
||||
childUnit == eCSSUnit_Calc_Clamp) {
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aOps.Append("calc(");
|
||||
if (unit == eCSSUnit_Calc) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
@@ -282,6 +357,14 @@ IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
|
||||
aUnit == eCSSUnit_Calc_Divided;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsCalcMinMaxClampUnit(nsCSSUnit aUnit)
|
||||
{
|
||||
return aUnit == eCSSUnit_Calc_Min ||
|
||||
aUnit == eCSSUnit_Calc_Max ||
|
||||
aUnit == eCSSUnit_Calc_Clamp;
|
||||
}
|
||||
|
||||
// Serialize a non-toplevel value in a calc() tree. See big comment
|
||||
// above.
|
||||
template <class CalcOps>
|
||||
@@ -318,11 +401,7 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
if (needParens) {
|
||||
aOps.Append("(");
|
||||
}
|
||||
if (unit == eCSSUnit_Calc_Times_L) {
|
||||
aOps.AppendNumber(array->Item(0));
|
||||
} else {
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
}
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
if (needParens) {
|
||||
aOps.Append(")");
|
||||
}
|
||||
@@ -340,14 +419,33 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
if (needParens) {
|
||||
aOps.Append("(");
|
||||
}
|
||||
if (unit == eCSSUnit_Calc_Times_L) {
|
||||
SerializeCalcInternal(array->Item(1), aOps);
|
||||
} else {
|
||||
aOps.AppendNumber(array->Item(1));
|
||||
}
|
||||
SerializeCalcInternal(array->Item(1), aOps);
|
||||
if (needParens) {
|
||||
aOps.Append(")");
|
||||
}
|
||||
} else if (IsCalcMinMaxClampUnit(unit)) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(unit != eCSSUnit_Calc_Clamp || array->Count() == 3,
|
||||
"unexpected length");
|
||||
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp || array->Count() >= 1,
|
||||
"unexpected length");
|
||||
|
||||
if (unit == eCSSUnit_Calc_Min) {
|
||||
aOps.Append("min(");
|
||||
} else if (unit == eCSSUnit_Calc_Max) {
|
||||
aOps.Append("max(");
|
||||
} else {
|
||||
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp, "unexpected unit");
|
||||
aOps.Append("clamp(");
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < array->Count(); ++i) {
|
||||
if (i != 0) {
|
||||
aOps.Append(", ");
|
||||
}
|
||||
SerializeCalcInternal(array->Item(i), aOps);
|
||||
}
|
||||
aOps.Append(")");
|
||||
} else {
|
||||
aOps.AppendLeafValue(aValue);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "mozilla/CSSStyleSheet.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "nsIAtom.h"
|
||||
#include "nsCSSRuleProcessor.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
@@ -130,6 +132,153 @@ int32_t DoCompare(Numeric a, Numeric b)
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct MediaQueryTypedCalcLengthResult
|
||||
{
|
||||
double mValue;
|
||||
int32_t mExponent;
|
||||
};
|
||||
|
||||
static bool
|
||||
EvaluateMediaQueryTypedCalcLength(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue,
|
||||
MediaQueryTypedCalcLengthResult& aResult)
|
||||
{
|
||||
switch (aValue.GetUnit()) {
|
||||
case eCSSUnit_Calc: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
return EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
|
||||
aResult);
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Plus:
|
||||
case eCSSUnit_Calc_Minus: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
MediaQueryTypedCalcLengthResult lhs;
|
||||
MediaQueryTypedCalcLengthResult rhs;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs) ||
|
||||
lhs.mExponent != rhs.mExponent) {
|
||||
return false;
|
||||
}
|
||||
aResult.mExponent = lhs.mExponent;
|
||||
aResult.mValue = aValue.GetUnit() == eCSSUnit_Calc_Plus
|
||||
? lhs.mValue + rhs.mValue
|
||||
: lhs.mValue - rhs.mValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Times_L:
|
||||
case eCSSUnit_Calc_Times_R:
|
||||
case eCSSUnit_Calc_Divided: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
MediaQueryTypedCalcLengthResult lhs;
|
||||
MediaQueryTypedCalcLengthResult rhs;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Divided) {
|
||||
if (rhs.mValue == 0.0) {
|
||||
return false;
|
||||
}
|
||||
aResult.mValue = lhs.mValue / rhs.mValue;
|
||||
aResult.mExponent = lhs.mExponent - rhs.mExponent;
|
||||
} else {
|
||||
aResult.mValue = lhs.mValue * rhs.mValue;
|
||||
aResult.mExponent = lhs.mExponent + rhs.mExponent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Min:
|
||||
case eCSSUnit_Calc_Max:
|
||||
case eCSSUnit_Calc_Clamp: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Calc_Clamp ||
|
||||
array->Count() == 3, "unexpected length");
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc_Clamp ||
|
||||
array->Count() >= 1, "unexpected length");
|
||||
|
||||
MediaQueryTypedCalcLengthResult result;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
|
||||
result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Clamp) {
|
||||
MediaQueryTypedCalcLengthResult center;
|
||||
MediaQueryTypedCalcLengthResult max;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1),
|
||||
center) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(2),
|
||||
max) ||
|
||||
result.mExponent != center.mExponent ||
|
||||
result.mExponent != max.mExponent) {
|
||||
return false;
|
||||
}
|
||||
aResult.mExponent = result.mExponent;
|
||||
aResult.mValue = std::max(result.mValue,
|
||||
std::min(center.mValue, max.mValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < array->Count(); ++i) {
|
||||
MediaQueryTypedCalcLengthResult item;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(i),
|
||||
item) ||
|
||||
result.mExponent != item.mExponent) {
|
||||
return false;
|
||||
}
|
||||
result.mValue = aValue.GetUnit() == eCSSUnit_Calc_Min
|
||||
? std::min(result.mValue, item.mValue)
|
||||
: std::max(result.mValue, item.mValue);
|
||||
}
|
||||
aResult = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Number:
|
||||
aResult.mValue = aValue.GetFloatValue();
|
||||
aResult.mExponent = 0;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!aValue.IsLengthUnit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aResult.mValue = double(nsRuleNode::CalcLengthWithInitialFont(aPresContext,
|
||||
aValue));
|
||||
aResult.mExponent = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ComputeMediaQueryTypedCalcLength(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue,
|
||||
nscoord& aResult)
|
||||
{
|
||||
if (aValue.IsLengthUnit()) {
|
||||
aResult = nsRuleNode::CalcLengthWithInitialFont(aPresContext, aValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaQueryTypedCalcLengthResult result;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, aValue, result) ||
|
||||
result.mExponent != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aResult = NSToCoordFloorClamped(result.mValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
const nsCSSValue& aActualValue) const
|
||||
@@ -161,11 +310,15 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
case nsMediaFeature::eLength:
|
||||
{
|
||||
NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
|
||||
NS_ASSERTION(required.IsLengthUnit(), "bad required value");
|
||||
NS_ASSERTION(required.IsLengthUnit() || required.IsCalcUnit(),
|
||||
"bad required value");
|
||||
nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
|
||||
aPresContext, actual);
|
||||
nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
|
||||
aPresContext, required);
|
||||
nscoord requiredCoord;
|
||||
if (!ComputeMediaQueryTypedCalcLength(aPresContext, required,
|
||||
requiredCoord)) {
|
||||
return false;
|
||||
}
|
||||
cmp = DoCompare(actualCoord, requiredCoord);
|
||||
}
|
||||
break;
|
||||
@@ -283,6 +436,10 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
return cmp != 1;
|
||||
case nsMediaExpression::eEqual:
|
||||
return cmp == 0;
|
||||
case nsMediaExpression::eMinExclusive:
|
||||
return cmp == 1;
|
||||
case nsMediaExpression::eMaxExclusive:
|
||||
return cmp == -1;
|
||||
}
|
||||
NS_NOTREACHED("unexpected mRange");
|
||||
return false;
|
||||
@@ -487,10 +644,17 @@ nsMediaQuery::AppendToString(nsAString& aString) const
|
||||
aString.Append(nsDependentAtomString(*feature->mName));
|
||||
|
||||
if (expr.mValue.GetUnit() != eCSSUnit_Null) {
|
||||
aString.AppendLiteral(": ");
|
||||
if (expr.mRange == nsMediaExpression::eMinExclusive) {
|
||||
aString.AppendLiteral(" > ");
|
||||
} else if (expr.mRange == nsMediaExpression::eMaxExclusive) {
|
||||
aString.AppendLiteral(" < ");
|
||||
} else {
|
||||
aString.AppendLiteral(": ");
|
||||
}
|
||||
switch (feature->mValueType) {
|
||||
case nsMediaFeature::eLength:
|
||||
NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
|
||||
NS_ASSERTION(expr.mValue.IsLengthUnit() || expr.mValue.IsCalcUnit(),
|
||||
"bad unit");
|
||||
// Use 'width' as a property that takes length values
|
||||
// written in the normal way.
|
||||
expr.mValue.AppendToString(eCSSProperty_width, aString,
|
||||
|
||||
@@ -404,6 +404,22 @@ RuleHash::AppendUniversalRule(const RuleSelectorPair& aRuleInfo)
|
||||
RuleValue(aRuleInfo, mRuleCount++, mQuirksMode));
|
||||
}
|
||||
|
||||
static bool
|
||||
SelectorContainsPseudoClass(nsCSSSelector* aSelector, CSSPseudoClassType aType)
|
||||
{
|
||||
for (nsCSSSelector* selector = aSelector; selector; selector = selector->mNext) {
|
||||
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
|
||||
pseudoClass;
|
||||
pseudoClass = pseudoClass->mNext) {
|
||||
if (pseudoClass->mType == aType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo)
|
||||
{
|
||||
@@ -411,7 +427,10 @@ RuleHash::AppendRule(const RuleSelectorPair& aRuleInfo)
|
||||
if (selector->IsPseudoElement()) {
|
||||
selector = selector->mNext;
|
||||
}
|
||||
if (nullptr != selector->mIDList) {
|
||||
if (SelectorContainsPseudoClass(selector, CSSPseudoClassType::part)) {
|
||||
AppendUniversalRule(aRuleInfo);
|
||||
RULE_HASH_STAT_INCREMENT(mUniversalSelectors);
|
||||
} else if (nullptr != selector->mIDList) {
|
||||
AppendRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo);
|
||||
RULE_HASH_STAT_INCREMENT(mIdSelectors);
|
||||
} else if (nullptr != selector->mClassList) {
|
||||
@@ -452,6 +471,10 @@ LookForTargetPseudo(nsCSSSelector* aSelector,
|
||||
nsRestyleHint* possibleChange)
|
||||
{
|
||||
if (aMatchContext->mOnlyMatchHostPseudo) {
|
||||
if (SelectorContainsPseudoClass(aSelector, CSSPseudoClassType::part)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (aSelector && aSelector->mNext != nullptr) {
|
||||
aSelector = aSelector->mNext;
|
||||
}
|
||||
@@ -502,7 +525,8 @@ ContentEnumFunc(const RuleValue& value,
|
||||
data->mTreeMatchContext.SetHaveRelevantLink();
|
||||
}
|
||||
// XXX: Ignore the ancestor filter if we're testing the assigned slot.
|
||||
bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot);
|
||||
bool useAncestorFilter = !(data->mTreeMatchContext.mForAssignedSlot ||
|
||||
data->mTreeMatchContext.mOnlyMatchHostPseudo);
|
||||
if (useAncestorFilter && ancestorFilter &&
|
||||
!ancestorFilter->MightHaveMatchingAncestor<RuleValue::eMaxAncestorHashes>(
|
||||
value.mAncestorSelectorHashes)) {
|
||||
@@ -556,9 +580,18 @@ ContentEnumFunc(const RuleValue& value,
|
||||
nodeContext,
|
||||
data->mTreeMatchContext,
|
||||
selectorFlags)) {
|
||||
Element* treeMatchStart = data->mElement;
|
||||
if (SelectorContainsPseudoClass(selector, CSSPseudoClassType::part)) {
|
||||
nsIContent* partHost = data->mElement->GetContainingShadowHost();
|
||||
if (!partHost || !partHost->IsElement()) {
|
||||
return;
|
||||
}
|
||||
treeMatchStart = partHost->AsElement();
|
||||
}
|
||||
|
||||
nsCSSSelector* next = selector->mNext;
|
||||
if (!next || nsCSSRuleUtils::SelectorMatchesTree(
|
||||
data->mElement,
|
||||
treeMatchStart,
|
||||
next,
|
||||
data->mTreeMatchContext,
|
||||
nodeContext.mIsRelevantLink ? SelectorMatchesTreeFlags(0)
|
||||
|
||||
@@ -335,6 +335,7 @@ nsCSSSelector::Clone(bool aDeepNext, bool aDeepNegations) const
|
||||
result->mCasedTag = mCasedTag;
|
||||
result->mOperator = mOperator;
|
||||
result->mPseudoType = mPseudoType;
|
||||
result->mHybridPseudoType = mHybridPseudoType;
|
||||
|
||||
NS_IF_CLONE(mIDList);
|
||||
NS_IF_CLONE(mClassList);
|
||||
|
||||
@@ -209,11 +209,38 @@ MapSinglePropertyInto(nsCSSPropertyID aTargetProp,
|
||||
* property based on writing mode information obtained from aRuleData's
|
||||
* style context.
|
||||
*/
|
||||
static inline int
|
||||
PhysicalCornerIndexForLogicalCorner(mozilla::Side aBlockSide,
|
||||
mozilla::Side aInlineSide)
|
||||
{
|
||||
// gBorderRadiusSubpropTable is in top-left, top-right, bottom-right,
|
||||
// bottom-left order.
|
||||
switch (aBlockSide) {
|
||||
case eSideTop:
|
||||
MOZ_ASSERT(aInlineSide == eSideLeft || aInlineSide == eSideRight);
|
||||
return aInlineSide == eSideLeft ? 0 : 1;
|
||||
case eSideRight:
|
||||
MOZ_ASSERT(aInlineSide == eSideTop || aInlineSide == eSideBottom);
|
||||
return aInlineSide == eSideTop ? 1 : 2;
|
||||
case eSideBottom:
|
||||
MOZ_ASSERT(aInlineSide == eSideLeft || aInlineSide == eSideRight);
|
||||
return aInlineSide == eSideRight ? 2 : 3;
|
||||
case eSideLeft:
|
||||
MOZ_ASSERT(aInlineSide == eSideTop || aInlineSide == eSideBottom);
|
||||
return aInlineSide == eSideTop ? 0 : 3;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("unexpected physical side");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
EnsurePhysicalProperty(nsCSSPropertyID& aProperty, nsRuleData* aRuleData)
|
||||
{
|
||||
bool isAxisProperty =
|
||||
nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_AXIS);
|
||||
bool isCornerProperty =
|
||||
nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_CORNER);
|
||||
bool isBlock =
|
||||
nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_BLOCK_AXIS);
|
||||
|
||||
@@ -230,6 +257,23 @@ EnsurePhysicalProperty(nsCSSPropertyID& aProperty, nsRuleData* aRuleData)
|
||||
static_assert(eAxisVertical == 0 && eAxisHorizontal == 1,
|
||||
"unexpected axis constant values");
|
||||
index = axis;
|
||||
} else if (isCornerProperty) {
|
||||
bool isInlineEnd =
|
||||
nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_END_EDGE);
|
||||
|
||||
LogicalEdge blockEdge =
|
||||
isBlock ? eLogicalEdgeEnd : eLogicalEdgeStart;
|
||||
LogicalEdge inlineEdge =
|
||||
isInlineEnd ? eLogicalEdgeEnd : eLogicalEdgeStart;
|
||||
|
||||
uint8_t wmBits = aRuleData->mStyleContext->StyleVisibility()->mWritingMode;
|
||||
mozilla::Side blockSide =
|
||||
WritingMode::PhysicalSideForBlockAxis(wmBits, blockEdge);
|
||||
|
||||
WritingMode wm(aRuleData->mStyleContext);
|
||||
mozilla::Side inlineSide = wm.PhysicalSideForInlineAxis(inlineEdge);
|
||||
|
||||
index = PhysicalCornerIndexForLogicalCorner(blockSide, inlineSide);
|
||||
} else {
|
||||
bool isEnd =
|
||||
nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_LOGICAL_END_EDGE);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef nsCSSNonSRGBColorSpace_h___
|
||||
#define nsCSSNonSRGBColorSpace_h___
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
@@ -18,7 +19,46 @@ namespace css {
|
||||
static constexpr float kLabLightnessMax = 100.0f;
|
||||
static constexpr float kLchPercentScaleC = 150.0f;
|
||||
static constexpr float kOklabPercentScaleAB = 0.4f;
|
||||
static constexpr float kOklchPowerlessChromaEpsilon = 0.000004f;
|
||||
static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489;
|
||||
static constexpr double kDegreesPerRadian = 57.295779513082320876798154814105;
|
||||
|
||||
struct OklabColor {
|
||||
float mL;
|
||||
float mA;
|
||||
float mB;
|
||||
};
|
||||
|
||||
struct OklchColor {
|
||||
float mL;
|
||||
float mChroma;
|
||||
float mHue;
|
||||
};
|
||||
|
||||
struct LinearSRGBColor {
|
||||
float mR;
|
||||
float mG;
|
||||
float mB;
|
||||
};
|
||||
|
||||
inline float
|
||||
NormalizeHue(float aHue)
|
||||
{
|
||||
float hue = std::fmod(aHue, 360.0f);
|
||||
if (hue < 0.0f) {
|
||||
hue += 360.0f;
|
||||
}
|
||||
return hue;
|
||||
}
|
||||
|
||||
inline float
|
||||
EncodedSRGBToLinear(float aValue)
|
||||
{
|
||||
if (aValue <= 0.04045f) {
|
||||
return aValue / 12.92f;
|
||||
}
|
||||
return std::pow((aValue + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
inline float
|
||||
LinearSRGBToEncoded(float aValue)
|
||||
@@ -44,43 +84,159 @@ LinearSRGBToColor(float aLinearR, float aLinearG, float aLinearB,
|
||||
aAlpha);
|
||||
}
|
||||
|
||||
inline nscolor
|
||||
OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
|
||||
inline LinearSRGBColor
|
||||
OklabToLinearSRGB(float aL, float aA, float aB)
|
||||
{
|
||||
// Per CSS Color, the lightness component for Oklab/Oklch is clamped.
|
||||
float lightness = mozilla::clamped(aL, 0.0f, 1.0f);
|
||||
uint8_t alpha =
|
||||
nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f));
|
||||
|
||||
// Treat values extremely close to zero as zero to avoid tiny floating-point
|
||||
// representation differences for percentage inputs.
|
||||
static constexpr float kLightnessEndpointEpsilon = 0.000002f;
|
||||
|
||||
if (lightness <= kLightnessEndpointEpsilon) {
|
||||
return NS_RGBA(0, 0, 0, alpha);
|
||||
}
|
||||
|
||||
float lRoot = lightness + 0.3963377774f * aA + 0.2158037573f * aB;
|
||||
float mRoot = lightness - 0.1055613458f * aA - 0.0638541728f * aB;
|
||||
float sRoot = lightness - 0.0894841775f * aA - 1.2914855480f * aB;
|
||||
float lRoot = aL + 0.3963377774f * aA + 0.2158037573f * aB;
|
||||
float mRoot = aL - 0.1055613458f * aA - 0.0638541728f * aB;
|
||||
float sRoot = aL - 0.0894841775f * aA - 1.2914855480f * aB;
|
||||
|
||||
float l = lRoot * lRoot * lRoot;
|
||||
float m = mRoot * mRoot * mRoot;
|
||||
float s = sRoot * sRoot * sRoot;
|
||||
|
||||
float linearR = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
|
||||
float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
|
||||
float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
|
||||
return {
|
||||
4.0767416361f * l - 3.3077115393f * m + 0.2309699032f * s,
|
||||
-1.2684379733f * l + 2.6097573493f * m - 0.3413193760f * s,
|
||||
-0.0041960761f * l - 0.7034186179f * m + 1.7076146941f * s
|
||||
};
|
||||
}
|
||||
|
||||
return LinearSRGBToColor(linearR, linearG, linearB, alpha);
|
||||
inline OklabColor
|
||||
LinearSRGBToOklabColor(float aLinearR, float aLinearG, float aLinearB)
|
||||
{
|
||||
float l = std::cbrt(0.4122214695f * aLinearR +
|
||||
0.5363325373f * aLinearG +
|
||||
0.0514459933f * aLinearB);
|
||||
float m = std::cbrt(0.2119034958f * aLinearR +
|
||||
0.6806995506f * aLinearG +
|
||||
0.1073969535f * aLinearB);
|
||||
float s = std::cbrt(0.0883024592f * aLinearR +
|
||||
0.2817188391f * aLinearG +
|
||||
0.6299787017f * aLinearB);
|
||||
|
||||
return {
|
||||
0.2104542683f * l + 0.7936177747f * m - 0.0040720430f * s,
|
||||
1.9779985324f * l - 2.4285922420f * m + 0.4505937096f * s,
|
||||
0.0259040425f * l + 0.7827717125f * m - 0.8086757549f * s
|
||||
};
|
||||
}
|
||||
|
||||
inline OklabColor
|
||||
SRGBToOklabColor(nscolor aColor)
|
||||
{
|
||||
float linearR = EncodedSRGBToLinear(NS_GET_R(aColor) / 255.0f);
|
||||
float linearG = EncodedSRGBToLinear(NS_GET_G(aColor) / 255.0f);
|
||||
float linearB = EncodedSRGBToLinear(NS_GET_B(aColor) / 255.0f);
|
||||
|
||||
return LinearSRGBToOklabColor(linearR, linearG, linearB);
|
||||
}
|
||||
|
||||
inline OklchColor
|
||||
OklabToOklchColor(const OklabColor& aColor)
|
||||
{
|
||||
float chroma = std::sqrt(aColor.mA * aColor.mA + aColor.mB * aColor.mB);
|
||||
float hue = std::atan2(aColor.mB, aColor.mA) * kDegreesPerRadian;
|
||||
if (hue < 0.0f) {
|
||||
hue += 360.0f;
|
||||
}
|
||||
|
||||
return { aColor.mL, chroma, hue };
|
||||
}
|
||||
|
||||
inline nscolor
|
||||
OklabToSRGBColor(float aL, float aA, float aB, float aAlpha)
|
||||
{
|
||||
// Per CSS Color, the lightness component for Oklab/Oklch is clamped at
|
||||
// parsed-value time.
|
||||
float lightness = mozilla::clamped(aL, 0.0f, 1.0f);
|
||||
uint8_t alpha =
|
||||
nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f));
|
||||
|
||||
OklabColor mapped = { lightness, aA, aB };
|
||||
auto isInSRGBGamut = [](const OklabColor& aColor) {
|
||||
LinearSRGBColor linear =
|
||||
OklabToLinearSRGB(aColor.mL, aColor.mA, aColor.mB);
|
||||
return linear.mR >= 0.0f && linear.mR <= 1.0f &&
|
||||
linear.mG >= 0.0f && linear.mG <= 1.0f &&
|
||||
linear.mB >= 0.0f && linear.mB <= 1.0f;
|
||||
};
|
||||
auto clippedOklab = [](const OklabColor& aColor) {
|
||||
LinearSRGBColor linear =
|
||||
OklabToLinearSRGB(aColor.mL, aColor.mA, aColor.mB);
|
||||
return LinearSRGBToOklabColor(
|
||||
mozilla::clamped(linear.mR, 0.0f, 1.0f),
|
||||
mozilla::clamped(linear.mG, 0.0f, 1.0f),
|
||||
mozilla::clamped(linear.mB, 0.0f, 1.0f));
|
||||
};
|
||||
auto deltaEOK = [](const OklabColor& aColor1, const OklabColor& aColor2) {
|
||||
float deltaL = aColor1.mL - aColor2.mL;
|
||||
float deltaA = aColor1.mA - aColor2.mA;
|
||||
float deltaB = aColor1.mB - aColor2.mB;
|
||||
return std::sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
|
||||
};
|
||||
|
||||
if (lightness <= 0.0f) {
|
||||
mapped = { 0.0f, 0.0f, 0.0f };
|
||||
} else if (lightness >= 1.0f) {
|
||||
mapped = { 1.0f, 0.0f, 0.0f };
|
||||
} else if (!isInSRGBGamut(mapped)) {
|
||||
// CSS Color 4 binary search gamut mapping with local MINDE, targeting sRGB.
|
||||
static constexpr float kJND = 0.02f;
|
||||
static constexpr float kGamutMapEpsilon = 0.0001f;
|
||||
|
||||
OklchColor origin = OklabToOklchColor(mapped);
|
||||
OklabColor clipped = clippedOklab(mapped);
|
||||
float delta = deltaEOK(clipped, mapped);
|
||||
|
||||
if (delta < kJND) {
|
||||
mapped = clipped;
|
||||
} else {
|
||||
float min = 0.0f;
|
||||
float max = origin.mChroma;
|
||||
bool minInGamut = true;
|
||||
OklabColor current = mapped;
|
||||
|
||||
while (max - min > kGamutMapEpsilon) {
|
||||
float chroma = (min + max) / 2.0f;
|
||||
current.mL = origin.mL;
|
||||
current.mA = chroma * std::cos(origin.mHue * kRadiansPerDegree);
|
||||
current.mB = chroma * std::sin(origin.mHue * kRadiansPerDegree);
|
||||
|
||||
if (minInGamut && isInSRGBGamut(current)) {
|
||||
min = chroma;
|
||||
continue;
|
||||
}
|
||||
|
||||
clipped = clippedOklab(current);
|
||||
delta = deltaEOK(clipped, current);
|
||||
if (delta < kJND) {
|
||||
if (kJND - delta < kGamutMapEpsilon) {
|
||||
mapped = clipped;
|
||||
break;
|
||||
}
|
||||
minInGamut = false;
|
||||
min = chroma;
|
||||
} else {
|
||||
max = chroma;
|
||||
}
|
||||
mapped = clipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinearSRGBColor linear =
|
||||
OklabToLinearSRGB(mapped.mL, mapped.mA, mapped.mB);
|
||||
return LinearSRGBToColor(linear.mR, linear.mG, linear.mB, alpha);
|
||||
}
|
||||
|
||||
inline nscolor
|
||||
OklchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha)
|
||||
{
|
||||
double hueRadians = aHue * kRadiansPerDegree;
|
||||
float a = aChroma * std::cos(hueRadians);
|
||||
float b = aChroma * std::sin(hueRadians);
|
||||
float chroma = std::max(aChroma, 0.0f);
|
||||
double hueRadians = NormalizeHue(aHue) * kRadiansPerDegree;
|
||||
float a = chroma * std::cos(hueRadians);
|
||||
float b = chroma * std::sin(hueRadians);
|
||||
return OklabToSRGBColor(aL, a, b, aAlpha);
|
||||
}
|
||||
|
||||
|
||||
+771
-63
File diff suppressed because it is too large
Load Diff
@@ -470,19 +470,17 @@ CSS_PROP_DISPLAY(
|
||||
kAppearanceKTable,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_Discrete)
|
||||
#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
|
||||
CSS_PROP_POSITION(
|
||||
aspect-ratio,
|
||||
aspect_ratio,
|
||||
AspectRatio,
|
||||
CSS_PROPERTY_INTERNAL |
|
||||
CSS_PROPERTY_PARSE_INACCESSIBLE,
|
||||
CSS_PROPERTY_PARSE_VALUE |
|
||||
CSS_PROPERTY_VALUE_PARSER_FUNCTION,
|
||||
"",
|
||||
VARIANT_NUMBER,
|
||||
0,
|
||||
nullptr,
|
||||
offsetof(nsStylePosition, mAspectRatio),
|
||||
eStyleAnimType_None)
|
||||
#endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
|
||||
CSS_PROP_DISPLAY(
|
||||
backface-visibility,
|
||||
backface_visibility,
|
||||
@@ -885,6 +883,43 @@ CSS_PROP_SHORTHAND(
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_HASHLESS_COLOR_QUIRK,
|
||||
"")
|
||||
CSS_PROP_LOGICAL(
|
||||
border-end-end-radius,
|
||||
border_end_end_radius,
|
||||
BorderEndEndRadius,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
|
||||
CSS_PROPERTY_VALUE_NONNEGATIVE |
|
||||
CSS_PROPERTY_STORES_CALC |
|
||||
CSS_PROPERTY_LOGICAL |
|
||||
CSS_PROPERTY_LOGICAL_CORNER |
|
||||
CSS_PROPERTY_LOGICAL_BLOCK_AXIS |
|
||||
CSS_PROPERTY_LOGICAL_END_EDGE,
|
||||
"",
|
||||
0,
|
||||
nullptr,
|
||||
BorderRadius,
|
||||
Border,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_None)
|
||||
CSS_PROP_LOGICAL(
|
||||
border-end-start-radius,
|
||||
border_end_start_radius,
|
||||
BorderEndStartRadius,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
|
||||
CSS_PROPERTY_VALUE_NONNEGATIVE |
|
||||
CSS_PROPERTY_STORES_CALC |
|
||||
CSS_PROPERTY_LOGICAL |
|
||||
CSS_PROPERTY_LOGICAL_CORNER |
|
||||
CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
|
||||
"",
|
||||
0,
|
||||
nullptr,
|
||||
BorderRadius,
|
||||
Border,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_None)
|
||||
CSS_PROP_SHORTHAND(
|
||||
border-image,
|
||||
border_image,
|
||||
@@ -1180,6 +1215,41 @@ CSS_PROP_TABLEBORDER(
|
||||
nullptr,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_Custom)
|
||||
CSS_PROP_LOGICAL(
|
||||
border-start-end-radius,
|
||||
border_start_end_radius,
|
||||
BorderStartEndRadius,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
|
||||
CSS_PROPERTY_VALUE_NONNEGATIVE |
|
||||
CSS_PROPERTY_STORES_CALC |
|
||||
CSS_PROPERTY_LOGICAL |
|
||||
CSS_PROPERTY_LOGICAL_CORNER |
|
||||
CSS_PROPERTY_LOGICAL_END_EDGE,
|
||||
"",
|
||||
0,
|
||||
nullptr,
|
||||
BorderRadius,
|
||||
Border,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_None)
|
||||
CSS_PROP_LOGICAL(
|
||||
border-start-start-radius,
|
||||
border_start_start_radius,
|
||||
BorderStartStartRadius,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
|
||||
CSS_PROPERTY_VALUE_NONNEGATIVE |
|
||||
CSS_PROPERTY_STORES_CALC |
|
||||
CSS_PROPERTY_LOGICAL |
|
||||
CSS_PROPERTY_LOGICAL_CORNER,
|
||||
"",
|
||||
0,
|
||||
nullptr,
|
||||
BorderRadius,
|
||||
Border,
|
||||
CSS_PROP_NO_OFFSET,
|
||||
eStyleAnimType_None)
|
||||
CSS_PROP_SHORTHAND(
|
||||
border-style,
|
||||
border_style,
|
||||
@@ -1273,7 +1343,8 @@ CSS_PROP_SHORTHAND(
|
||||
border_width,
|
||||
BorderWidth,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
|
||||
CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
|
||||
CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
|
||||
"")
|
||||
CSS_PROP_POSITION(
|
||||
bottom,
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
// order, followed by an nsCSSProperty_UNKNOWN entry.
|
||||
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderColor)
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderRadius)
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderStyle)
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(BorderWidth)
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(Margin)
|
||||
@@ -54,4 +55,4 @@ CSS_PROP_LOGICAL_GROUP_BOX(Offset)
|
||||
CSS_PROP_LOGICAL_GROUP_SHORTHAND(Padding)
|
||||
CSS_PROP_LOGICAL_GROUP_AXIS(MinSize)
|
||||
CSS_PROP_LOGICAL_GROUP_AXIS(Size)
|
||||
CSS_PROP_LOGICAL_GROUP_AXIS(Overflow)
|
||||
CSS_PROP_LOGICAL_GROUP_AXIS(Overflow)
|
||||
|
||||
@@ -3492,7 +3492,10 @@ nsCSSProps::gPropertyEnabled[eCSSProperty_COUNT_with_aliases] = {
|
||||
"the CSS_PROPERTY_LOGICAL_BLOCK_AXIS flag"); \
|
||||
static_assert(!((flags_) & CSS_PROPERTY_LOGICAL_END_EDGE), \
|
||||
"only properties defined with CSS_PROP_LOGICAL can use " \
|
||||
"the CSS_PROPERTY_LOGICAL_END_EDGE flag");
|
||||
"the CSS_PROPERTY_LOGICAL_END_EDGE flag"); \
|
||||
static_assert(!((flags_) & CSS_PROPERTY_LOGICAL_CORNER), \
|
||||
"only properties defined with CSS_PROP_LOGICAL can use " \
|
||||
"the CSS_PROPERTY_LOGICAL_CORNER flag");
|
||||
#define CSS_PROP_LOGICAL(name_, id_, method_, flags_, pref_, parsevariant_, \
|
||||
kwtable_, group_, stylestruct_, \
|
||||
stylestructoffset_, animtype_) \
|
||||
@@ -3505,6 +3508,10 @@ nsCSSProps::gPropertyEnabled[eCSSProperty_COUNT_with_aliases] = {
|
||||
static_assert(!(((flags_) & CSS_PROPERTY_LOGICAL_AXIS) && \
|
||||
((flags_) & CSS_PROPERTY_LOGICAL_END_EDGE)), \
|
||||
"CSS_PROPERTY_LOGICAL_END_EDGE makes no sense when used " \
|
||||
"with CSS_PROPERTY_LOGICAL_AXIS"); \
|
||||
static_assert(!(((flags_) & CSS_PROPERTY_LOGICAL_AXIS) && \
|
||||
((flags_) & CSS_PROPERTY_LOGICAL_CORNER)), \
|
||||
"CSS_PROPERTY_LOGICAL_CORNER makes no sense when used " \
|
||||
"with CSS_PROPERTY_LOGICAL_AXIS");
|
||||
#include "nsCSSPropList.h"
|
||||
#undef CSS_PROP_LOGICAL
|
||||
|
||||
@@ -155,7 +155,8 @@
|
||||
// example, the block-size logical property has this flag set, as it
|
||||
// represents the size in either the block or inline axis dimensions, and
|
||||
// has two corresponding physical properties, width and height. Must not
|
||||
// be used in conjunction with CSS_PROPERTY_LOGICAL_END_EDGE.
|
||||
// be used in conjunction with CSS_PROPERTY_LOGICAL_END_EDGE or
|
||||
// CSS_PROPERTY_LOGICAL_CORNER.
|
||||
#define CSS_PROPERTY_LOGICAL_AXIS (1<<7)
|
||||
|
||||
// This property allows calc() between lengths and percentages and
|
||||
@@ -247,7 +248,9 @@ static_assert((CSS_PROPERTY_PARSE_PROPERTY_MASK &
|
||||
// sides (such as margin-block-start or margin-block-end). Must only be
|
||||
// set if CSS_PROPERTY_LOGICAL is set. When not set, the logical
|
||||
// property is for one of the two inline axis sides (such as
|
||||
// margin-inline-start or margin-inline-end).
|
||||
// margin-inline-start or margin-inline-end). For logical corner
|
||||
// properties, this flag means the block-end edge; when not set, the
|
||||
// property is for the block-start edge.
|
||||
#define CSS_PROPERTY_LOGICAL_BLOCK_AXIS (1<<25)
|
||||
|
||||
// This property is a logical property for the "end" edge of the
|
||||
@@ -255,9 +258,18 @@ static_assert((CSS_PROPERTY_PARSE_PROPERTY_MASK &
|
||||
// CSS_PROPERTY_LOGICAL_BLOCK_AXIS (such as margin-block-end or
|
||||
// margin-inline-end). Must only be set if CSS_PROPERTY_LOGICAL is set.
|
||||
// When not set, the logical property is for the "start" edge (such as
|
||||
// margin-block-start or margin-inline-start).
|
||||
// margin-block-start or margin-inline-start). For logical corner
|
||||
// properties, this flag means the inline-end edge; when not set, the
|
||||
// property is for the inline-start edge.
|
||||
#define CSS_PROPERTY_LOGICAL_END_EDGE (1<<26)
|
||||
|
||||
// This property is a logical corner property, such as
|
||||
// border-start-start-radius. CSS_PROPERTY_LOGICAL_BLOCK_AXIS and
|
||||
// CSS_PROPERTY_LOGICAL_END_EDGE select the block and inline edges that
|
||||
// form the corner. Must only be set if CSS_PROPERTY_LOGICAL is set, and
|
||||
// must not be used in conjunction with CSS_PROPERTY_LOGICAL_AXIS.
|
||||
#define CSS_PROPERTY_LOGICAL_CORNER (1u<<31)
|
||||
|
||||
// This property can be animated on the compositor.
|
||||
#define CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR (1<<27)
|
||||
|
||||
@@ -584,7 +596,9 @@ public:
|
||||
*
|
||||
* When called with a property that has the CSS_PROPERTY_LOGICAL_AXIS
|
||||
* flag, the returned array will have two values preceding the sentinel;
|
||||
* otherwise it will have four.
|
||||
* otherwise it will have four. For logical corner properties, the four
|
||||
* properties are returned in top-left, top-right, bottom-right,
|
||||
* bottom-left order.
|
||||
*
|
||||
* (Note that the running time of this function is proportional to the
|
||||
* number of logical longhand properties that exist. If we start
|
||||
|
||||
@@ -94,6 +94,9 @@ CSS_PSEUDO_CLASS(nthLastOfType, ":nth-last-of-type", 0, "")
|
||||
// Match slot nodes.
|
||||
CSS_PSEUDO_CLASS(slotted, ":slotted", 0, "layout.css.slotted-pseudo.enabled")
|
||||
|
||||
// Match elements exposed through a shadow host's part attribute.
|
||||
CSS_PSEUDO_CLASS(part, ":part", 0, "dom.webcomponents.enabled")
|
||||
|
||||
// Match nodes that are HTML but not XHTML
|
||||
CSS_PSEUDO_CLASS(mozIsHTML, ":-moz-is-html", 0, "")
|
||||
|
||||
|
||||
@@ -125,7 +125,8 @@ nsCSSPseudoClasses::HasStringArg(Type aType)
|
||||
aType == Type::mozSystemMetric ||
|
||||
aType == Type::mozLocaleDir ||
|
||||
aType == Type::mozDir ||
|
||||
aType == Type::dir;
|
||||
aType == Type::dir ||
|
||||
aType == Type::part;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -210,5 +211,6 @@ nsCSSPseudoClasses::IsUserActionPseudoClass(Type aType)
|
||||
/* static */ bool
|
||||
nsCSSPseudoClasses::IsHybridPseudoElement(Type aType)
|
||||
{
|
||||
return aType == Type::slotted;
|
||||
return aType == Type::slotted ||
|
||||
aType == Type::part;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@
|
||||
CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2)
|
||||
CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2)
|
||||
|
||||
// XXX: ::slotted() is treated as if it were a pseudo-class, and
|
||||
// is never parsed as a pseudo-element.
|
||||
// XXX: ::slotted() and ::part() are treated as if they were pseudo-classes,
|
||||
// and are never parsed as pseudo-elements.
|
||||
CSS_PSEUDO_ELEMENT(slotted, ":slotted",
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING)
|
||||
CSS_PSEUDO_ELEMENT(part, ":part",
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_TREE_ABIDING |
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE)
|
||||
|
||||
CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ nsCSSPseudoElements::IsCSS2PseudoElement(nsIAtom *aAtom)
|
||||
/* static */ bool
|
||||
nsCSSPseudoElements::IsHybridPseudoElement(CSSPseudoElementType aType)
|
||||
{
|
||||
return aType == CSSPseudoElementType::slotted;
|
||||
return aType == CSSPseudoElementType::slotted ||
|
||||
aType == CSSPseudoElementType::part;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "mozilla/dom/HTMLSlotElement.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "nsIMozBrowserFrame.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsRuleWalker.h"
|
||||
#include "nsStyleUtil.h"
|
||||
#include "StyleRule.h"
|
||||
@@ -188,6 +189,36 @@ InitSystemMetrics()
|
||||
return true;
|
||||
}
|
||||
|
||||
static nsPseudoClassList*
|
||||
FindPseudoClass(const nsCSSSelector* aSelector, CSSPseudoClassType aType)
|
||||
{
|
||||
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
|
||||
pseudoClass;
|
||||
pseudoClass = pseudoClass->mNext) {
|
||||
if (pseudoClass->mType == aType) {
|
||||
return pseudoClass;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool
|
||||
ElementMatchesPart(Element* aElement, const nsPseudoClassList* aPseudoClass)
|
||||
{
|
||||
MOZ_ASSERT(aPseudoClass->mType == CSSPseudoClassType::part);
|
||||
MOZ_ASSERT(aPseudoClass->u.mString);
|
||||
|
||||
nsAutoString partValue;
|
||||
if (!aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::part, partValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nsDefaultStringComparator comparator;
|
||||
return nsStyleUtil::ValueIncludes(
|
||||
partValue, nsDependentString(aPseudoClass->u.mString), comparator);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
nsCSSRuleUtils::FreeSystemMetrics()
|
||||
{
|
||||
@@ -640,7 +671,10 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
|
||||
"is false since we don't know how to set it correctly in "
|
||||
"Has(Attribute|State)DependentStyle");
|
||||
|
||||
if (aNodeMatchContext.mIsFeatureless &&
|
||||
nsPseudoClassList* partPseudo =
|
||||
FindPseudoClass(aSelector, CSSPseudoClassType::part);
|
||||
|
||||
if (aNodeMatchContext.mIsFeatureless && !partPseudo &&
|
||||
!CanMatchFeaturelessElement(aSelector)) {
|
||||
return false;
|
||||
}
|
||||
@@ -656,6 +690,27 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
|
||||
targetElement = slot->AsElement();
|
||||
}
|
||||
|
||||
if (partPseudo) {
|
||||
if (!ElementMatchesPart(aElement, partPseudo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsIContent* partHostContent = aElement->GetContainingShadowHost();
|
||||
if (!partHostContent || !partHostContent->IsElement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Element* partHost = partHostContent->AsElement();
|
||||
if (aTreeMatchContext.mScopedRoot) {
|
||||
ShadowRoot* scopedShadow = aTreeMatchContext.mScopedRoot->GetShadowRoot();
|
||||
if (!scopedShadow || partHost->GetContainingShadow() != scopedShadow) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
targetElement = partHost;
|
||||
}
|
||||
|
||||
// namespace/tag match
|
||||
// optimization : bail out early if we can
|
||||
if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
|
||||
@@ -915,6 +970,9 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
|
||||
}
|
||||
} break;
|
||||
|
||||
case CSSPseudoClassType::part:
|
||||
break;
|
||||
|
||||
case CSSPseudoClassType::host: {
|
||||
ShadowRoot* shadow = aElement->GetShadowRoot();
|
||||
// In order to match :host, the element must be a shadow root host,
|
||||
@@ -927,15 +985,11 @@ nsCSSRuleUtils::SelectorMatches(Element* aElement,
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're matching :host from inside the shadow root.
|
||||
if (!aTreeMatchContext.mOnlyMatchHostPseudo) {
|
||||
// Check if the element has the same shadow root.
|
||||
if (aTreeMatchContext.mScopedRoot) {
|
||||
if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) {
|
||||
return false;
|
||||
}
|
||||
// Check if the element has the same shadow root.
|
||||
if (aTreeMatchContext.mScopedRoot) {
|
||||
if (shadow != aTreeMatchContext.mScopedRoot->GetShadowRoot()) {
|
||||
return false;
|
||||
}
|
||||
// We were called elsewhere.
|
||||
}
|
||||
|
||||
// Reject if the next selector is an explicit universal selector.
|
||||
|
||||
+286
-16
@@ -6,6 +6,7 @@
|
||||
/* representation of simple property values within CSS declarations */
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
|
||||
#include "nsCSSValue.h"
|
||||
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "gfxFontConstants.h"
|
||||
#include "imgIRequest.h"
|
||||
#include "imgRequestProxy.h"
|
||||
#include "nsCSSNonSRGBColorSpace.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPresContext.h"
|
||||
@@ -29,6 +31,25 @@
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::css;
|
||||
|
||||
static void
|
||||
AppendSerializedCSSFloat(float aValue, nsAString& aResult)
|
||||
{
|
||||
if (mozilla::IsNaN(aValue)) {
|
||||
aResult.AppendLiteral("NaN");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mozilla::IsInfinite(aValue)) {
|
||||
if (aValue < 0.0f) {
|
||||
aResult.Append('-');
|
||||
}
|
||||
aResult.AppendLiteral("infinity");
|
||||
return;
|
||||
}
|
||||
|
||||
aResult.AppendFloat(aValue);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsLocalRefURL(nsStringBuffer* aString)
|
||||
{
|
||||
@@ -67,7 +88,6 @@ nsCSSValue::nsCSSValue(float aValue, nsCSSUnit aUnit)
|
||||
MOZ_ASSERT(eCSSUnit_Percent <= aUnit, "not a float value");
|
||||
if (eCSSUnit_Percent <= aUnit) {
|
||||
mValue.mFloat = aValue;
|
||||
MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
|
||||
}
|
||||
else {
|
||||
mUnit = eCSSUnit_Null;
|
||||
@@ -146,7 +166,6 @@ nsCSSValue::nsCSSValue(const nsCSSValue& aCopy)
|
||||
}
|
||||
else if (eCSSUnit_Percent <= mUnit) {
|
||||
mValue.mFloat = aCopy.mValue.mFloat;
|
||||
MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
|
||||
}
|
||||
else if (UnitHasStringValue()) {
|
||||
mValue.mString = aCopy.mValue.mString;
|
||||
@@ -483,7 +502,6 @@ void nsCSSValue::SetPercentValue(float aValue)
|
||||
Reset();
|
||||
mUnit = eCSSUnit_Percent;
|
||||
mValue.mFloat = aValue;
|
||||
MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
|
||||
}
|
||||
|
||||
void nsCSSValue::SetFloatValue(float aValue, nsCSSUnit aUnit)
|
||||
@@ -493,7 +511,6 @@ void nsCSSValue::SetFloatValue(float aValue, nsCSSUnit aUnit)
|
||||
if (IsFloatUnit(aUnit)) {
|
||||
mUnit = aUnit;
|
||||
mValue.mFloat = aValue;
|
||||
MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1035,209 @@ private:
|
||||
nsCSSValue::Serialization mValueSerialization;
|
||||
};
|
||||
|
||||
struct CSSValueReduceNumberCalcOps : public BasicFloatCalcOps,
|
||||
public CSSValueInputCalcOps
|
||||
{
|
||||
result_type ComputeLeafValue(const nsCSSValue& aValue)
|
||||
{
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
|
||||
return aValue.GetFloatValue();
|
||||
}
|
||||
|
||||
float ComputeNumber(const nsCSSValue& aValue)
|
||||
{
|
||||
return css::ComputeCalc(aValue, *this);
|
||||
}
|
||||
};
|
||||
|
||||
struct CSSValueCalcLengthTerm {
|
||||
nsCSSUnit mUnit;
|
||||
float mValue;
|
||||
};
|
||||
|
||||
struct CSSValueLengthPercentCalcAccumulator {
|
||||
AutoTArray<CSSValueCalcLengthTerm, 8> mLengths;
|
||||
float mPercent = 0.0f;
|
||||
bool mSawPercent = false;
|
||||
|
||||
void AddLength(nsCSSUnit aUnit, float aValue)
|
||||
{
|
||||
for (CSSValueCalcLengthTerm& term : mLengths) {
|
||||
if (term.mUnit == aUnit) {
|
||||
term.mValue += aValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CSSValueCalcLengthTerm& term = *mLengths.AppendElement();
|
||||
term.mUnit = aUnit;
|
||||
term.mValue = aValue;
|
||||
}
|
||||
};
|
||||
|
||||
static bool
|
||||
IsNearlyZero(float aValue)
|
||||
{
|
||||
return aValue > -0.000001f && aValue < 0.000001f;
|
||||
}
|
||||
|
||||
static bool
|
||||
ComputeCalcNumberValue(const nsCSSValue& aValue, float& aResult)
|
||||
{
|
||||
if (aValue.GetUnit() == eCSSUnit_Number) {
|
||||
aResult = aValue.GetFloatValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!aValue.IsCalcUnit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CSSValueReduceNumberCalcOps ops;
|
||||
aResult = css::ComputeCalc(aValue, ops);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
AccumulateCalcLengthPercentTerms(
|
||||
const nsCSSValue& aValue,
|
||||
float aScale,
|
||||
CSSValueLengthPercentCalcAccumulator& aAccumulator)
|
||||
{
|
||||
switch (aValue.GetUnit()) {
|
||||
case eCSSUnit_Calc: {
|
||||
const nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
return AccumulateCalcLengthPercentTerms(array->Item(0), aScale,
|
||||
aAccumulator);
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Plus:
|
||||
case eCSSUnit_Calc_Minus: {
|
||||
const nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
if (!AccumulateCalcLengthPercentTerms(array->Item(0), aScale,
|
||||
aAccumulator)) {
|
||||
return false;
|
||||
}
|
||||
float rhsScale = aValue.GetUnit() == eCSSUnit_Calc_Plus
|
||||
? aScale
|
||||
: -aScale;
|
||||
return AccumulateCalcLengthPercentTerms(array->Item(1), rhsScale,
|
||||
aAccumulator);
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Times_L:
|
||||
case eCSSUnit_Calc_Times_R:
|
||||
case eCSSUnit_Calc_Divided: {
|
||||
const nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
|
||||
const nsCSSValue* value = &array->Item(0);
|
||||
const nsCSSValue* factorValue = &array->Item(1);
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Times_L) {
|
||||
value = &array->Item(1);
|
||||
factorValue = &array->Item(0);
|
||||
}
|
||||
|
||||
float factor;
|
||||
if (!ComputeCalcNumberValue(*factorValue, factor)) {
|
||||
return false;
|
||||
}
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Divided) {
|
||||
factor = 1.0f / factor;
|
||||
}
|
||||
return AccumulateCalcLengthPercentTerms(*value, aScale * factor,
|
||||
aAccumulator);
|
||||
}
|
||||
|
||||
case eCSSUnit_Percent:
|
||||
aAccumulator.mSawPercent = true;
|
||||
aAccumulator.mPercent += aScale * aValue.GetPercentValue();
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (aValue.IsLengthUnit()) {
|
||||
aAccumulator.AddLength(aValue.GetUnit(), aScale * aValue.GetFloatValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
AppendSerializedCalcLeafValue(nsCSSPropertyID aProperty,
|
||||
nsAString& aResult,
|
||||
nsCSSValue::Serialization aSerialization,
|
||||
nsCSSUnit aUnit,
|
||||
float aValue)
|
||||
{
|
||||
nsCSSValue value;
|
||||
if (aUnit == eCSSUnit_Percent) {
|
||||
value.SetPercentValue(aValue);
|
||||
} else {
|
||||
value.SetFloatValue(aValue, aUnit);
|
||||
}
|
||||
value.AppendToString(aProperty, aResult, aSerialization);
|
||||
}
|
||||
|
||||
static bool
|
||||
AppendNormalizedLengthPercentCalcToString(
|
||||
const nsCSSValue& aValue,
|
||||
nsCSSPropertyID aProperty,
|
||||
nsAString& aResult,
|
||||
nsCSSValue::Serialization aSerialization)
|
||||
{
|
||||
CSSValueLengthPercentCalcAccumulator accumulator;
|
||||
if (!AccumulateCalcLengthPercentTerms(aValue, 1.0f, accumulator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoTArray<CSSValueCalcLengthTerm, 9> serializedTerms;
|
||||
if (!IsNearlyZero(accumulator.mPercent) || accumulator.mSawPercent) {
|
||||
CSSValueCalcLengthTerm& term = *serializedTerms.AppendElement();
|
||||
term.mUnit = eCSSUnit_Percent;
|
||||
term.mValue = accumulator.mPercent;
|
||||
}
|
||||
|
||||
for (const CSSValueCalcLengthTerm& term : accumulator.mLengths) {
|
||||
if (IsNearlyZero(term.mValue)) {
|
||||
continue;
|
||||
}
|
||||
serializedTerms.AppendElement(term);
|
||||
}
|
||||
|
||||
if (serializedTerms.IsEmpty()) {
|
||||
CSSValueCalcLengthTerm& term = *serializedTerms.AppendElement();
|
||||
term.mUnit = accumulator.mSawPercent ? eCSSUnit_Percent : eCSSUnit_Pixel;
|
||||
term.mValue = 0.0f;
|
||||
}
|
||||
|
||||
aResult.AppendLiteral("calc(");
|
||||
AppendSerializedCalcLeafValue(aProperty, aResult, aSerialization,
|
||||
serializedTerms[0].mUnit,
|
||||
serializedTerms[0].mValue);
|
||||
|
||||
for (uint32_t i = 1; i < serializedTerms.Length(); ++i) {
|
||||
const CSSValueCalcLengthTerm& term = serializedTerms[i];
|
||||
if (term.mValue < 0.0f) {
|
||||
aResult.AppendLiteral(" - ");
|
||||
AppendSerializedCalcLeafValue(aProperty, aResult, aSerialization,
|
||||
term.mUnit, -term.mValue);
|
||||
} else {
|
||||
aResult.AppendLiteral(" + ");
|
||||
AppendSerializedCalcLeafValue(aProperty, aResult, aSerialization,
|
||||
term.mUnit, term.mValue);
|
||||
}
|
||||
}
|
||||
|
||||
aResult.Append(')');
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
@@ -1473,9 +1693,11 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
aResult.Append(')');
|
||||
}
|
||||
else if (IsCalcUnit()) {
|
||||
MOZ_ASSERT(GetUnit() == eCSSUnit_Calc, "unexpected unit");
|
||||
CSSValueSerializeCalcOps ops(aProperty, aResult, aSerialization);
|
||||
css::SerializeCalc(*this, ops);
|
||||
if (!AppendNormalizedLengthPercentCalcToString(*this, aProperty, aResult,
|
||||
aSerialization)) {
|
||||
CSSValueSerializeCalcOps ops(aProperty, aResult, aSerialization);
|
||||
css::SerializeCalc(*this, ops);
|
||||
}
|
||||
}
|
||||
else if (eCSSUnit_Integer == unit) {
|
||||
aResult.AppendInt(GetIntValue(), 10);
|
||||
@@ -1743,6 +1965,12 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
case mozilla::css::ColorMixColorSpace::HSL:
|
||||
aResult.AppendLiteral("hsl");
|
||||
break;
|
||||
case mozilla::css::ColorMixColorSpace::Oklab:
|
||||
aResult.AppendLiteral("oklab");
|
||||
break;
|
||||
case mozilla::css::ColorMixColorSpace::Oklch:
|
||||
aResult.AppendLiteral("oklch");
|
||||
break;
|
||||
}
|
||||
|
||||
// append color1 and color2
|
||||
@@ -1767,10 +1995,10 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
aResult.Append(')');
|
||||
}
|
||||
else if (eCSSUnit_Percent == unit) {
|
||||
aResult.AppendFloat(GetPercentValue() * 100.0f);
|
||||
AppendSerializedCSSFloat(GetPercentValue() * 100.0f, aResult);
|
||||
}
|
||||
else if (eCSSUnit_Percent < unit) { // length unit
|
||||
aResult.AppendFloat(GetFloatValue());
|
||||
AppendSerializedCSSFloat(GetFloatValue(), aResult);
|
||||
}
|
||||
else if (eCSSUnit_Gradient == unit) {
|
||||
nsCSSValueGradient* gradient = GetGradientValue();
|
||||
@@ -2037,6 +2265,9 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
case eCSSUnit_Calc_Times_L: break;
|
||||
case eCSSUnit_Calc_Times_R: break;
|
||||
case eCSSUnit_Calc_Divided: break;
|
||||
case eCSSUnit_Calc_Min: break;
|
||||
case eCSSUnit_Calc_Max: break;
|
||||
case eCSSUnit_Calc_Clamp: break;
|
||||
case eCSSUnit_Integer: break;
|
||||
case eCSSUnit_Enumerated: break;
|
||||
case eCSSUnit_EnumColor: break;
|
||||
@@ -2050,6 +2281,8 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
case eCSSUnit_PercentageRGBAColor: break;
|
||||
case eCSSUnit_HSLColor: break;
|
||||
case eCSSUnit_HSLAColor: break;
|
||||
case eCSSUnit_OklabColor: break;
|
||||
case eCSSUnit_OklchColor: break;
|
||||
case eCSSUnit_ComplexColor: break;
|
||||
case eCSSUnit_ColorMix: break;
|
||||
case eCSSUnit_Percent: aResult.Append(char16_t('%')); break;
|
||||
@@ -2167,6 +2400,9 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
case eCSSUnit_Calc_Times_L:
|
||||
case eCSSUnit_Calc_Times_R:
|
||||
case eCSSUnit_Calc_Divided:
|
||||
case eCSSUnit_Calc_Min:
|
||||
case eCSSUnit_Calc_Max:
|
||||
case eCSSUnit_Calc_Clamp:
|
||||
break;
|
||||
|
||||
// URL
|
||||
@@ -2258,6 +2494,8 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
case eCSSUnit_PercentageRGBAColor:
|
||||
case eCSSUnit_HSLColor:
|
||||
case eCSSUnit_HSLAColor:
|
||||
case eCSSUnit_OklabColor:
|
||||
case eCSSUnit_OklchColor:
|
||||
n += mValue.mFloatColor->SizeOfIncludingThis(aMallocSizeOf);
|
||||
break;
|
||||
|
||||
@@ -3254,6 +3492,16 @@ nsCSSValueFloatColor::GetColorValue(nsCSSUnit aUnit) const
|
||||
NSToIntRound(mozilla::clamped(mAlpha, 0.0f, 1.0f) * 255.0f));
|
||||
}
|
||||
|
||||
if (aUnit == eCSSUnit_OklabColor) {
|
||||
return css::OklabToSRGBColor(mComponent1, mComponent2, mComponent3,
|
||||
mAlpha);
|
||||
}
|
||||
|
||||
if (aUnit == eCSSUnit_OklchColor) {
|
||||
return css::OklchToSRGBColor(mComponent1, mComponent2, mComponent3,
|
||||
mAlpha);
|
||||
}
|
||||
|
||||
// HSL color
|
||||
MOZ_ASSERT(aUnit == eCSSUnit_HSLColor ||
|
||||
aUnit == eCSSUnit_HSLAColor);
|
||||
@@ -3282,8 +3530,14 @@ nsCSSValueFloatColor::AppendToString(nsCSSUnit aUnit, nsAString& aResult) const
|
||||
bool showAlpha = (mAlpha != 1.0f);
|
||||
bool isHSL = (aUnit == eCSSUnit_HSLColor ||
|
||||
aUnit == eCSSUnit_HSLAColor);
|
||||
bool isOklab = aUnit == eCSSUnit_OklabColor;
|
||||
bool isOklch = aUnit == eCSSUnit_OklchColor;
|
||||
|
||||
if (isHSL) {
|
||||
if (isOklab) {
|
||||
aResult.AppendLiteral("oklab");
|
||||
} else if (isOklch) {
|
||||
aResult.AppendLiteral("oklch");
|
||||
} else if (isHSL) {
|
||||
aResult.AppendLiteral("hsl");
|
||||
} else {
|
||||
aResult.AppendLiteral("rgb");
|
||||
@@ -3293,22 +3547,38 @@ nsCSSValueFloatColor::AppendToString(nsCSSUnit aUnit, nsAString& aResult) const
|
||||
} else {
|
||||
aResult.Append('(');
|
||||
}
|
||||
if (isHSL) {
|
||||
if (isOklab || isOklch) {
|
||||
aResult.AppendFloat(mComponent1);
|
||||
aResult.Append(' ');
|
||||
aResult.AppendFloat(mComponent2);
|
||||
aResult.Append(' ');
|
||||
aResult.AppendFloat(mComponent3);
|
||||
} else if (isHSL) {
|
||||
aResult.AppendFloat(mComponent1 * 360.0f);
|
||||
aResult.AppendLiteral(", ");
|
||||
} else {
|
||||
aResult.AppendFloat(mComponent1 * 100.0f);
|
||||
aResult.AppendLiteral("%, ");
|
||||
}
|
||||
aResult.AppendFloat(mComponent2 * 100.0f);
|
||||
aResult.AppendLiteral("%, ");
|
||||
aResult.AppendFloat(mComponent3 * 100.0f);
|
||||
if (showAlpha) {
|
||||
if (!isOklab && !isOklch) {
|
||||
aResult.AppendFloat(mComponent2 * 100.0f);
|
||||
aResult.AppendLiteral("%, ");
|
||||
aResult.AppendFloat(mComponent3 * 100.0f);
|
||||
}
|
||||
if (showAlpha) {
|
||||
if (isOklab || isOklch) {
|
||||
aResult.AppendLiteral(" / ");
|
||||
} else {
|
||||
aResult.AppendLiteral("%, ");
|
||||
}
|
||||
aResult.AppendFloat(mAlpha);
|
||||
aResult.Append(')');
|
||||
} else {
|
||||
aResult.AppendLiteral("%)");
|
||||
if (isOklab || isOklch) {
|
||||
aResult.Append(')');
|
||||
} else {
|
||||
aResult.AppendLiteral("%)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -477,6 +477,11 @@ enum nsCSSUnit {
|
||||
eCSSUnit_Calc_Times_L = 33, // (nsCSSValue::Array*) num * val within calc
|
||||
eCSSUnit_Calc_Times_R = 34, // (nsCSSValue::Array*) val * num within calc
|
||||
eCSSUnit_Calc_Divided = 35, // (nsCSSValue::Array*) / within calc
|
||||
// Min and Max have arrays with one or more elements. Clamp has an array
|
||||
// with exactly 3 elements: lower bound, center, upper bound.
|
||||
eCSSUnit_Calc_Min = 36, // (nsCSSValue::Array*) min() node
|
||||
eCSSUnit_Calc_Max = 37, // (nsCSSValue::Array*) max() node
|
||||
eCSSUnit_Calc_Clamp = 38, // (nsCSSValue::Array*) clamp() node
|
||||
|
||||
eCSSUnit_URL = 40, // (nsCSSValue::URL*) value
|
||||
eCSSUnit_Image = 41, // (nsCSSValue::Image*) value
|
||||
@@ -520,8 +525,10 @@ enum nsCSSUnit {
|
||||
// allowed.
|
||||
eCSSUnit_HSLColor = 89, // (nsCSSValueFloatColor*)
|
||||
eCSSUnit_HSLAColor = 90, // (nsCSSValueFloatColor*)
|
||||
eCSSUnit_ComplexColor = 91, // (ComplexColorValue*)
|
||||
eCSSUnit_ColorMix = 92, // (ColorMixValue*)
|
||||
eCSSUnit_OklabColor = 91, // (nsCSSValueFloatColor*)
|
||||
eCSSUnit_OklchColor = 92, // (nsCSSValueFloatColor*)
|
||||
eCSSUnit_ComplexColor = 93, // (ComplexColorValue*)
|
||||
eCSSUnit_ColorMix = 94, // (ColorMixValue*)
|
||||
|
||||
eCSSUnit_Percent = 100, // (float) 1.0 == 100%) value is percentage of something
|
||||
eCSSUnit_Number = 101, // (float) value is numeric (usually multiplier, different behavior than percent)
|
||||
@@ -711,12 +718,12 @@ public:
|
||||
bool IsTimeUnit() const
|
||||
{ return eCSSUnit_Seconds <= mUnit && mUnit <= eCSSUnit_Milliseconds; }
|
||||
bool IsCalcUnit() const
|
||||
{ return eCSSUnit_Calc <= mUnit && mUnit <= eCSSUnit_Calc_Divided; }
|
||||
{ return eCSSUnit_Calc <= mUnit && mUnit <= eCSSUnit_Calc_Clamp; }
|
||||
|
||||
bool UnitHasStringValue() const
|
||||
{ return eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Element; }
|
||||
bool UnitHasArrayValue() const
|
||||
{ return eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Calc_Divided; }
|
||||
{ return eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Calc_Clamp; }
|
||||
|
||||
// Checks for the nsCSSValue being of a particular type of color unit:
|
||||
//
|
||||
@@ -733,6 +740,8 @@ public:
|
||||
// eCSSUnit_PercentageRGBAColor -- rgba(%,%,%,float)
|
||||
// eCSSUnit_HSLColor -- hsl(float,%,%)
|
||||
// eCSSUnit_HSLAColor -- hsla(float,%,%,float)
|
||||
// eCSSUnit_OklabColor -- oklab(float,float,float,float)
|
||||
// eCSSUnit_OklchColor -- oklch(float,float,float,float)
|
||||
//
|
||||
// - IsNumericColorUnit returns true for any of the above units.
|
||||
//
|
||||
@@ -745,7 +754,7 @@ public:
|
||||
{ return eCSSUnit_RGBColor <= aUnit && aUnit <= eCSSUnit_ShortHexColorAlpha; }
|
||||
static bool IsFloatColorUnit(nsCSSUnit aUnit)
|
||||
{ return eCSSUnit_PercentageRGBColor <= aUnit &&
|
||||
aUnit <= eCSSUnit_HSLAColor; }
|
||||
aUnit <= eCSSUnit_OklchColor; }
|
||||
static bool IsNumericColorUnit(nsCSSUnit aUnit)
|
||||
{ return IsIntegerColorUnit(aUnit) || IsFloatColorUnit(aUnit); }
|
||||
|
||||
@@ -773,7 +782,6 @@ public:
|
||||
float GetFloatValue() const
|
||||
{
|
||||
MOZ_ASSERT(eCSSUnit_Number <= mUnit, "not a float value");
|
||||
MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat));
|
||||
return mValue.mFloat;
|
||||
}
|
||||
|
||||
@@ -1928,6 +1936,9 @@ private:
|
||||
// [0, 1] for hue represents
|
||||
// [0deg, 360deg].
|
||||
//
|
||||
// OklabColor stores L in mComponent1, a in mComponent2, b in mComponent3.
|
||||
// OklchColor stores L in mComponent1, C in mComponent2, H in mComponent3.
|
||||
//
|
||||
// [-float::max(), float::max()] for PercentageRGBColor, PercentageRGBAColor.
|
||||
// 1.0 means 100%.
|
||||
float mComponent1;
|
||||
@@ -1994,7 +2005,9 @@ namespace css {
|
||||
|
||||
enum class ColorMixColorSpace {
|
||||
sRGB,
|
||||
HSL
|
||||
HSL,
|
||||
Oklab,
|
||||
Oklch
|
||||
};
|
||||
|
||||
struct ColorMixValue final
|
||||
@@ -2026,4 +2039,3 @@ private:
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* nsCSSValue_h___ */
|
||||
|
||||
|
||||
@@ -964,6 +964,22 @@ nsComputedDOMStyle::DoGetBinding()
|
||||
return val.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetAspectRatio()
|
||||
{
|
||||
RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
|
||||
float ratio = StylePosition()->mAspectRatio;
|
||||
if (ratio == 0.0f) {
|
||||
val->SetIdent(eCSSKeyword_auto);
|
||||
} else {
|
||||
nsAutoString ratioString;
|
||||
nsStyleUtil::AppendCSSNumber(ratio, ratioString);
|
||||
ratioString.AppendLiteral(" / 1");
|
||||
val->SetString(ratioString);
|
||||
}
|
||||
return val.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetClear()
|
||||
{
|
||||
@@ -1883,6 +1899,94 @@ nsComputedDOMStyle::DoGetBackgroundColor()
|
||||
return val.forget();
|
||||
}
|
||||
|
||||
static void
|
||||
AppendCalcNodeToString(const nsStyleCoord::CalcNode* aNode,
|
||||
nsROCSSPrimitiveValue* aPrimitive,
|
||||
nsAString& aResult);
|
||||
|
||||
static void
|
||||
AppendCalcLeafToString(const nsStyleCoord::CalcNode* aNode,
|
||||
nsROCSSPrimitiveValue* aPrimitive,
|
||||
nsAString& aResult)
|
||||
{
|
||||
nsAutoString tmp;
|
||||
|
||||
if (!aNode->mHasPercent) {
|
||||
aPrimitive->SetAppUnits(aNode->mLength);
|
||||
aPrimitive->GetCssText(tmp);
|
||||
aResult.Append(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
aResult.AppendLiteral("calc(");
|
||||
aPrimitive->SetAppUnits(aNode->mLength);
|
||||
aPrimitive->GetCssText(tmp);
|
||||
aResult.Append(tmp);
|
||||
aResult.AppendLiteral(" + ");
|
||||
aPrimitive->SetPercent(aNode->mPercent);
|
||||
aPrimitive->GetCssText(tmp);
|
||||
aResult.Append(tmp);
|
||||
aResult.Append(')');
|
||||
}
|
||||
|
||||
static void
|
||||
AppendCalcNodeToString(const nsStyleCoord::CalcNode* aNode,
|
||||
nsROCSSPrimitiveValue* aPrimitive,
|
||||
nsAString& aResult)
|
||||
{
|
||||
using Type = nsStyleCoord::CalcNode::Type;
|
||||
|
||||
switch (aNode->mType) {
|
||||
case Type::Leaf:
|
||||
AppendCalcLeafToString(aNode, aPrimitive, aResult);
|
||||
return;
|
||||
case Type::Add:
|
||||
case Type::Subtract:
|
||||
aResult.AppendLiteral("calc(");
|
||||
AppendCalcNodeToString(aNode->mChildren[0], aPrimitive, aResult);
|
||||
aResult.Append(aNode->mType == Type::Add
|
||||
? NS_LITERAL_STRING(" + ")
|
||||
: NS_LITERAL_STRING(" - "));
|
||||
AppendCalcNodeToString(aNode->mChildren[1], aPrimitive, aResult);
|
||||
aResult.Append(')');
|
||||
return;
|
||||
case Type::Multiply:
|
||||
case Type::Divide: {
|
||||
nsAutoString tmp;
|
||||
aResult.AppendLiteral("calc(");
|
||||
AppendCalcNodeToString(aNode->mChildren[0], aPrimitive, aResult);
|
||||
aResult.Append(aNode->mType == Type::Multiply
|
||||
? NS_LITERAL_STRING(" * ")
|
||||
: NS_LITERAL_STRING(" / "));
|
||||
aPrimitive->SetNumber(aNode->mNumber);
|
||||
aPrimitive->GetCssText(tmp);
|
||||
aResult.Append(tmp);
|
||||
aResult.Append(')');
|
||||
return;
|
||||
}
|
||||
case Type::Min:
|
||||
case Type::Max:
|
||||
case Type::Clamp:
|
||||
if (aNode->mType == Type::Min) {
|
||||
aResult.AppendLiteral("min(");
|
||||
} else if (aNode->mType == Type::Max) {
|
||||
aResult.AppendLiteral("max(");
|
||||
} else {
|
||||
aResult.AppendLiteral("clamp(");
|
||||
}
|
||||
for (uint32_t i = 0; i < aNode->mChildren.Length(); ++i) {
|
||||
if (i != 0) {
|
||||
aResult.AppendLiteral(", ");
|
||||
}
|
||||
AppendCalcNodeToString(aNode->mChildren[i], aPrimitive, aResult);
|
||||
}
|
||||
aResult.Append(')');
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("unexpected calc node type");
|
||||
}
|
||||
|
||||
static void
|
||||
SetValueToCalc(const nsStyleCoord::CalcValue* aCalc,
|
||||
nsROCSSPrimitiveValue* aValue)
|
||||
@@ -1909,6 +2013,21 @@ SetValueToCalc(const nsStyleCoord::CalcValue* aCalc,
|
||||
aValue->SetString(result); // not really SetString
|
||||
}
|
||||
|
||||
static void
|
||||
SetValueToCalc(const nsStyleCoord::Calc* aCalc,
|
||||
nsROCSSPrimitiveValue* aValue)
|
||||
{
|
||||
if (!aCalc->HasCalcNode()) {
|
||||
SetValueToCalc(static_cast<const nsStyleCoord::CalcValue*>(aCalc), aValue);
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
|
||||
nsAutoString result;
|
||||
AppendCalcNodeToString(aCalc->mNode, val, result);
|
||||
aValue->SetString(result); // not really SetString
|
||||
}
|
||||
|
||||
static void
|
||||
AppendCSSGradientLength(const nsStyleCoord& aValue,
|
||||
nsROCSSPrimitiveValue* aPrimitive,
|
||||
@@ -3193,6 +3312,66 @@ nsComputedDOMStyle::DoGetBorderRightWidth()
|
||||
return GetBorderWidthFor(eSideRight);
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetBorderWidth()
|
||||
{
|
||||
nscoord widths[4];
|
||||
if (mInnerFrame) {
|
||||
AssertFlushedPendingReflows();
|
||||
const nsMargin& usedBorder = mInnerFrame->GetUsedBorder();
|
||||
NS_FOR_CSS_SIDES(side) {
|
||||
widths[side] = usedBorder.Side(side);
|
||||
}
|
||||
} else {
|
||||
const nsStyleBorder* border = StyleBorder();
|
||||
NS_FOR_CSS_SIDES(side) {
|
||||
widths[side] = border->GetComputedBorderWidth(side);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> top = new nsROCSSPrimitiveValue;
|
||||
top->SetAppUnits(widths[eSideTop]);
|
||||
valueList->AppendCSSValue(top.forget());
|
||||
|
||||
if (widths[eSideRight] == widths[eSideLeft]) {
|
||||
if (widths[eSideTop] == widths[eSideBottom]) {
|
||||
if (widths[eSideTop] == widths[eSideRight]) {
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> bottom = new nsROCSSPrimitiveValue;
|
||||
bottom->SetAppUnits(widths[eSideBottom]);
|
||||
valueList->AppendCSSValue(bottom.forget());
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> bottom = new nsROCSSPrimitiveValue;
|
||||
bottom->SetAppUnits(widths[eSideBottom]);
|
||||
valueList->AppendCSSValue(bottom.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> left = new nsROCSSPrimitiveValue;
|
||||
left->SetAppUnits(widths[eSideLeft]);
|
||||
valueList->AppendCSSValue(left.forget());
|
||||
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetBorderTopColor()
|
||||
{
|
||||
@@ -6842,5 +7021,3 @@ nsComputedDOMStyle::DoGetOverflowBlockEnd()
|
||||
{
|
||||
return DoGetOverflowBlock();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -226,6 +226,7 @@ private:
|
||||
*/
|
||||
|
||||
already_AddRefed<CSSValue> DoGetAppearance();
|
||||
already_AddRefed<CSSValue> DoGetAspectRatio();
|
||||
|
||||
/* Box properties */
|
||||
already_AddRefed<CSSValue> DoGetBoxAlign();
|
||||
@@ -344,6 +345,7 @@ private:
|
||||
already_AddRefed<CSSValue> DoGetBorderBottomWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderLeftWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderRightWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderTopColor();
|
||||
already_AddRefed<CSSValue> DoGetBorderBottomColor();
|
||||
already_AddRefed<CSSValue> DoGetBorderLeftColor();
|
||||
|
||||
@@ -51,6 +51,7 @@ COMPUTED_STYLE_PROP(animation_iteration_count, AnimationIterationCount)
|
||||
COMPUTED_STYLE_PROP(animation_name, AnimationName)
|
||||
COMPUTED_STYLE_PROP(animation_play_state, AnimationPlayState)
|
||||
COMPUTED_STYLE_PROP(animation_timing_function, AnimationTimingFunction)
|
||||
COMPUTED_STYLE_PROP(aspect_ratio, AspectRatio)
|
||||
COMPUTED_STYLE_PROP(backface_visibility, BackfaceVisibility)
|
||||
//// COMPUTED_STYLE_PROP(background, Background)
|
||||
COMPUTED_STYLE_PROP(background_attachment, BackgroundAttachment)
|
||||
@@ -95,7 +96,7 @@ COMPUTED_STYLE_PROP(border_top_left_radius, BorderTopLeftRadius)
|
||||
COMPUTED_STYLE_PROP(border_top_right_radius, BorderTopRightRadius)
|
||||
COMPUTED_STYLE_PROP(border_top_style, BorderTopStyle)
|
||||
COMPUTED_STYLE_PROP(border_top_width, BorderTopWidth)
|
||||
//// COMPUTED_STYLE_PROP(border_width, BorderWidth)
|
||||
COMPUTED_STYLE_PROP(border_width, BorderWidth)
|
||||
COMPUTED_STYLE_PROP(bottom, Bottom)
|
||||
COMPUTED_STYLE_PROP(box_decoration_break, BoxDecorationBreak)
|
||||
COMPUTED_STYLE_PROP(box_shadow, BoxShadow)
|
||||
|
||||
@@ -33,7 +33,7 @@ class DocumentRule;
|
||||
} // namespace mozilla
|
||||
|
||||
struct nsMediaExpression {
|
||||
enum Range { eMin, eMax, eEqual };
|
||||
enum Range { eMin, eMax, eEqual, eMinExclusive, eMaxExclusive };
|
||||
|
||||
const nsMediaFeature *mFeature;
|
||||
Range mRange;
|
||||
|
||||
+527
-41
@@ -13,6 +13,7 @@
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Function.h"
|
||||
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
|
||||
#include "mozilla/Likely.h"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "nsIStyleRule.h"
|
||||
#include "nsBidiUtils.h"
|
||||
#include "nsStyleStructInlines.h"
|
||||
#include "nsCSSNonSRGBColorSpace.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsContentUtils.h"
|
||||
@@ -319,6 +321,11 @@ nsRuleNode::EnsureInlineDisplay(StyleDisplay& display)
|
||||
}
|
||||
}
|
||||
|
||||
enum class AppUnitRounding {
|
||||
Default,
|
||||
TowardZero
|
||||
};
|
||||
|
||||
static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
nscoord aFontSize,
|
||||
const nsStyleFont* aStyleFont,
|
||||
@@ -326,7 +333,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
nsPresContext* aPresContext,
|
||||
bool aUseProvidedRootEmSize,
|
||||
bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions);
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding =
|
||||
AppUnitRounding::Default);
|
||||
|
||||
struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
public css::NumbersAlreadyNormalizedOps
|
||||
@@ -339,18 +348,21 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
const bool mUseProvidedRootEmSize;
|
||||
const bool mUseUserFontSet;
|
||||
RuleNodeCacheConditions& mConditions;
|
||||
const AppUnitRounding mRounding;
|
||||
|
||||
CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont,
|
||||
nsStyleContext* aStyleContext, nsPresContext* aPresContext,
|
||||
bool aUseProvidedRootEmSize, bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding)
|
||||
: mFontSize(aFontSize),
|
||||
mStyleFont(aStyleFont),
|
||||
mStyleContext(aStyleContext),
|
||||
mPresContext(aPresContext),
|
||||
mUseProvidedRootEmSize(aUseProvidedRootEmSize),
|
||||
mUseUserFontSet(aUseUserFontSet),
|
||||
mConditions(aConditions)
|
||||
mConditions(aConditions),
|
||||
mRounding(aRounding)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -358,15 +370,84 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
{
|
||||
return CalcLengthWith(aValue, mFontSize, mStyleFont,
|
||||
mStyleContext, mPresContext, mUseProvidedRootEmSize,
|
||||
mUseUserFontSet, mConditions);
|
||||
mUseUserFontSet, mConditions, mRounding);
|
||||
}
|
||||
};
|
||||
|
||||
static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
|
||||
static inline nscoord
|
||||
ScaleCoordWithRounding(const nsCSSValue& aValue, float aFactor,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::TowardZero) {
|
||||
return NSToCoordTruncClamped(aValue.GetFloatValue() * aFactor);
|
||||
}
|
||||
return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
|
||||
}
|
||||
|
||||
static inline nscoord
|
||||
CSSPixelsToAppUnitsWithRounding(float aPixels, AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::TowardZero) {
|
||||
return NSToCoordTruncClamped(double(aPixels) *
|
||||
nsPresContext::AppUnitsPerCSSPixel());
|
||||
}
|
||||
return nsPresContext::CSSPixelsToAppUnits(aPixels);
|
||||
}
|
||||
|
||||
static nscoord
|
||||
GetFixedLengthWithRounding(const nsCSSValue& aValue,
|
||||
nsPresContext* aPresContext,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::Default) {
|
||||
return aValue.GetFixedLength(aPresContext);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_PhysicalMillimeter,
|
||||
"not a fixed length unit");
|
||||
double inches = double(aValue.GetFloatValue()) / MM_PER_INCH_FLOAT;
|
||||
return NSToCoordTruncClamped(
|
||||
inches * aPresContext->DeviceContext()->AppUnitsPerPhysicalInch());
|
||||
}
|
||||
|
||||
static nscoord
|
||||
GetPixelLengthWithRounding(const nsCSSValue& aValue,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
MOZ_ASSERT(aValue.IsPixelLengthUnit(), "not a pixel length unit");
|
||||
|
||||
double scaleFactor;
|
||||
switch (aValue.GetUnit()) {
|
||||
case eCSSUnit_Pixel:
|
||||
return CSSPixelsToAppUnitsWithRounding(aValue.GetFloatValue(),
|
||||
aRounding);
|
||||
case eCSSUnit_Pica:
|
||||
scaleFactor = 16.0;
|
||||
break;
|
||||
case eCSSUnit_Point:
|
||||
scaleFactor = 4.0 / 3.0;
|
||||
break;
|
||||
case eCSSUnit_Inch:
|
||||
scaleFactor = 96.0;
|
||||
break;
|
||||
case eCSSUnit_Millimeter:
|
||||
scaleFactor = 96.0 / 25.4;
|
||||
break;
|
||||
case eCSSUnit_Centimeter:
|
||||
scaleFactor = 96.0 / 2.54;
|
||||
break;
|
||||
case eCSSUnit_Quarter:
|
||||
scaleFactor = 96.0 / 101.6;
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("should never get here");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CSSPixelsToAppUnitsWithRounding(
|
||||
float(double(aValue.GetFloatValue()) * scaleFactor), aRounding);
|
||||
}
|
||||
|
||||
static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue,
|
||||
nscoord aViewportSize)
|
||||
{
|
||||
@@ -465,7 +546,8 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
// except when called from
|
||||
// CalcLengthWithInitialFont.
|
||||
bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(),
|
||||
"not a length or calc unit");
|
||||
@@ -476,10 +558,10 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
NS_ASSERTION(aPresContext, "Must have prescontext");
|
||||
|
||||
if (aValue.IsFixedLengthUnit()) {
|
||||
return aValue.GetFixedLength(aPresContext);
|
||||
return GetFixedLengthWithRounding(aValue, aPresContext, aRounding);
|
||||
}
|
||||
if (aValue.IsPixelLengthUnit()) {
|
||||
return aValue.GetPixelLength();
|
||||
return GetPixelLengthWithRounding(aValue, aRounding);
|
||||
}
|
||||
if (aValue.IsCalcUnit()) {
|
||||
// For properties for which lengths are the *only* units accepted in
|
||||
@@ -490,7 +572,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
CalcLengthCalcOps ops(aFontSize, aStyleFont,
|
||||
aStyleContext, aPresContext,
|
||||
aUseProvidedRootEmSize, aUseUserFontSet,
|
||||
aConditions);
|
||||
aConditions, aRounding);
|
||||
return css::ComputeCalc(aValue, ops);
|
||||
}
|
||||
switch (aValue.GetUnit()) {
|
||||
@@ -623,7 +705,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
rootFontSize = rootStyleFont->mFont.size;
|
||||
}
|
||||
|
||||
return ScaleCoordRound(aValue, float(rootFontSize));
|
||||
return ScaleCoordWithRounding(aValue, float(rootFontSize), aRounding);
|
||||
}
|
||||
default:
|
||||
// Fall through to the code for units that can't be stored in the
|
||||
@@ -648,7 +730,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
// CSS2.1 specifies that this unit scales to the computed font
|
||||
// size, not the em-width in the font metrics, despite the name.
|
||||
aConditions.SetFontSizeDependency(aFontSize);
|
||||
return ScaleCoordRound(aValue, float(aFontSize));
|
||||
return ScaleCoordWithRounding(aValue, float(aFontSize), aRounding);
|
||||
}
|
||||
case eCSSUnit_XHeight: {
|
||||
aPresContext->SetUsesExChUnits(true);
|
||||
@@ -656,7 +738,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
GetMetricsFor(aPresContext, aStyleContext, styleFont,
|
||||
aFontSize, aUseUserFontSet);
|
||||
aConditions.SetUncacheable();
|
||||
return ScaleCoordRound(aValue, float(fm->XHeight()));
|
||||
return ScaleCoordWithRounding(aValue, float(fm->XHeight()), aRounding);
|
||||
}
|
||||
case eCSSUnit_Char: {
|
||||
aPresContext->SetUsesExChUnits(true);
|
||||
@@ -668,8 +750,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
|
||||
|
||||
aConditions.SetUncacheable();
|
||||
return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
|
||||
zeroWidth));
|
||||
return ScaleCoordWithRounding(
|
||||
aValue, ceil(aPresContext->AppUnitsPerDevPixel() * zeroWidth),
|
||||
aRounding);
|
||||
}
|
||||
default:
|
||||
NS_NOTREACHED("unexpected unit");
|
||||
@@ -701,6 +784,30 @@ static inline nscoord CalcLength(const nsCSSValue& aValue,
|
||||
aPresContext, aConditions);
|
||||
}
|
||||
|
||||
static inline nscoord
|
||||
CalcLengthTowardZero(const nsCSSValue& aValue,
|
||||
nsStyleContext* aStyleContext,
|
||||
nsPresContext* aPresContext,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
{
|
||||
NS_ASSERTION(aStyleContext, "Must have style data");
|
||||
|
||||
return CalcLengthWith(aValue, -1, nullptr,
|
||||
aStyleContext, aPresContext,
|
||||
false, true, aConditions,
|
||||
AppUnitRounding::TowardZero);
|
||||
}
|
||||
|
||||
already_AddRefed<nsStyleCoord::CalcNode>
|
||||
nsRuleNode::ComputedCalc::ToCalcNode() const
|
||||
{
|
||||
if (mNode) {
|
||||
RefPtr<nsStyleCoord::CalcNode> node = mNode;
|
||||
return node.forget();
|
||||
}
|
||||
return nsStyleCoord::CalcNode::CreateLeaf(mLength, mPercent, mHasPercent);
|
||||
}
|
||||
|
||||
/* static */ nscoord
|
||||
nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue)
|
||||
@@ -733,27 +840,42 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps
|
||||
{
|
||||
if (aValue.GetUnit() == eCSSUnit_Percent) {
|
||||
mHasPercent = true;
|
||||
return result_type(0, aValue.GetPercentValue());
|
||||
return result_type(0, aValue.GetPercentValue(), true);
|
||||
}
|
||||
return result_type(CalcLength(aValue, mContext, mPresContext,
|
||||
mConditions),
|
||||
0.0f);
|
||||
0.0f, false);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeAdditive(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
if (aValue1.HasNode() || aValue2.HasNode()) {
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(
|
||||
aCalcFunction == eCSSUnit_Calc_Plus
|
||||
? nsStyleCoord::CalcNode::Type::Add
|
||||
: nsStyleCoord::CalcNode::Type::Subtract);
|
||||
node->mChildren.AppendElement(aValue1.ToCalcNode());
|
||||
node->mChildren.AppendElement(aValue2.ToCalcNode());
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
|
||||
if (aCalcFunction == eCSSUnit_Calc_Plus) {
|
||||
return result_type(NSCoordSaturatingAdd(aValue1.mLength,
|
||||
aValue2.mLength),
|
||||
aValue1.mPercent + aValue2.mPercent);
|
||||
aValue1.mPercent + aValue2.mPercent,
|
||||
aValue1.mHasPercent || aValue2.mHasPercent);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
|
||||
"min() and max() are not allowed in calc() on transform");
|
||||
return result_type(NSCoordSaturatingSubtract(aValue1.mLength,
|
||||
aValue2.mLength, 0),
|
||||
aValue1.mPercent - aValue2.mPercent);
|
||||
aValue1.mPercent - aValue2.mPercent,
|
||||
aValue1.mHasPercent || aValue2.mHasPercent);
|
||||
}
|
||||
|
||||
result_type
|
||||
@@ -762,8 +884,19 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps
|
||||
{
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
|
||||
"unexpected unit");
|
||||
if (aValue2.HasNode()) {
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Multiply);
|
||||
node->mChildren.AppendElement(aValue2.ToCalcNode());
|
||||
node->mNumber = aValue1;
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
|
||||
return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1),
|
||||
aValue1 * aValue2.mPercent);
|
||||
aValue1 * aValue2.mPercent,
|
||||
aValue2.mHasPercent);
|
||||
}
|
||||
|
||||
result_type
|
||||
@@ -774,10 +907,89 @@ struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps
|
||||
aCalcFunction == eCSSUnit_Calc_Divided,
|
||||
"unexpected unit");
|
||||
if (aCalcFunction == eCSSUnit_Calc_Divided) {
|
||||
if (aValue1.HasNode()) {
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Divide);
|
||||
node->mChildren.AppendElement(aValue1.ToCalcNode());
|
||||
node->mNumber = aValue2;
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
aValue2 = 1.0f / aValue2;
|
||||
} else if (aValue1.HasNode()) {
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Multiply);
|
||||
node->mChildren.AppendElement(aValue1.ToCalcNode());
|
||||
node->mNumber = aValue2;
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2),
|
||||
aValue1.mPercent * aValue2);
|
||||
aValue1.mPercent * aValue2,
|
||||
aValue1.mHasPercent);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
const bool hasNode = aValue1.HasNode() || aValue2.HasNode();
|
||||
const bool hasPercent = aValue1.mHasPercent || aValue2.mHasPercent;
|
||||
if (!hasNode && !hasPercent) {
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
return result_type(std::min(aValue1.mLength, aValue2.mLength),
|
||||
0.0f, false);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
return result_type(std::max(aValue1.mLength, aValue2.mLength),
|
||||
0.0f, false);
|
||||
}
|
||||
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(
|
||||
aCalcFunction == eCSSUnit_Calc_Min
|
||||
? nsStyleCoord::CalcNode::Type::Min
|
||||
: nsStyleCoord::CalcNode::Type::Max);
|
||||
if (aValue1.HasNode() &&
|
||||
aValue1.mNode->mType == node->mType) {
|
||||
node->mChildren.AppendElements(aValue1.mNode->mChildren);
|
||||
} else {
|
||||
node->mChildren.AppendElement(aValue1.ToCalcNode());
|
||||
}
|
||||
if (aValue2.HasNode() &&
|
||||
aValue2.mNode->mType == node->mType) {
|
||||
node->mChildren.AppendElements(aValue2.mNode->mChildren);
|
||||
} else {
|
||||
node->mChildren.AppendElement(aValue2.ToCalcNode());
|
||||
}
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
const bool hasNode = aMin.HasNode() || aCenter.HasNode() || aMax.HasNode();
|
||||
const bool hasPercent = aMin.mHasPercent ||
|
||||
aCenter.mHasPercent ||
|
||||
aMax.mHasPercent;
|
||||
if (!hasNode && !hasPercent) {
|
||||
return result_type(std::max(aMin.mLength,
|
||||
std::min(aCenter.mLength, aMax.mLength)),
|
||||
0.0f, false);
|
||||
}
|
||||
|
||||
RefPtr<nsStyleCoord::CalcNode> node =
|
||||
nsStyleCoord::CalcNode::Create(nsStyleCoord::CalcNode::Type::Clamp);
|
||||
node->mChildren.AppendElement(aMin.ToCalcNode());
|
||||
node->mChildren.AppendElement(aCenter.ToCalcNode());
|
||||
node->mChildren.AppendElement(aMax.ToCalcNode());
|
||||
node->mHasPercent = node->HasPercent();
|
||||
mHasPercent = mHasPercent || node->mHasPercent;
|
||||
return result_type(node.forget());
|
||||
}
|
||||
|
||||
};
|
||||
@@ -795,7 +1007,8 @@ SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord,
|
||||
|
||||
calcObj->mLength = vals.mLength;
|
||||
calcObj->mPercent = vals.mPercent;
|
||||
calcObj->mHasPercent = ops.mHasPercent;
|
||||
calcObj->mHasPercent = vals.mHasPercent || ops.mHasPercent;
|
||||
calcObj->mNode = vals.mNode;
|
||||
|
||||
aCoord.SetCalcValue(calcObj);
|
||||
}
|
||||
@@ -818,8 +1031,7 @@ nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue,
|
||||
nscoord aPercentageBasis)
|
||||
{
|
||||
nsStyleCoord::Calc* calc = aValue.GetCalcValue();
|
||||
return calc->mLength +
|
||||
NSToCoordFloorClamped(aPercentageBasis * calc->mPercent);
|
||||
return calc->Resolve(aPercentageBasis);
|
||||
}
|
||||
|
||||
/* static */ nscoord
|
||||
@@ -1056,6 +1268,137 @@ SetPairCoords(const nsCSSValue& aValue,
|
||||
return cX;
|
||||
}
|
||||
|
||||
static void
|
||||
GetColorMixPremultipliedWeights(float aAlpha1, float aAlpha2,
|
||||
float aWeight1, float aWeight2,
|
||||
float& aPremultipliedWeight1,
|
||||
float& aPremultipliedWeight2,
|
||||
float& aAlpha)
|
||||
{
|
||||
float alphaWeight1 = aWeight1 * aAlpha1;
|
||||
float alphaWeight2 = aWeight2 * aAlpha2;
|
||||
|
||||
aAlpha = alphaWeight1 + alphaWeight2;
|
||||
if (aAlpha <= 0.0f) {
|
||||
aPremultipliedWeight1 = 0.0f;
|
||||
aPremultipliedWeight2 = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
aPremultipliedWeight1 = alphaWeight1 / aAlpha;
|
||||
aPremultipliedWeight2 = alphaWeight2 / aAlpha;
|
||||
}
|
||||
|
||||
static css::OklabColor
|
||||
OklchToOklabColor(const css::OklchColor& aColor)
|
||||
{
|
||||
float hueRadians = aColor.mHue * css::kRadiansPerDegree;
|
||||
return {
|
||||
aColor.mL,
|
||||
aColor.mChroma * std::cos(hueRadians),
|
||||
aColor.mChroma * std::sin(hueRadians)
|
||||
};
|
||||
}
|
||||
|
||||
static void
|
||||
GetColorMixColorComponents(const nsCSSValue& aValue, nscolor aResolvedColor,
|
||||
css::OklabColor& aOklab,
|
||||
css::OklchColor& aOklch,
|
||||
float& aAlpha)
|
||||
{
|
||||
nsCSSUnit unit = aValue.GetUnit();
|
||||
if (unit == eCSSUnit_OklabColor) {
|
||||
const nsCSSValueFloatColor* color = aValue.GetFloatColorValue();
|
||||
aOklab = { color->Comp1(), color->Comp2(), color->Comp3() };
|
||||
aOklch = css::OklabToOklchColor(aOklab);
|
||||
aAlpha = mozilla::clamped(color->Alpha(), 0.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unit == eCSSUnit_OklchColor) {
|
||||
const nsCSSValueFloatColor* color = aValue.GetFloatColorValue();
|
||||
aOklch = { color->Comp1(), color->Comp2(), color->Comp3() };
|
||||
aOklab = OklchToOklabColor(aOklch);
|
||||
aAlpha = mozilla::clamped(color->Alpha(), 0.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
aOklab = css::SRGBToOklabColor(aResolvedColor);
|
||||
aOklch = css::OklabToOklchColor(aOklab);
|
||||
aAlpha = NS_GET_A(aResolvedColor) / 255.0f;
|
||||
}
|
||||
|
||||
static float
|
||||
InterpolateColorMixHue(float aHue1, float aHue2, float aWeight1,
|
||||
float aWeight2)
|
||||
{
|
||||
float hueDiff = aHue2 - aHue1;
|
||||
if (hueDiff > 180.0f) {
|
||||
aHue1 += 360.0f;
|
||||
} else if (hueDiff < -180.0f) {
|
||||
aHue2 += 360.0f;
|
||||
}
|
||||
|
||||
float hue = aHue1 * aWeight1 + aHue2 * aWeight2;
|
||||
hue = std::fmod(hue, 360.0f);
|
||||
if (hue < 0.0f) {
|
||||
hue += 360.0f;
|
||||
}
|
||||
return hue;
|
||||
}
|
||||
|
||||
static nscolor
|
||||
MixColorsInOklab(const css::OklabColor& aColor1, float aAlpha1,
|
||||
const css::OklabColor& aColor2, float aAlpha2,
|
||||
float aWeight1, float aWeight2, float aAlphaMultiplier)
|
||||
{
|
||||
float premultipliedWeight1, premultipliedWeight2, alpha;
|
||||
GetColorMixPremultipliedWeights(aAlpha1, aAlpha2, aWeight1, aWeight2,
|
||||
premultipliedWeight1, premultipliedWeight2,
|
||||
alpha);
|
||||
alpha *= aAlphaMultiplier;
|
||||
if (alpha <= 0.0f) {
|
||||
return NS_RGBA(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return css::OklabToSRGBColor(
|
||||
aColor1.mL * premultipliedWeight1 + aColor2.mL * premultipliedWeight2,
|
||||
aColor1.mA * premultipliedWeight1 + aColor2.mA * premultipliedWeight2,
|
||||
aColor1.mB * premultipliedWeight1 + aColor2.mB * premultipliedWeight2,
|
||||
alpha);
|
||||
}
|
||||
|
||||
static nscolor
|
||||
MixColorsInOklch(css::OklchColor aColor1, float aAlpha1,
|
||||
css::OklchColor aColor2, float aAlpha2,
|
||||
float aWeight1, float aWeight2, float aAlphaMultiplier)
|
||||
{
|
||||
if (aColor1.mChroma <= css::kOklchPowerlessChromaEpsilon) {
|
||||
aColor1.mHue = aColor2.mHue;
|
||||
}
|
||||
if (aColor2.mChroma <= css::kOklchPowerlessChromaEpsilon) {
|
||||
aColor2.mHue = aColor1.mHue;
|
||||
}
|
||||
|
||||
float premultipliedWeight1, premultipliedWeight2, alpha;
|
||||
GetColorMixPremultipliedWeights(aAlpha1, aAlpha2, aWeight1, aWeight2,
|
||||
premultipliedWeight1, premultipliedWeight2,
|
||||
alpha);
|
||||
alpha *= aAlphaMultiplier;
|
||||
if (alpha <= 0.0f) {
|
||||
return NS_RGBA(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
float hue = InterpolateColorMixHue(aColor1.mHue, aColor2.mHue,
|
||||
aWeight1, aWeight2);
|
||||
return css::OklchToSRGBColor(
|
||||
aColor1.mL * premultipliedWeight1 + aColor2.mL * premultipliedWeight2,
|
||||
aColor1.mChroma * premultipliedWeight1 +
|
||||
aColor2.mChroma * premultipliedWeight2,
|
||||
hue,
|
||||
alpha);
|
||||
}
|
||||
|
||||
static bool
|
||||
SetColor(const nsCSSValue& aValue,
|
||||
const nscolor aParentColor,
|
||||
@@ -1144,22 +1487,29 @@ SetColor(const nsCSSValue& aValue,
|
||||
const mozilla::css::ColorMixValue* colorMix = aValue.GetColorMixValue();
|
||||
if (colorMix) {
|
||||
nscolor color1, color2;
|
||||
bool resolvedColor1, resolvedColor2;
|
||||
|
||||
// XXX: This is a hack to avoid recursive calls to SetColor when either color resolves
|
||||
// to NS_COLOR_CURRENTCOLOR, as it would result in re-evaluation of the color.
|
||||
// Instead of recursing, we reach up to set either color to the parent color, instead.
|
||||
if (colorMix->mColor1.GetUnit() == eCSSUnit_EnumColor && colorMix->mColor1.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
|
||||
color1 = aParentColor;
|
||||
resolvedColor1 = true;
|
||||
} else {
|
||||
SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext, color1, aConditions);
|
||||
resolvedColor1 =
|
||||
SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext,
|
||||
color1, aConditions);
|
||||
}
|
||||
if (colorMix->mColor2.GetUnit() == eCSSUnit_EnumColor && colorMix->mColor2.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
|
||||
color2 = aParentColor;
|
||||
resolvedColor2 = true;
|
||||
} else {
|
||||
SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext, color2, aConditions);
|
||||
resolvedColor2 =
|
||||
SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext,
|
||||
color2, aConditions);
|
||||
}
|
||||
|
||||
if (color1 && color2) {
|
||||
if (resolvedColor1 && resolvedColor2) {
|
||||
// interpolate each RGBA component with proper percentage handling
|
||||
float w1 = colorMix->mWeight1;
|
||||
float w2 = colorMix->mWeight2;
|
||||
@@ -1176,9 +1526,18 @@ SetColor(const nsCSSValue& aValue,
|
||||
w1 = w2 = 0.5f;
|
||||
sum = 1.0f;
|
||||
}
|
||||
float alphaMultiplier = std::min(sum, 1.0f);
|
||||
|
||||
float norm1 = w1 / sum;
|
||||
float norm2 = w2 / sum;
|
||||
|
||||
css::OklabColor oklab1, oklab2;
|
||||
css::OklchColor oklch1, oklch2;
|
||||
float oklabAlpha1, oklabAlpha2;
|
||||
GetColorMixColorComponents(colorMix->mColor1, color1, oklab1,
|
||||
oklch1, oklabAlpha1);
|
||||
GetColorMixColorComponents(colorMix->mColor2, color2, oklab2,
|
||||
oklch2, oklabAlpha2);
|
||||
|
||||
if (colorMix->mColorSpace == mozilla::css::ColorMixColorSpace::HSL) {
|
||||
// HSL color space mixing
|
||||
@@ -1251,10 +1610,23 @@ SetColor(const nsCSSValue& aValue,
|
||||
|
||||
// Convert back to RGB
|
||||
nscolor hslResult = NS_HSL2RGB(h, s, l);
|
||||
uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f);
|
||||
uint8_t aInt = (uint8_t)mozilla::clamped(
|
||||
a * alphaMultiplier * 255.0f + 0.5f, 0.0f, 255.0f);
|
||||
|
||||
aResult = NS_RGBA(NS_GET_R(hslResult), NS_GET_G(hslResult), NS_GET_B(hslResult), aInt);
|
||||
result = true;
|
||||
} else if (colorMix->mColorSpace ==
|
||||
mozilla::css::ColorMixColorSpace::Oklab) {
|
||||
aResult = MixColorsInOklab(oklab1, oklabAlpha1,
|
||||
oklab2, oklabAlpha2,
|
||||
norm1, norm2, alphaMultiplier);
|
||||
result = true;
|
||||
} else if (colorMix->mColorSpace ==
|
||||
mozilla::css::ColorMixColorSpace::Oklch) {
|
||||
aResult = MixColorsInOklch(oklch1, oklabAlpha1,
|
||||
oklch2, oklabAlpha2,
|
||||
norm1, norm2, alphaMultiplier);
|
||||
result = true;
|
||||
} else {
|
||||
// sRGB color space mixing with proper alpha premultiplication
|
||||
float r1 = NS_GET_R(color1);
|
||||
@@ -1304,7 +1676,8 @@ SetColor(const nsCSSValue& aValue,
|
||||
uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f);
|
||||
uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f);
|
||||
uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f);
|
||||
uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f);
|
||||
uint8_t aInt = (uint8_t)mozilla::clamped(
|
||||
a * alphaMultiplier * 255.0f + 0.5f, 0.0f, 255.0f);
|
||||
|
||||
aResult = NS_RGBA(rInt, gInt, bInt, aInt);
|
||||
result = true;
|
||||
@@ -1778,6 +2151,17 @@ SetValue(const nsCSSValue& aValue, FieldT& aField,
|
||||
#define SETFCT_UNSET_INHERIT 0x00400000
|
||||
#define SETFCT_UNSET_INITIAL 0x00800000
|
||||
|
||||
struct RuleNodeReduceNumberCalcOps : public css::BasicFloatCalcOps,
|
||||
public css::CSSValueInputCalcOps,
|
||||
public css::NumbersAlreadyNormalizedOps
|
||||
{
|
||||
result_type ComputeLeafValue(const nsCSSValue& aValue)
|
||||
{
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
|
||||
return aValue.GetFloatValue();
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aConditions,
|
||||
float aParentValue, float aInitialValue, uint32_t aFlags = 0)
|
||||
@@ -1788,6 +2172,9 @@ SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aCon
|
||||
|
||||
case eCSSUnit_Number:
|
||||
aField = aValue.GetFloatValue();
|
||||
if (mozilla::IsNaN(aField)) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
if (aFlags & SETFCT_POSITIVE) {
|
||||
NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property");
|
||||
if (aField < 0.0f) {
|
||||
@@ -1804,6 +2191,29 @@ SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aCon
|
||||
}
|
||||
return;
|
||||
|
||||
case eCSSUnit_Calc: {
|
||||
RuleNodeReduceNumberCalcOps ops;
|
||||
aField = css::ComputeCalc(aValue, ops);
|
||||
if (mozilla::IsNaN(aField)) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
if (aFlags & SETFCT_POSITIVE) {
|
||||
NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property");
|
||||
if (aField < 0.0f) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
}
|
||||
if (aFlags & SETFCT_OPACITY) {
|
||||
if (aField < 0.0f) {
|
||||
aField = 0.0f;
|
||||
}
|
||||
if (aField > 1.0f) {
|
||||
aField = 1.0f;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case eCSSUnit_Inherit:
|
||||
aConditions.SetUncacheable();
|
||||
aField = aParentValue;
|
||||
@@ -4813,6 +5223,34 @@ struct LengthNumberCalcOps : public css::NumbersAlreadyNormalizedOps
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber);
|
||||
LengthNumberCalcObj result;
|
||||
result.mIsNumber = aValue1.mIsNumber;
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
result.mValue = std::min(aValue1.mValue, aValue2.mValue);
|
||||
return result;
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
result.mValue = std::max(aValue1.mValue, aValue2.mValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
MOZ_ASSERT(aMin.mIsNumber == aCenter.mIsNumber &&
|
||||
aCenter.mIsNumber == aMax.mIsNumber);
|
||||
LengthNumberCalcObj result;
|
||||
result.mIsNumber = aCenter.mIsNumber;
|
||||
result.mValue = std::max(aMin.mValue,
|
||||
std::min(aCenter.mValue, aMax.mValue));
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type ComputeLeafValue(const nsCSSValue& aValue)
|
||||
{
|
||||
LengthNumberCalcObj result;
|
||||
@@ -4903,6 +5341,38 @@ struct LengthPercentNumberCalcOps : public css::NumbersAlreadyNormalizedOps
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber);
|
||||
result_type result;
|
||||
result.mIsNumber = aValue1.mIsNumber;
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
result.mLength = std::min(aValue1.mLength, aValue2.mLength);
|
||||
result.mPercent = std::min(aValue1.mPercent, aValue2.mPercent);
|
||||
} else {
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
result.mLength = std::max(aValue1.mLength, aValue2.mLength);
|
||||
result.mPercent = std::max(aValue1.mPercent, aValue2.mPercent);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
MOZ_ASSERT(aMin.mIsNumber == aCenter.mIsNumber &&
|
||||
aCenter.mIsNumber == aMax.mIsNumber);
|
||||
result_type result;
|
||||
result.mIsNumber = aCenter.mIsNumber;
|
||||
result.mLength = std::max(aMin.mLength,
|
||||
std::min(aCenter.mLength, aMax.mLength));
|
||||
result.mPercent = std::max(aMin.mPercent,
|
||||
std::min(aCenter.mPercent, aMax.mPercent));
|
||||
return result;
|
||||
}
|
||||
|
||||
result_type
|
||||
ComputeLeafValue(const nsCSSValue& aValue)
|
||||
{
|
||||
@@ -8022,13 +8492,11 @@ nsRuleNode::ComputeBorderData(void* aStartStruct,
|
||||
"Unexpected enum value");
|
||||
border->SetBorderWidth(side,
|
||||
(mPresContext->GetBorderWidthTable())[value.GetIntValue()]);
|
||||
// OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
|
||||
} else if (SetCoord(value, coord, nsStyleCoord(),
|
||||
SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
|
||||
aContext, mPresContext, conditions)) {
|
||||
NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
|
||||
} else if (value.IsLengthUnit() || value.IsCalcUnit()) {
|
||||
// clamp negative calc() to 0.
|
||||
border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
|
||||
border->SetBorderWidth(side,
|
||||
std::max(CalcLengthTowardZero(value, aContext, mPresContext,
|
||||
conditions), 0));
|
||||
} else if (eCSSUnit_Inherit == value.GetUnit()) {
|
||||
conditions.SetUncacheable();
|
||||
border->SetBorderWidth(side,
|
||||
@@ -8306,6 +8774,11 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
|
||||
eCSSUnit_Revert == outlineWidthValue->GetUnit()) {
|
||||
outline->mOutlineWidth =
|
||||
nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
|
||||
} else if (outlineWidthValue->IsLengthUnit() ||
|
||||
outlineWidthValue->IsCalcUnit()) {
|
||||
outline->mOutlineWidth.SetCoordValue(
|
||||
CalcLengthTowardZero(*outlineWidthValue, aContext, mPresContext,
|
||||
conditions));
|
||||
} else {
|
||||
SetCoord(*outlineWidthValue, outline->mOutlineWidth,
|
||||
parentOutline->mOutlineWidth,
|
||||
@@ -8316,13 +8789,21 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
|
||||
// outline-offset: length, inherit
|
||||
nsStyleCoord tempCoord;
|
||||
const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
|
||||
if (SetCoord(*outlineOffsetValue, tempCoord,
|
||||
if (outlineOffsetValue->IsLengthUnit() || outlineOffsetValue->IsCalcUnit()) {
|
||||
outline->mOutlineOffset =
|
||||
NS_ROUND_OFFSET_TO_PIXELS(
|
||||
CalcLengthTowardZero(*outlineOffsetValue, aContext, mPresContext,
|
||||
conditions),
|
||||
mPresContext->AppUnitsPerDevPixel());
|
||||
} else if (SetCoord(*outlineOffsetValue, tempCoord,
|
||||
nsStyleCoord(parentOutline->mOutlineOffset,
|
||||
nsStyleCoord::CoordConstructor),
|
||||
SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
|
||||
SETCOORD_UNSET_INITIAL,
|
||||
aContext, mPresContext, conditions)) {
|
||||
outline->mOutlineOffset = tempCoord.GetCoordValue();
|
||||
outline->mOutlineOffset =
|
||||
NS_ROUND_OFFSET_TO_PIXELS(tempCoord.GetCoordValue(),
|
||||
mPresContext->AppUnitsPerDevPixel());
|
||||
} else {
|
||||
NS_ASSERTION(outlineOffsetValue->GetUnit() == eCSSUnit_Null,
|
||||
"unexpected unit");
|
||||
@@ -8998,11 +9479,16 @@ nsRuleNode::ComputePositionData(void* aStartStruct,
|
||||
SETCOORD_UNSET_INITIAL,
|
||||
aContext, mPresContext, conditions);
|
||||
|
||||
// aspect-ratio: float, initial
|
||||
SetFactor(*aRuleData->ValueForAspectRatio(),
|
||||
pos->mAspectRatio, conditions,
|
||||
parentPos->mAspectRatio, 0.0f,
|
||||
SETFCT_UNSET_INITIAL | SETFCT_POSITIVE | SETFCT_NONE);
|
||||
// aspect-ratio: auto | <ratio>
|
||||
const nsCSSValue* aspectRatio = aRuleData->ValueForAspectRatio();
|
||||
if (aspectRatio->GetUnit() == eCSSUnit_Auto) {
|
||||
pos->mAspectRatio = 0.0f;
|
||||
} else {
|
||||
SetFactor(*aspectRatio,
|
||||
pos->mAspectRatio, conditions,
|
||||
parentPos->mAspectRatio, 0.0f,
|
||||
SETFCT_UNSET_INITIAL | SETFCT_POSITIVE | SETFCT_NONE);
|
||||
}
|
||||
|
||||
// box-sizing: enum, inherit, initial
|
||||
SetValue(*aRuleData->ValueForBoxSizing(),
|
||||
|
||||
@@ -986,9 +986,22 @@ public:
|
||||
struct ComputedCalc {
|
||||
nscoord mLength;
|
||||
float mPercent;
|
||||
bool mHasPercent;
|
||||
RefPtr<nsStyleCoord::CalcNode> mNode;
|
||||
|
||||
ComputedCalc(nscoord aLength, float aPercent)
|
||||
: mLength(aLength), mPercent(aPercent) {}
|
||||
: mLength(aLength), mPercent(aPercent),
|
||||
mHasPercent(aPercent != 0.0f) {}
|
||||
|
||||
ComputedCalc(nscoord aLength, float aPercent, bool aHasPercent)
|
||||
: mLength(aLength), mPercent(aPercent),
|
||||
mHasPercent(aHasPercent) {}
|
||||
|
||||
explicit ComputedCalc(already_AddRefed<nsStyleCoord::CalcNode> aNode)
|
||||
: mLength(0), mPercent(0.0f), mHasPercent(true), mNode(aNode) {}
|
||||
|
||||
bool HasNode() const { return !!mNode; }
|
||||
already_AddRefed<nsStyleCoord::CalcNode> ToCalcNode() const;
|
||||
};
|
||||
static ComputedCalc
|
||||
SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
|
||||
|
||||
@@ -8,6 +8,150 @@
|
||||
#include "nsStyleCoord.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include <algorithm>
|
||||
|
||||
already_AddRefed<nsStyleCoord::CalcNode>
|
||||
nsStyleCoord::CalcNode::CreateLeaf(nscoord aLength, float aPercent,
|
||||
bool aHasPercent)
|
||||
{
|
||||
RefPtr<CalcNode> node = new CalcNode(Type::Leaf);
|
||||
node->mLength = aLength;
|
||||
node->mPercent = aPercent;
|
||||
node->mHasPercent = aHasPercent;
|
||||
return node.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<nsStyleCoord::CalcNode>
|
||||
nsStyleCoord::CalcNode::Create(Type aType)
|
||||
{
|
||||
RefPtr<CalcNode> node = new CalcNode(aType);
|
||||
return node.forget();
|
||||
}
|
||||
|
||||
nsStyleCoord::CalcNode::CalcNode(Type aType)
|
||||
: mType(aType)
|
||||
, mLength(0)
|
||||
, mPercent(0.0f)
|
||||
, mNumber(0.0f)
|
||||
, mHasPercent(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
nsStyleCoord::CalcNode::HasPercent() const
|
||||
{
|
||||
if (mHasPercent) {
|
||||
return true;
|
||||
}
|
||||
for (const RefPtr<CalcNode>& child : mChildren) {
|
||||
if (child->HasPercent()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
nsStyleCoord::CalcNode::Equals(const CalcNode& aOther) const
|
||||
{
|
||||
if (mType != aOther.mType ||
|
||||
mLength != aOther.mLength ||
|
||||
mPercent != aOther.mPercent ||
|
||||
mNumber != aOther.mNumber ||
|
||||
mHasPercent != aOther.mHasPercent ||
|
||||
mChildren.Length() != aOther.mChildren.Length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < mChildren.Length(); ++i) {
|
||||
if (!mChildren[i]->Equals(*aOther.mChildren[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsStyleCoord::CalcNode::HashValue(uint32_t aHash) const
|
||||
{
|
||||
aHash = mozilla::AddToHash(aHash, uint8_t(mType), mLength, mPercent,
|
||||
mNumber, mHasPercent);
|
||||
for (const RefPtr<CalcNode>& child : mChildren) {
|
||||
aHash = child->HashValue(aHash);
|
||||
}
|
||||
return aHash;
|
||||
}
|
||||
|
||||
static nscoord
|
||||
ResolveCalcNode(const nsStyleCoord::CalcNode& aNode, nscoord aPercentageBasis)
|
||||
{
|
||||
using Type = nsStyleCoord::CalcNode::Type;
|
||||
|
||||
switch (aNode.mType) {
|
||||
case Type::Leaf:
|
||||
return aNode.mLength +
|
||||
NSToCoordFloorClamped(aPercentageBasis * aNode.mPercent);
|
||||
case Type::Add:
|
||||
MOZ_ASSERT(aNode.mChildren.Length() == 2, "unexpected child count");
|
||||
return NSCoordSaturatingAdd(
|
||||
ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis),
|
||||
ResolveCalcNode(*aNode.mChildren[1], aPercentageBasis));
|
||||
case Type::Subtract:
|
||||
MOZ_ASSERT(aNode.mChildren.Length() == 2, "unexpected child count");
|
||||
return NSCoordSaturatingSubtract(
|
||||
ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis),
|
||||
ResolveCalcNode(*aNode.mChildren[1], aPercentageBasis), 0);
|
||||
case Type::Multiply:
|
||||
MOZ_ASSERT(aNode.mChildren.Length() == 1, "unexpected child count");
|
||||
return NSCoordSaturatingMultiply(
|
||||
ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis),
|
||||
aNode.mNumber);
|
||||
case Type::Divide:
|
||||
MOZ_ASSERT(aNode.mChildren.Length() == 1, "unexpected child count");
|
||||
return NSCoordSaturatingMultiply(
|
||||
ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis),
|
||||
1.0f / aNode.mNumber);
|
||||
case Type::Min: {
|
||||
MOZ_ASSERT(!aNode.mChildren.IsEmpty(), "unexpected child count");
|
||||
nscoord result = ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis);
|
||||
for (uint32_t i = 1; i < aNode.mChildren.Length(); ++i) {
|
||||
result = std::min(result,
|
||||
ResolveCalcNode(*aNode.mChildren[i],
|
||||
aPercentageBasis));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Type::Max: {
|
||||
MOZ_ASSERT(!aNode.mChildren.IsEmpty(), "unexpected child count");
|
||||
nscoord result = ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis);
|
||||
for (uint32_t i = 1; i < aNode.mChildren.Length(); ++i) {
|
||||
result = std::max(result,
|
||||
ResolveCalcNode(*aNode.mChildren[i],
|
||||
aPercentageBasis));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Type::Clamp:
|
||||
MOZ_ASSERT(aNode.mChildren.Length() == 3, "unexpected child count");
|
||||
return std::max(ResolveCalcNode(*aNode.mChildren[0], aPercentageBasis),
|
||||
std::min(ResolveCalcNode(*aNode.mChildren[1],
|
||||
aPercentageBasis),
|
||||
ResolveCalcNode(*aNode.mChildren[2],
|
||||
aPercentageBasis)));
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("unexpected calc node type");
|
||||
return 0;
|
||||
}
|
||||
|
||||
nscoord
|
||||
nsStyleCoord::Calc::Resolve(nscoord aPercentageBasis) const
|
||||
{
|
||||
if (mNode) {
|
||||
return ResolveCalcNode(*mNode, aPercentageBasis);
|
||||
}
|
||||
return mLength + NSToCoordFloorClamped(aPercentageBasis * mPercent);
|
||||
}
|
||||
|
||||
nsStyleCoord::nsStyleCoord(nsStyleUnit aUnit)
|
||||
: mUnit(aUnit)
|
||||
@@ -71,8 +215,15 @@ bool nsStyleCoord::operator==(const nsStyleCoord& aOther) const
|
||||
case eStyleUnit_Integer:
|
||||
case eStyleUnit_Enumerated:
|
||||
return mValue.mInt == aOther.mValue.mInt;
|
||||
case eStyleUnit_Calc:
|
||||
return *this->GetCalcValue() == *aOther.GetCalcValue();
|
||||
case eStyleUnit_Calc: {
|
||||
Calc* thisCalc = GetCalcValue();
|
||||
Calc* otherCalc = aOther.GetCalcValue();
|
||||
if (thisCalc->HasCalcNode() || otherCalc->HasCalcNode()) {
|
||||
return thisCalc->HasCalcNode() == otherCalc->HasCalcNode() &&
|
||||
thisCalc->mNode->Equals(*otherCalc->mNode);
|
||||
}
|
||||
return *thisCalc == *otherCalc;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(false, "unexpected unit");
|
||||
return false;
|
||||
@@ -100,13 +251,17 @@ uint32_t nsStyleCoord::HashValue(uint32_t aHash = 0) const
|
||||
case eStyleUnit_Integer:
|
||||
case eStyleUnit_Enumerated:
|
||||
return mozilla::AddToHash(aHash, mValue.mInt);
|
||||
case eStyleUnit_Calc:
|
||||
case eStyleUnit_Calc: {
|
||||
Calc* calcValue = GetCalcValue();
|
||||
if (calcValue->HasCalcNode()) {
|
||||
return calcValue->mNode->HashValue(aHash);
|
||||
}
|
||||
aHash = mozilla::AddToHash(aHash, calcValue->mLength);
|
||||
if (HasPercent()) {
|
||||
return mozilla::AddToHash(aHash, calcValue->mPercent);
|
||||
}
|
||||
return aHash;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(false, "unexpected unit");
|
||||
return aHash;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "nsCoord.h"
|
||||
#include "nsStyleConsts.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -106,7 +108,44 @@ public:
|
||||
|
||||
// If this returns true the value is definitely zero. It it returns false
|
||||
// it might be zero. So it's best used for conservative optimization.
|
||||
bool IsDefinitelyZero() const { return mLength == 0 && mPercent == 0; }
|
||||
bool IsDefinitelyZero() const {
|
||||
return mLength == 0 && mPercent == 0 && !mHasPercent;
|
||||
}
|
||||
};
|
||||
|
||||
struct CalcNode final {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CalcNode)
|
||||
|
||||
enum class Type : uint8_t {
|
||||
Leaf,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Min,
|
||||
Max,
|
||||
Clamp
|
||||
};
|
||||
|
||||
static already_AddRefed<CalcNode> CreateLeaf(nscoord aLength,
|
||||
float aPercent,
|
||||
bool aHasPercent);
|
||||
static already_AddRefed<CalcNode> Create(Type aType);
|
||||
|
||||
bool HasPercent() const;
|
||||
bool Equals(const CalcNode& aOther) const;
|
||||
uint32_t HashValue(uint32_t aHash) const;
|
||||
|
||||
Type mType;
|
||||
nscoord mLength;
|
||||
float mPercent;
|
||||
float mNumber;
|
||||
bool mHasPercent;
|
||||
nsTArray<RefPtr<CalcNode>> mChildren;
|
||||
|
||||
private:
|
||||
explicit CalcNode(Type aType);
|
||||
~CalcNode() {}
|
||||
};
|
||||
|
||||
// Reference counted calc() value. This is the type that is used to store
|
||||
@@ -115,6 +154,11 @@ public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Calc)
|
||||
Calc() {}
|
||||
|
||||
bool HasCalcNode() const { return !!mNode; }
|
||||
nscoord Resolve(nscoord aPercentageBasis) const;
|
||||
|
||||
RefPtr<CalcNode> mNode;
|
||||
|
||||
private:
|
||||
Calc(const Calc&) = delete;
|
||||
~Calc() {}
|
||||
|
||||
@@ -1145,21 +1145,19 @@ private:
|
||||
nsCSSShadowItem mArray[1]; // This MUST be the last item
|
||||
};
|
||||
|
||||
// Border widths are rounded to the nearest integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
// Border widths are rounded down to integer pixels, but values between zero and
|
||||
// one device pixel are always rounded up to one device pixel.
|
||||
#define NS_ROUND_BORDER_TO_PIXELS(l,tpp) \
|
||||
((l) == 0) ? 0 : std::max((tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp))
|
||||
(((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp)))
|
||||
// Caret widths are rounded to the nearest-below integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
#define NS_ROUND_CARET_TO_PIXELS(l,tpp) \
|
||||
((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
|
||||
// Outline offset is rounded to the nearest integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
// Note that the offset can be negative.
|
||||
NS_ROUND_BORDER_TO_PIXELS(l,tpp)
|
||||
// Outline offset is snapped like border widths, preserving the sign.
|
||||
#define NS_ROUND_OFFSET_TO_PIXELS(l,tpp) \
|
||||
(((l) == 0) ? 0 : \
|
||||
((l) > 0) ? std::max( (tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp)) : \
|
||||
std::min(-(tpp), ((l) - ((tpp) / 2)) / (tpp) * (tpp)))
|
||||
(((l) > 0) ? NS_ROUND_BORDER_TO_PIXELS((l), (tpp)) : \
|
||||
std::min(-(tpp), (l) / (tpp) * (tpp))))
|
||||
|
||||
// Returns if the given border style type is visible or not
|
||||
static bool IsVisibleBorderStyle(uint8_t aStyle)
|
||||
|
||||
@@ -105,7 +105,6 @@ const char *gInaccessibleProperties[] = {
|
||||
"-x-span",
|
||||
"-x-system-font",
|
||||
"-x-text-zoom",
|
||||
"aspect-ratio", // for now.
|
||||
"-moz-control-character-visibility",
|
||||
"-moz-script-level", // parsed by UA sheets only
|
||||
"-moz-script-size-multiplier",
|
||||
|
||||
@@ -70,12 +70,15 @@ support-files = file_animations_with_disabled_properties.html
|
||||
[test_any_dynamic.html]
|
||||
[test_at_rule_parse_serialize.html]
|
||||
[test_attribute_selector_eof_behavior.html]
|
||||
[test_aspect_ratio_property.html]
|
||||
[test_background_blend_mode.html]
|
||||
[test_basic_nesting_flattening.html]
|
||||
[test_nesting_flattening_parser_edges.html]
|
||||
[test_nesting_flattening_recovery.html]
|
||||
[test_box_size_keywords.html]
|
||||
[test_border_width_rounding.html]
|
||||
[test_bug73586.html]
|
||||
[test_css_math_functions.html]
|
||||
[test_bug74880.html]
|
||||
[test_bug98997.html]
|
||||
[test_bug160403.html]
|
||||
@@ -156,6 +159,8 @@ support-files = file_bug1089417_iframe.html
|
||||
[test_clip-path_polygon.html]
|
||||
[test_compute_data_with_start_struct.html]
|
||||
[test_computed_style.html]
|
||||
[test_calc_numeric_types.html]
|
||||
prefs = layout.css.filters.enabled=true
|
||||
[test_computed_style_min_size_auto.html]
|
||||
[test_computed_style_no_pseudo.html]
|
||||
[test_computed_style_prefs.html]
|
||||
@@ -211,6 +216,7 @@ support-files =
|
||||
[test_initial_storage.html]
|
||||
[test_keyframes_rules.html]
|
||||
[test_load_events_on_stylesheets.html]
|
||||
[test_logical_border_radius.html]
|
||||
[test_logical_properties.html]
|
||||
[test_media_queries.html]
|
||||
[test_media_queries_dynamic.html]
|
||||
|
||||
@@ -122,6 +122,8 @@ var validGradientAndElementValues = [
|
||||
"linear-gradient(10deg, red, blue)",
|
||||
"linear-gradient(1turn, red, blue)",
|
||||
"linear-gradient(.414rad, red, blue)",
|
||||
"linear-gradient(calc(90deg / 2), red, blue)",
|
||||
"linear-gradient(calc(calc(0.25turn) + 45deg), red, blue)",
|
||||
"linear-gradient(90deg in srgb, yellow, purple)",
|
||||
"linear-gradient(90deg in hsl, yellow, purple)",
|
||||
"linear-gradient(90deg in lch, yellow, purple)",
|
||||
@@ -1005,7 +1007,8 @@ var gCSSProperties = {
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["0s", "0ms"],
|
||||
other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
|
||||
other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s",
|
||||
"calc(250ms - 0.5s)", "calc(calc(500ms) - 250ms)"],
|
||||
invalid_values: ["0", "0px"],
|
||||
},
|
||||
"animation-direction": {
|
||||
@@ -1030,7 +1033,8 @@ var gCSSProperties = {
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["0s", "0ms"],
|
||||
other_values: ["1s", "250ms", "1s, 250ms, 2.3s"],
|
||||
other_values: ["1s", "250ms", "1s, 250ms, 2.3s",
|
||||
"calc(calc(0.25s) + 250ms)"],
|
||||
invalid_values: ["0", "0px", "-1ms", "-2s"],
|
||||
},
|
||||
"animation-fill-mode": {
|
||||
@@ -1163,6 +1167,14 @@ var gCSSProperties = {
|
||||
other_values: ["url(foo.xml)"],
|
||||
invalid_values: [],
|
||||
},
|
||||
"aspect-ratio": {
|
||||
domProp: "aspectRatio",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["auto"],
|
||||
other_values: ["1", "2"],
|
||||
invalid_values: ["none", "-1", "1px", "1 / -1", "1 /", "auto auto"],
|
||||
},
|
||||
"-moz-border-bottom-colors": {
|
||||
domProp: "MozBorderBottomColors",
|
||||
inherited: false,
|
||||
@@ -1643,6 +1655,154 @@ var gCSSProperties = {
|
||||
"2px calc(0px + rubbish)",
|
||||
],
|
||||
},
|
||||
"border-end-end-radius": {
|
||||
domProp: "borderEndEndRadius",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
logical: true,
|
||||
get_computed: logical_corner_prop_get_computed,
|
||||
prerequisites: { width: "200px", height: "100px", display: "inline-block" },
|
||||
initial_values: ["0", "0px", "calc(-2px)"],
|
||||
other_values: [
|
||||
"0%",
|
||||
"3%",
|
||||
"1px",
|
||||
"2em", // circular
|
||||
"3% 2%",
|
||||
"1px 4px",
|
||||
"2em 2pt", // elliptical
|
||||
"calc(-1%)",
|
||||
"calc(2px)",
|
||||
"calc(50%)",
|
||||
"calc(3*25px)",
|
||||
"calc(3*25px) 5px",
|
||||
"5px calc(3*25px)",
|
||||
"calc(20%) calc(3*25px)",
|
||||
"calc(25px*3)",
|
||||
"calc(3*25px + 50%)",
|
||||
],
|
||||
invalid_values: [
|
||||
"-1px",
|
||||
"4px -2px",
|
||||
"inherit 2px",
|
||||
"2px inherit",
|
||||
"2",
|
||||
"2px 2",
|
||||
"2 2px",
|
||||
"2px calc(0px + rubbish)",
|
||||
],
|
||||
},
|
||||
"border-end-start-radius": {
|
||||
domProp: "borderEndStartRadius",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
logical: true,
|
||||
get_computed: logical_corner_prop_get_computed,
|
||||
prerequisites: { width: "200px", height: "100px", display: "inline-block" },
|
||||
initial_values: ["0", "0px", "calc(-2px)"],
|
||||
other_values: [
|
||||
"0%",
|
||||
"3%",
|
||||
"1px",
|
||||
"2em", // circular
|
||||
"3% 2%",
|
||||
"1px 4px",
|
||||
"2em 2pt", // elliptical
|
||||
"calc(-1%)",
|
||||
"calc(2px)",
|
||||
"calc(50%)",
|
||||
"calc(3*25px)",
|
||||
"calc(3*25px) 5px",
|
||||
"5px calc(3*25px)",
|
||||
"calc(20%) calc(3*25px)",
|
||||
"calc(25px*3)",
|
||||
"calc(3*25px + 50%)",
|
||||
],
|
||||
invalid_values: [
|
||||
"-1px",
|
||||
"4px -2px",
|
||||
"inherit 2px",
|
||||
"2px inherit",
|
||||
"2",
|
||||
"2px 2",
|
||||
"2 2px",
|
||||
"2px calc(0px + rubbish)",
|
||||
],
|
||||
},
|
||||
"border-start-end-radius": {
|
||||
domProp: "borderStartEndRadius",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
logical: true,
|
||||
get_computed: logical_corner_prop_get_computed,
|
||||
prerequisites: { width: "200px", height: "100px", display: "inline-block" },
|
||||
initial_values: ["0", "0px", "calc(-2px)"],
|
||||
other_values: [
|
||||
"0%",
|
||||
"3%",
|
||||
"1px",
|
||||
"2em", // circular
|
||||
"3% 2%",
|
||||
"1px 4px",
|
||||
"2em 2pt", // elliptical
|
||||
"calc(-1%)",
|
||||
"calc(2px)",
|
||||
"calc(50%)",
|
||||
"calc(3*25px)",
|
||||
"calc(3*25px) 5px",
|
||||
"5px calc(3*25px)",
|
||||
"calc(20%) calc(3*25px)",
|
||||
"calc(25px*3)",
|
||||
"calc(3*25px + 50%)",
|
||||
],
|
||||
invalid_values: [
|
||||
"-1px",
|
||||
"4px -2px",
|
||||
"inherit 2px",
|
||||
"2px inherit",
|
||||
"2",
|
||||
"2px 2",
|
||||
"2 2px",
|
||||
"2px calc(0px + rubbish)",
|
||||
],
|
||||
},
|
||||
"border-start-start-radius": {
|
||||
domProp: "borderStartStartRadius",
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
logical: true,
|
||||
get_computed: logical_corner_prop_get_computed,
|
||||
prerequisites: { width: "200px", height: "100px", display: "inline-block" },
|
||||
initial_values: ["0", "0px", "calc(-2px)"],
|
||||
other_values: [
|
||||
"0%",
|
||||
"3%",
|
||||
"1px",
|
||||
"2em", // circular
|
||||
"3% 2%",
|
||||
"1px 4px",
|
||||
"2em 2pt", // elliptical
|
||||
"calc(-1%)",
|
||||
"calc(2px)",
|
||||
"calc(50%)",
|
||||
"calc(3*25px)",
|
||||
"calc(3*25px) 5px",
|
||||
"5px calc(3*25px)",
|
||||
"calc(20%) calc(3*25px)",
|
||||
"calc(25px*3)",
|
||||
"calc(3*25px + 50%)",
|
||||
],
|
||||
invalid_values: [
|
||||
"-1px",
|
||||
"4px -2px",
|
||||
"inherit 2px",
|
||||
"2px inherit",
|
||||
"2",
|
||||
"2px 2",
|
||||
"2 2px",
|
||||
"2px calc(0px + rubbish)",
|
||||
],
|
||||
},
|
||||
"-moz-border-right-colors": {
|
||||
domProp: "MozBorderRightColors",
|
||||
inherited: false,
|
||||
@@ -2854,6 +3014,9 @@ var gCSSProperties = {
|
||||
"translate(calc(5px - 10% * 3))",
|
||||
"translate(calc(5px - 3 * 10%), 50px)",
|
||||
"translate(-50px, calc(5px - 10% * 3))",
|
||||
"rotate(calc(45deg + 45deg))",
|
||||
"rotate(calc(calc(0.125turn) + 45deg))",
|
||||
"scale(calc(1 + 0.5))",
|
||||
"translatez(1px)",
|
||||
"translatez(4em)",
|
||||
"translatez(-4px)",
|
||||
@@ -4325,6 +4488,10 @@ var gCSSProperties = {
|
||||
"oklab(100% 0 0)",
|
||||
"oklab(60% 0.1 -0.1 / 75%)",
|
||||
"oklch(70% 0.2 180deg / 40%)",
|
||||
"color-mix(in oklab, red, blue)",
|
||||
"color-mix(in oklch, red, blue)",
|
||||
"color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))",
|
||||
"color-mix(in oklch, oklch(0.1 0.2 30), oklch(0.5 0.6 70))",
|
||||
],
|
||||
invalid_values: [
|
||||
"#f",
|
||||
@@ -5572,7 +5739,8 @@ var gCSSProperties = {
|
||||
"3e+0",
|
||||
"3e-0",
|
||||
],
|
||||
other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"],
|
||||
other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%",
|
||||
"calc(25% * 2)", "calc(calc(0.25) + 0.25)"],
|
||||
invalid_values: ["0px", "1px"],
|
||||
},
|
||||
"-moz-orient": {
|
||||
@@ -6401,7 +6569,8 @@ var gCSSProperties = {
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["0s", "0ms"],
|
||||
other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
|
||||
other_values: ["1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s",
|
||||
"calc(250ms - 0.5s)", "calc(calc(500ms) - 250ms)"],
|
||||
invalid_values: ["0", "0px"],
|
||||
},
|
||||
"transition-duration": {
|
||||
@@ -6409,7 +6578,8 @@ var gCSSProperties = {
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["0s", "0ms"],
|
||||
other_values: ["1s", "250ms", "1s, 250ms, 2.3s"],
|
||||
other_values: ["1s", "250ms", "1s, 250ms, 2.3s",
|
||||
"calc(calc(0.25s) + 250ms)"],
|
||||
invalid_values: ["0", "0px", "-1ms", "-2s"],
|
||||
},
|
||||
"transition-property": {
|
||||
@@ -6773,7 +6943,7 @@ var gCSSProperties = {
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
/* XXX requires position */
|
||||
initial_values: ["auto"],
|
||||
other_values: ["0", "3", "-7000", "12000"],
|
||||
other_values: ["0", "3", "-7000", "12000", "calc(2.5)"],
|
||||
invalid_values: ["3.0", "17.5", "3e1"],
|
||||
},
|
||||
"clip-path": {
|
||||
@@ -7633,7 +7803,8 @@ var gCSSProperties = {
|
||||
inherited: false,
|
||||
type: CSS_TYPE_LONGHAND,
|
||||
initial_values: ["0"],
|
||||
other_values: ["1", "99999", "-1", "-50"],
|
||||
other_values: ["1", "99999", "-1", "-50", "calc(1.5)",
|
||||
"calc(calc(-2) + 1)"],
|
||||
invalid_values: ["0px", "1.0", "1.", "1%", "0.2", "3em", "stretch"],
|
||||
},
|
||||
|
||||
@@ -8880,6 +9051,54 @@ function logical_box_prop_get_computed(cs, property) {
|
||||
return cs.getPropertyValue(property);
|
||||
}
|
||||
|
||||
function logical_corner_prop_get_computed(cs, property) {
|
||||
// Use default for writing-mode in case the vertical text
|
||||
// pref (which it lives behind) is turned off.
|
||||
var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb";
|
||||
var direction = cs.getPropertyValue("direction");
|
||||
|
||||
var blockMappings = {
|
||||
"horizontal-tb": { start: "top", end: "bottom" },
|
||||
"vertical-rl": { start: "right", end: "left" },
|
||||
"vertical-lr": { start: "left", end: "right" },
|
||||
"sideways-rl": { start: "right", end: "left" },
|
||||
"sideways-lr": { start: "left", end: "right" },
|
||||
};
|
||||
|
||||
var inlineMappings = {
|
||||
"horizontal-tb ltr": { start: "left", end: "right" },
|
||||
"horizontal-tb rtl": { start: "right", end: "left" },
|
||||
"vertical-.. ltr": { start: "top", end: "bottom" },
|
||||
"vertical-.. rtl": { start: "bottom", end: "top" },
|
||||
"sideways-lr ltr": { start: "bottom", end: "top" },
|
||||
"sideways-lr rtl": { start: "top", end: "bottom" },
|
||||
"sideways-rl ltr": { start: "top", end: "bottom" },
|
||||
"sideways-rl rtl": { start: "bottom", end: "top" },
|
||||
};
|
||||
|
||||
var blockMapping = blockMappings[writingMode];
|
||||
var inlineMapping;
|
||||
|
||||
var key = `${writingMode} ${direction}`;
|
||||
for (var k in inlineMappings) {
|
||||
if (new RegExp(k).test(key)) {
|
||||
inlineMapping = inlineMappings[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var match = /^border-(start|end)-(start|end)-radius$/.exec(property);
|
||||
if (!match || !blockMapping || !inlineMapping) {
|
||||
throw "Unexpected logical corner property";
|
||||
}
|
||||
|
||||
var blockSide = blockMapping[match[1]];
|
||||
var inlineSide = inlineMapping[match[2]];
|
||||
var verticalSide = /^(top|bottom)$/.test(blockSide) ? blockSide : inlineSide;
|
||||
var horizontalSide = /^(left|right)$/.test(blockSide) ? blockSide : inlineSide;
|
||||
return cs.getPropertyValue(`border-${verticalSide}-${horizontalSide}-radius`);
|
||||
}
|
||||
|
||||
// Get the computed value for a property. For shorthands, return the
|
||||
// computed values of all the subproperties, delimited by " ; ".
|
||||
function get_computed_value(cs, property) {
|
||||
@@ -9293,6 +9512,8 @@ if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
|
||||
"brightness(2)",
|
||||
"brightness(350%)",
|
||||
"brightness(4.567)",
|
||||
"brightness(calc(25% * 2))",
|
||||
"brightness(calc(calc(0.25) + 0.25))",
|
||||
|
||||
"contrast(0)",
|
||||
"contrast(50%)",
|
||||
@@ -9337,6 +9558,7 @@ if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) {
|
||||
"hue-rotate(-1.6rad)",
|
||||
"hue-rotate(0.5turn)",
|
||||
"hue-rotate(-2turn)",
|
||||
"hue-rotate(calc(90deg + 0.125turn))",
|
||||
|
||||
"invert(0)",
|
||||
"invert(50%)",
|
||||
@@ -11529,6 +11751,22 @@ if (IsCSSPropertyPrefEnabled("layout.css.unset-value.enabled")) {
|
||||
"unset 2px",
|
||||
"2px unset",
|
||||
);
|
||||
gCSSProperties["border-end-end-radius"].invalid_values.push(
|
||||
"unset 2px",
|
||||
"2px unset",
|
||||
);
|
||||
gCSSProperties["border-end-start-radius"].invalid_values.push(
|
||||
"unset 2px",
|
||||
"2px unset",
|
||||
);
|
||||
gCSSProperties["border-start-end-radius"].invalid_values.push(
|
||||
"unset 2px",
|
||||
"2px unset",
|
||||
);
|
||||
gCSSProperties["border-start-start-radius"].invalid_values.push(
|
||||
"unset 2px",
|
||||
"2px unset",
|
||||
);
|
||||
gCSSProperties["-moz-border-right-colors"].invalid_values.push(
|
||||
"red unset",
|
||||
"unset red",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test CSS aspect-ratio property</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" style="position:absolute; left:0; top:0; width:120px;">
|
||||
<div id="ratio" style="width:120px; aspect-ratio:3 / 2;"></div>
|
||||
<div id="wrapper" style="position:relative; width:120px; aspect-ratio:1 / 1;">
|
||||
<div style="position:absolute; inset:0;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
ok(CSS.supports("aspect-ratio", "1 / 1"),
|
||||
"aspect-ratio accepts ratio syntax");
|
||||
ok(CSS.supports("aspect-ratio", "auto"),
|
||||
"aspect-ratio accepts auto");
|
||||
ok(CSS.supports("aspect-ratio", "auto 16 / 9"),
|
||||
"aspect-ratio accepts auto plus ratio");
|
||||
ok(!CSS.supports("aspect-ratio", "1px"),
|
||||
"aspect-ratio rejects lengths");
|
||||
|
||||
var ratio = document.getElementById("ratio");
|
||||
is(ratio.getBoundingClientRect().height, 80,
|
||||
"aspect-ratio computes an automatic block size from inline size");
|
||||
|
||||
var wrapper = document.getElementById("wrapper");
|
||||
is(wrapper.getBoundingClientRect().height, 120,
|
||||
"aspect-ratio gives an empty wrapper with absolute children a block size");
|
||||
|
||||
is(getComputedStyle(wrapper).aspectRatio, "1 / 1",
|
||||
"computed aspect-ratio exposes a ratio value");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test fractional border and outline width rounding</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var display = document.getElementById("display");
|
||||
|
||||
var tests = [
|
||||
["0px", "0px"],
|
||||
["0.1px", "1px"],
|
||||
["0.25px", "1px"],
|
||||
["0.5px", "1px"],
|
||||
["0.9px", "1px"],
|
||||
["1px", "1px"],
|
||||
["1.25px", "1px"],
|
||||
["1.5px", "1px"],
|
||||
["2px", "2px"],
|
||||
["2.75px", "2px"],
|
||||
["2.999px", "2px"],
|
||||
["3px", "3px"],
|
||||
["3.001px", "3px"],
|
||||
];
|
||||
|
||||
function checkWidth(property, setupProperty, setupValue) {
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[setupProperty] = setupValue;
|
||||
div.style[property] = specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes " + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function checkOffset(property) {
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[property] = specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes " + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[0] == "0px" ? "0px" : "-" + test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[property] = "-" + specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes -" + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
checkWidth("borderWidth", "borderStyle", "solid");
|
||||
checkWidth("outlineWidth", "outlineStyle", "solid");
|
||||
checkOffset("outlineOffset");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test calc() numeric type support</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function appendTestNode(tag = "div") {
|
||||
const node = document.createElement(tag);
|
||||
document.getElementById("display").appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function parse2dMatrix(transformValue) {
|
||||
const match = /^matrix\(([^)]+)\)$/.exec(transformValue);
|
||||
ok(match, `expected a 2d matrix, got "${transformValue}"`);
|
||||
return match[1].split(",").map(value => parseFloat(value.trim()));
|
||||
}
|
||||
|
||||
(function testNestedCalcLengthPercentage() {
|
||||
const container = appendTestNode();
|
||||
container.style.width = "200px";
|
||||
container.style.position = "absolute";
|
||||
|
||||
const child = document.createElement("div");
|
||||
child.style.width = "calc(calc(50% + 10px) - calc(5px + 10%))";
|
||||
container.appendChild(child);
|
||||
|
||||
is(getComputedStyle(child).width, "85px",
|
||||
"nested calc() should resolve length-percentage expressions correctly");
|
||||
|
||||
container.remove();
|
||||
})();
|
||||
|
||||
(function testTimeCalc() {
|
||||
const div = appendTestNode();
|
||||
div.style.transitionDuration = "calc(calc(0.25s) + 250ms)";
|
||||
div.style.animationDelay = "calc(250ms - 0.5s)";
|
||||
|
||||
is(getComputedStyle(div).transitionDuration, "0.5s",
|
||||
"transition-duration should accept calc() time values");
|
||||
is(getComputedStyle(div).animationDelay, "-0.25s",
|
||||
"animation-delay should accept calc() time values");
|
||||
|
||||
div.remove();
|
||||
})();
|
||||
|
||||
(function testNumericAndIntegerCalc() {
|
||||
const div = appendTestNode();
|
||||
div.style.opacity = "calc(25% * 2)";
|
||||
div.style.order = "calc(1.5)";
|
||||
div.style.zIndex = "calc(2.5)";
|
||||
|
||||
is(getComputedStyle(div).opacity, "0.5",
|
||||
"opacity should accept calc() number/percent values");
|
||||
is(getComputedStyle(div).order, "2",
|
||||
"order should round calc() results to the nearest integer");
|
||||
is(getComputedStyle(div).zIndex, "3",
|
||||
"z-index should round calc() results to the nearest integer");
|
||||
|
||||
div.remove();
|
||||
})();
|
||||
|
||||
(function testAngleAndNumberCalcInFunctions() {
|
||||
const div = appendTestNode();
|
||||
div.style.transform = "rotate(calc(45deg + 45deg)) scale(calc(1 + 0.5))";
|
||||
div.style.filter = "hue-rotate(calc(90deg + 0.125turn)) brightness(calc(0.25 + 0.25))";
|
||||
div.style.backgroundImage = "linear-gradient(calc(90deg / 2), red, blue)";
|
||||
|
||||
ok(div.style.filter !== "",
|
||||
"filter should accept calc() in angle and number function arguments");
|
||||
ok(div.style.backgroundImage !== "",
|
||||
"gradients should accept calc() in angle arguments");
|
||||
|
||||
const matrix = parse2dMatrix(getComputedStyle(div).transform);
|
||||
ok(Math.abs(matrix[0]) < 1e-6, "rotate(calc()) should zero out the xx entry");
|
||||
ok(Math.abs(matrix[1] - 1.5) < 1e-6, "scale(calc()) should affect the xy entry");
|
||||
ok(Math.abs(matrix[2] + 1.5) < 1e-6, "scale(calc()) should affect the yx entry");
|
||||
ok(Math.abs(matrix[3]) < 1e-6, "rotate(calc()) should zero out the yy entry");
|
||||
|
||||
div.remove();
|
||||
})();
|
||||
|
||||
(function testNestedCalcSpecifiedSerialization() {
|
||||
const div = appendTestNode();
|
||||
const cases = [
|
||||
["left", "calc(calc(20px) + calc(80px))", "calc(100px)"],
|
||||
["left", "calc(calc(2) * calc(50px))", "calc(100px)"],
|
||||
["left", "calc(calc(150px * 2 / 3))", "calc(100px)"],
|
||||
["left", "calc(50px + calc(40%))", "calc(40% + 50px)"],
|
||||
["border", "calc(calc(10px)) solid pink", "calc(10px) solid pink"],
|
||||
];
|
||||
|
||||
for (const [property, input, expected] of cases) {
|
||||
div.style.setProperty(property, input, "");
|
||||
is(div.style.getPropertyValue(property), expected,
|
||||
`${property} should canonicalize nested calc() serialization`);
|
||||
}
|
||||
|
||||
div.remove();
|
||||
})();
|
||||
|
||||
(function testSpecialNumberCalcSpecifiedSerialization() {
|
||||
const div = appendTestNode();
|
||||
const cases = [
|
||||
["calc(NaN)", "calc(NaN)"],
|
||||
["calc(infinity)", "calc(infinity)"],
|
||||
["calc(-infinity)", "calc(-infinity)"],
|
||||
["calc(1 * NaN)", "calc(NaN)"],
|
||||
["calc(1 * infinity / infinity)", "calc(NaN)"],
|
||||
["calc(1 * 0 * infinity)", "calc(NaN)"],
|
||||
["calc(1 * (infinity + -infinity))", "calc(NaN)"],
|
||||
["calc(1 * (infinity - infinity))", "calc(NaN)"],
|
||||
["calc(1 * infinity)", "calc(infinity)"],
|
||||
["calc(1 * -infinity)", "calc(-infinity)"],
|
||||
["calc(1 * 1/infinity)", "calc(0)"],
|
||||
["calc(1 * infinity * infinity)", "calc(infinity)"],
|
||||
["calc(1 * max(INFinity*3, 0))", "calc(infinity)"],
|
||||
["calc(1 * min(inFInity*4, 0))", "calc(0)"],
|
||||
["calc(1 * max(nAn*2, 0))", "calc(NaN)"],
|
||||
["calc(1 * clamp(-INFINITY*20, 0, infiniTY*10))", "calc(0)"],
|
||||
["calc(1 * max(NaN, min(0,10)))", "calc(NaN)"],
|
||||
["calc(1 * clamp(NaN, 0, 10))", "calc(NaN)"],
|
||||
["calc(1 * max(0, min(10, NaN)))", "calc(NaN)"],
|
||||
["calc(1 * clamp(0, 10, NaN))", "calc(NaN)"],
|
||||
["calc(1 * max(0, min(NaN, 10)))", "calc(NaN)"],
|
||||
["calc(1 * clamp(0, NaN, 10))", "calc(NaN)"],
|
||||
["calc(1 * clamp(-Infinity, 0, infinity))", "calc(0)"],
|
||||
["calc(1 * clamp(-inFinity, infinity, 10))", "calc(10)"],
|
||||
];
|
||||
|
||||
for (const [input, expected] of cases) {
|
||||
div.style.setProperty("opacity", input, "");
|
||||
is(div.style.getPropertyValue("opacity"), expected,
|
||||
`opacity should serialize ${input} as ${expected}`);
|
||||
}
|
||||
|
||||
div.remove();
|
||||
})();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -406,14 +406,21 @@ var noframe_container = document.getElementById("content");
|
||||
["lch(150% 50 60 / 1)", "rgb(255, 255, 255)"],
|
||||
["oklab(0 0 0 / 1)", "rgb(0, 0, 0)"],
|
||||
["oklab(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
|
||||
["oklab(150% 0.5 0.2 / 1)", "rgb(255, 0, 0)"],
|
||||
["oklab(150% 0.5 0.2 / 1)", "rgb(255, 255, 255)"],
|
||||
["oklch(0 0 0 / 1)", "rgb(0, 0, 0)"],
|
||||
["oklch(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"],
|
||||
["oklch(0.0001% 0.2 45 / 1)", "rgb(0, 0, 0)"],
|
||||
["oklch(99.9999% 0.2 45 / 1)", "rgb(255, 207, 132)"],
|
||||
["oklch(99.9999% 0.2 45 / 1)", "rgb(255, 251, 242)"],
|
||||
["oklch(0% 1.1 60 / 1)", "rgb(0, 0, 0)"],
|
||||
["oklch(100% 110 60 / 1)", "rgb(0, 255, 0)"],
|
||||
["oklch(150% 0.5 50 / 1)", "rgb(255, 0, 0)"],
|
||||
["oklch(100% 110 60 / 1)", "rgb(255, 255, 255)"],
|
||||
["oklch(150% 0.5 50 / 1)", "rgb(255, 255, 255)"],
|
||||
["color-mix(in oklab, red, blue)", "rgb(140, 83, 162)"],
|
||||
["color-mix(in oklch, red, blue)", "rgb(183, 0, 190)"],
|
||||
["color-mix(in oklab, red, transparent)", "rgba(255, 0, 0, 0.5)"],
|
||||
["color-mix(in srgb, red, transparent calc(100% - 100%*1))", "rgb(255, 0, 0)"],
|
||||
["color-mix(in srgb, red, transparent calc(50%))", "rgba(255, 0, 0, 0.5)"],
|
||||
["color-mix(in oklab, oklab(0.1 0.2 0.3), oklab(0.5 0.6 0.7))", "rgb(83, 24, 0)"],
|
||||
["color-mix(in oklch, oklch(0.1 0.2 30), oklch(0.5 0.6 70))", "rgb(84, 23, 0)"],
|
||||
];
|
||||
|
||||
var p = document.createElement("p");
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test CSS sizing math functions</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="narrow" style="position:absolute; left:0; top:0; width:200px;">
|
||||
<div id="min-fill" style="height:10px; width:min(500px, 100%);"></div>
|
||||
<div id="max-half" style="height:10px; width:max(75px, 50%);"></div>
|
||||
<div id="clamp-half" style="height:10px; width:clamp(80px, 50%, 120px);"></div>
|
||||
<div id="nested" style="height:10px; width:calc(min(500px, 100%) - 20px);"></div>
|
||||
</div>
|
||||
<div id="wide" style="position:absolute; left:0; top:100px; width:700px;">
|
||||
<div id="min-cap" style="height:10px; width:min(500px, 100%);"></div>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
ok(CSS.supports("width", "min(500px, 100%)"),
|
||||
"width accepts min() with length and percentage");
|
||||
ok(CSS.supports("width", "max(75px, 50%)"),
|
||||
"width accepts max() with length and percentage");
|
||||
ok(CSS.supports("width", "clamp(80px, 50%, 120px)"),
|
||||
"width accepts clamp() with length and percentage");
|
||||
|
||||
var probe = document.createElement("div");
|
||||
probe.style.width = "min(500px, 100%)";
|
||||
is(probe.style.width, "min(500px, 100%)",
|
||||
"specified min() serializes without being dropped");
|
||||
|
||||
is(document.getElementById("min-fill").getBoundingClientRect().width, 200,
|
||||
"min() resolves percentage against a narrow containing block");
|
||||
is(document.getElementById("min-cap").getBoundingClientRect().width, 500,
|
||||
"min() caps width when the containing block is wider than the length");
|
||||
is(document.getElementById("max-half").getBoundingClientRect().width, 100,
|
||||
"max() resolves mixed length and percentage values");
|
||||
is(document.getElementById("clamp-half").getBoundingClientRect().width, 100,
|
||||
"clamp() resolves mixed length and percentage values");
|
||||
is(document.getElementById("nested").getBoundingClientRect().width, 180,
|
||||
"math functions can participate in calc() expressions");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Test logical border radius properties</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<div id="test"></div>
|
||||
|
||||
<script>
|
||||
var gTest = document.getElementById("test");
|
||||
|
||||
[
|
||||
"border-start-start-radius",
|
||||
"border-start-end-radius",
|
||||
"border-end-start-radius",
|
||||
"border-end-end-radius",
|
||||
].forEach(function(property) {
|
||||
ok(CSS.supports(property, "6px"), `${property} parses`);
|
||||
ok(CSS.supports(property, "var(--input-corner-radius)"),
|
||||
`${property} accepts variables`);
|
||||
ok(!CSS.supports(property, "-1px"), `${property} rejects negative lengths`);
|
||||
});
|
||||
|
||||
function reset(style) {
|
||||
gTest.setAttribute("style",
|
||||
"width: 200px; height: 100px; display: block; " +
|
||||
"border-radius: 0; " + style);
|
||||
return getComputedStyle(gTest);
|
||||
}
|
||||
|
||||
function checkMapped(name, writingMode, expected) {
|
||||
var cs = reset(
|
||||
writingMode +
|
||||
"border-start-start-radius: 3px; " +
|
||||
"border-start-end-radius: 5px; " +
|
||||
"border-end-end-radius: 7px; " +
|
||||
"border-end-start-radius: 11px; ");
|
||||
|
||||
is(cs.borderTopLeftRadius, expected.topLeft, `${name}: top left`);
|
||||
is(cs.borderTopRightRadius, expected.topRight, `${name}: top right`);
|
||||
is(cs.borderBottomRightRadius, expected.bottomRight, `${name}: bottom right`);
|
||||
is(cs.borderBottomLeftRadius, expected.bottomLeft, `${name}: bottom left`);
|
||||
}
|
||||
|
||||
checkMapped("horizontal ltr",
|
||||
"writing-mode: horizontal-tb; direction: ltr; ",
|
||||
{ topLeft: "3px", topRight: "5px",
|
||||
bottomRight: "7px", bottomLeft: "11px" });
|
||||
checkMapped("horizontal rtl",
|
||||
"writing-mode: horizontal-tb; direction: rtl; ",
|
||||
{ topLeft: "5px", topRight: "3px",
|
||||
bottomRight: "11px", bottomLeft: "7px" });
|
||||
checkMapped("vertical-rl ltr",
|
||||
"writing-mode: vertical-rl; direction: ltr; ",
|
||||
{ topLeft: "11px", topRight: "3px",
|
||||
bottomRight: "5px", bottomLeft: "7px" });
|
||||
checkMapped("vertical-lr ltr",
|
||||
"writing-mode: vertical-lr; direction: ltr; ",
|
||||
{ topLeft: "3px", topRight: "11px",
|
||||
bottomRight: "7px", bottomLeft: "5px" });
|
||||
|
||||
var cs = reset("writing-mode: horizontal-tb; direction: ltr; " +
|
||||
"--input-corner-radius: 6px; " +
|
||||
"border-start-start-radius: var(--input-corner-radius); ");
|
||||
is(cs.borderTopLeftRadius, "6px", "logical radius resolves CSS variables");
|
||||
|
||||
cs = reset("writing-mode: horizontal-tb; direction: ltr; " +
|
||||
"border-top-left-radius: 1px; " +
|
||||
"border-start-start-radius: 6px; ");
|
||||
is(cs.borderTopLeftRadius, "6px", "logical corner wins later in declaration");
|
||||
|
||||
cs = reset("writing-mode: horizontal-tb; direction: ltr; " +
|
||||
"border-start-start-radius: 6px; " +
|
||||
"border-top-left-radius: 1px; ");
|
||||
is(cs.borderTopLeftRadius, "1px", "physical corner wins later in declaration");
|
||||
</script>
|
||||
@@ -237,6 +237,7 @@ function run() {
|
||||
expression_should_be_parseable(feature + ": 0");
|
||||
expression_should_be_parseable(feature + ": 0px");
|
||||
expression_should_be_parseable(feature + ": 0em");
|
||||
expression_should_be_parseable(feature + ": calc(0px + 0em)");
|
||||
expression_should_be_parseable(feature + ": -0");
|
||||
expression_should_be_parseable("min-" + feature + ": -0");
|
||||
expression_should_be_parseable("max-" + feature + ": -0");
|
||||
@@ -244,6 +245,7 @@ function run() {
|
||||
expression_should_be_parseable(feature + ": 1px");
|
||||
expression_should_be_parseable(feature + ": 0.001mm");
|
||||
expression_should_be_parseable(feature + ": 100000px");
|
||||
expression_should_not_be_parseable(feature + ": calc(1px + 1%)");
|
||||
expression_should_not_be_parseable(feature + ": -1px");
|
||||
expression_should_not_be_parseable("min-" + feature + ": -1px");
|
||||
expression_should_not_be_parseable("max-" + feature + ": -1px");
|
||||
@@ -263,8 +265,9 @@ function run() {
|
||||
|
||||
var content_div = document.getElementById("content");
|
||||
content_div.style.font = "initial";
|
||||
var em_size =
|
||||
getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
|
||||
var em_size = parseFloat(
|
||||
getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1]
|
||||
);
|
||||
|
||||
// in this test, assume the common underlying implementation is correct
|
||||
var width_val = 117; // pick two not-too-round numbers
|
||||
@@ -282,7 +285,11 @@ function run() {
|
||||
for (feature in features) {
|
||||
var value = features[feature];
|
||||
should_apply("all and (" + feature + ": " + value + "px)");
|
||||
should_apply("all and (" + feature + ": calc(" +
|
||||
(value - em_size) + "px + 1em))");
|
||||
should_not_apply("all and (" + feature + ": " + (value + 1) + "px)");
|
||||
should_not_apply("all and (" + feature + ": calc(" +
|
||||
(value - em_size + 1) + "px + 1em))");
|
||||
should_not_apply("all and (" + feature + ": " + (value - 1) + "px)");
|
||||
should_apply("all and (min-" + feature + ": " + value + "px)");
|
||||
should_not_apply("all and (min-" + feature + ": " + (value + 1) + "px)");
|
||||
@@ -290,6 +297,12 @@ function run() {
|
||||
should_apply("all and (max-" + feature + ": " + value + "px)");
|
||||
should_apply("all and (max-" + feature + ": " + (value + 1) + "px)");
|
||||
should_not_apply("all and (max-" + feature + ": " + (value - 1) + "px)");
|
||||
should_apply("all and (" + feature + " >= " + value + "px)");
|
||||
should_not_apply("all and (" + feature + " > " + value + "px)");
|
||||
should_apply("all and (" + feature + " > " + (value - 1) + "px)");
|
||||
should_apply("all and (" + feature + " <= " + value + "px)");
|
||||
should_not_apply("all and (" + feature + " < " + value + "px)");
|
||||
should_apply("all and (" + feature + " < " + (value + 1) + "px)");
|
||||
should_not_apply("all and (min-" + feature + ": " +
|
||||
(Math.ceil(value/em_size) + 1) + "em)");
|
||||
should_apply("all and (min-" + feature + ": " +
|
||||
@@ -308,6 +321,21 @@ function run() {
|
||||
(Math.floor(value/em_size) - 1) + "rem)");
|
||||
}
|
||||
|
||||
change_state(function() {
|
||||
iframe_style.width = "100px";
|
||||
iframe_style.height = "10px";
|
||||
});
|
||||
should_apply("all and (width: calc(100px / 1em * 1em))");
|
||||
should_apply("all and (width: calc((50vh * 5em) / 4px))");
|
||||
should_apply("all and (width: calc(10vw / 10px * 1000vh))");
|
||||
should_apply("all and (width: calc(8000vh * 1vw / 1em * 8px / 40vh))");
|
||||
should_not_apply("all and (width: calc(100px / 1em * 2em))");
|
||||
|
||||
change_state(function() {
|
||||
iframe_style.width = width_val + "px";
|
||||
iframe_style.height = height_val + "px";
|
||||
});
|
||||
|
||||
change_state(function() {
|
||||
iframe_style.width = "0";
|
||||
});
|
||||
@@ -349,6 +377,9 @@ function run() {
|
||||
should_apply("(orientation: landscape)");
|
||||
should_not_apply("(orientation: portrait)");
|
||||
should_apply("not all and (orientation: portrait)");
|
||||
expression_should_not_be_parseable("orientation > landscape");
|
||||
expression_should_not_be_parseable("min-width > 1px");
|
||||
expression_should_not_be_parseable("width >");
|
||||
// ratio that reduces to 59/80
|
||||
change_state(function() {
|
||||
iframe_style.height = "320px";
|
||||
|
||||
@@ -1106,58 +1106,6 @@ nsStandardURL::ParseURL(const char *spec, int32_t specLen)
|
||||
nsresult
|
||||
nsStandardURL::ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen)
|
||||
{
|
||||
// Cloudflare Image Resizing compatibility (pref-controlled)
|
||||
//
|
||||
// This feature detects the "/cdn-cgi/image/" marker in the URL path and
|
||||
// treats everything after it as opaque path data. Cloudflare's Image
|
||||
// Resizing service expects clients to preserve the entire suffix exactly.
|
||||
//
|
||||
// Because this code runs in a hot path (URL parsing), we avoid calling
|
||||
// Preferences::GetBool() repeatedly. Instead, we use AddBoolVarCache()
|
||||
// to cache the pref value once and read it cheaply thereafter.
|
||||
|
||||
// Cached preference: true = enable Cloudflare Image Resizing fixup
|
||||
static bool sCloudflareImageResizingEnabled = true;
|
||||
static bool sCloudflareImageResizingPrefCached = false;
|
||||
|
||||
if (!sCloudflareImageResizingPrefCached) {
|
||||
Preferences::AddBoolVarCache(
|
||||
&sCloudflareImageResizingEnabled,
|
||||
"network.url.cloudflare_image_resizing.enabled",
|
||||
true // default if pref does not exist
|
||||
);
|
||||
sCloudflareImageResizingPrefCached = true;
|
||||
}
|
||||
|
||||
if (sCloudflareImageResizingEnabled) {
|
||||
|
||||
// Extract the full path substring from the full URL spec.
|
||||
nsDependentCSubstring fullPath(spec + pathPos, pathLen);
|
||||
|
||||
// Prepare iterators for scanning the path.
|
||||
nsACString::const_iterator begin, end;
|
||||
fullPath.BeginReading(begin);
|
||||
fullPath.EndReading(end);
|
||||
|
||||
// Search for the Cloudflare Image Resizing marker.
|
||||
nsACString::const_iterator cfPos = begin;
|
||||
if (FindInReadable(NS_LITERAL_CSTRING("/cdn-cgi/image/"), cfPos, end)) {
|
||||
|
||||
// Compute how far into the path the marker was found.
|
||||
uint32_t offset = cfPos.get() - begin.get();
|
||||
|
||||
// Rewrite the internal path representation so that the path
|
||||
// begins at the Cloudflare marker. Everything before it is ignored.
|
||||
mPath.mPos = pathPos + offset;
|
||||
mPath.mLen = pathLen - offset;
|
||||
|
||||
// We handled the path; no further parsing needed.
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
LOG(("ParsePath: %s pathpos %d len %d\n",spec,pathPos,pathLen));
|
||||
|
||||
if (pathLen > net_GetURLMaxLength()) {
|
||||
|
||||
@@ -27,6 +27,24 @@ from mach.decorators import (
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def load_source_module(module_name, path):
|
||||
if module_name in sys.modules:
|
||||
return sys.modules[module_name]
|
||||
|
||||
try:
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
except ImportError:
|
||||
import imp
|
||||
with open(path, 'r') as fh:
|
||||
return imp.load_module(module_name, fh, path,
|
||||
('.py', 'r', imp.PY_SOURCE))
|
||||
|
||||
|
||||
ENG_BUILD_REQUIRED = '''
|
||||
The mochitest command requires an engineering build. It may be the case that
|
||||
VARIANT=user or PRODUCTION=1 were set. Try re-building with VARIANT=eng:
|
||||
@@ -175,11 +193,8 @@ class MochitestRunner(MozbuildObject):
|
||||
"""
|
||||
# runtests.py is ambiguous, so we load the file/module manually.
|
||||
if 'mochitest' not in sys.modules:
|
||||
import imp
|
||||
path = os.path.join(self.mochitest_dir, 'runtests.py')
|
||||
with open(path, 'r') as fh:
|
||||
imp.load_module('mochitest', fh, path,
|
||||
('.py', 'r', imp.PY_SOURCE))
|
||||
load_source_module('mochitest', path)
|
||||
|
||||
import mochitest
|
||||
|
||||
@@ -219,11 +234,8 @@ class MochitestRunner(MozbuildObject):
|
||||
if host_ret != 0:
|
||||
return host_ret
|
||||
|
||||
import imp
|
||||
path = os.path.join(self.mochitest_dir, 'runtestsremote.py')
|
||||
with open(path, 'r') as fh:
|
||||
imp.load_module('runtestsremote', fh, path,
|
||||
('.py', 'r', imp.PY_SOURCE))
|
||||
load_source_module('runtestsremote', path)
|
||||
import runtestsremote
|
||||
|
||||
options = Namespace(**kwargs)
|
||||
@@ -273,11 +285,8 @@ def setup_argument_parser():
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
|
||||
import imp
|
||||
path = os.path.join(build_obj.topobjdir, mochitest_dir, 'runtests.py')
|
||||
with open(path, 'r') as fh:
|
||||
imp.load_module('mochitest', fh, path,
|
||||
('.py', 'r', imp.PY_SOURCE))
|
||||
load_source_module('mochitest', path)
|
||||
|
||||
from mochitest_options import MochitestArgumentParser
|
||||
|
||||
|
||||
@@ -1188,11 +1188,16 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
|
||||
!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), nsCaseInsensitiveStringComparator()) &&
|
||||
// FIXME - these special hex values are currently used in xpcshell tests introduced by
|
||||
// bug 625160 patch 8/8. Maybe these tests need to be adjusted now that we're only whitelisting
|
||||
// intel/ati/nvidia.
|
||||
// intel/ati/nvidia. Also allow common virtual GPU vendor IDs (VirtualBox, VMware, QEMU/virtio, Parallels).
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0xabcd") &&
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0xdcba") &&
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0xabab") &&
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0xdcdc"))
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0xdcdc") &&
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0x80ee") && /* VirtualBox */
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0x15ad") && /* VMware */
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0x1234") && /* QEMU (common) */
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0x1af4") && /* virtio/QEMU */
|
||||
!adapterVendorID.LowerCaseEqualsLiteral("0x1ab8") ) /* Parallels */
|
||||
{
|
||||
aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR";
|
||||
*aStatus = FEATURE_BLOCKED_DEVICE;
|
||||
|
||||
Reference in New Issue
Block a user