forceCalendar
Salesforce

Locker Service Compatibility

How forceCalendar maintains compatibility with Salesforce Lightning Locker Service.

What is Locker Service?

Lightning Locker Service is Salesforce's security architecture that isolates Lightning components in sandboxed environments. It restricts access to global objects, DOM APIs, and certain JavaScript features to prevent cross-component interference and XSS attacks.

Restrictions and Solutions

No eval() or Function()

Restriction: eval(), new Function(), and setTimeout(string) are blocked.

Solution: forceCalendar uses no dynamic code evaluation. All logic is in static modules. Template rendering uses programmatic DOM creation, not template strings.

No innerHTML (security-critical paths)

Restriction: innerHTML assignments can be blocked or sanitized by Locker Service.

Solution: View renderers use document.createElement(), element.textContent, and element.appendChild() for all DOM construction. No HTML strings are parsed.

// What we do (safe)
const div = document.createElement('div');
div.className = 'fc-event';
div.textContent = event.title;
container.appendChild(div);

// What we avoid (blocked by Locker)
container.innerHTML = `<div class="fc-event">${event.title}</div>`;

Restricted Global Access

Restriction: Direct access to window, document, and navigator may be proxied or restricted.

Solution: The core package has zero DOM dependencies. The interface package accesses DOM APIs only through component-scoped references (this.shadowRoot, this.template). The AdaptiveMemoryManager accesses process.memoryUsage through an indirect reference to avoid triggering Locker Service restrictions:

// Indirect access pattern for Locker Service
const getMemory = () => {
  try {
    const p = globalThis.process || {};
    return typeof p.memoryUsage === 'function' ? p.memoryUsage() : null;
  } catch {
    return null;
  }
};

No Dynamic import()

Restriction: Dynamic import() is not available in Locker Service contexts.

Solution: All imports are static ESM import statements resolved at bundle time. The LWC component uses dynamic document.createElement() for the custom element instead of dynamic imports.

Web Worker Limitations

Restriction: Web Workers may not be available or may have restricted APIs.

Solution: SearchWorkerManager detects Worker availability and falls back to InvertedIndex (synchronous, main-thread) when Workers are unavailable:

// SearchWorkerManager automatically falls back
if (typeof Worker === 'undefined') {
  // Use InvertedIndex on the main thread
  this.fallbackIndex = new InvertedIndex();
}

Shadow DOM

Restriction: Locker Service enforces Shadow DOM boundaries more strictly than standard browsers.

Solution: All events use composed: true to cross shadow boundaries:

this.dispatchEvent(new CustomEvent('calendar-navigate', {
  detail: { date, view },
  bubbles: true,
  composed: true, // Crosses Shadow DOM boundaries
}));

No External Network Requests

Restriction: CSP headers in Salesforce block requests to external origins unless explicitly allowlisted.

Solution: The core package makes no network requests. ICS URL subscription (ICSHandler.subscribe()) and importFromURL() should only be used in non-Salesforce contexts. In Salesforce, use the Apex controller for all data access.

Compatibility Checklist

FeatureLocker Service SafeNotes
Core Calendar APIYesZero DOM dependency
Event CRUDYesPure JS objects
Recurrence expansionYesPure computation
State managementYesPure JS
Date utilitiesYesUses Intl.DateTimeFormat
ICS parsingYesString processing only
ICS URL importNoBlocked by CSP; use Apex instead
Full-text searchPartialInvertedIndex works; Web Workers may not
Web ComponentsYesShadow DOM with composed events
CSS themingYesCSS custom properties inherit through Shadow DOM
Adaptive memoryYesIndirect process access with fallback
Drag and dropPartialStandard DOM events work; pointer events may be proxied

Best Practices for Salesforce

  1. Use the Apex controller for all data access. Do not use fetch(), XMLHttpRequest, or ICS URL imports.

  2. Deploy as static resources. Bundle @forcecalendar/core and @forcecalendar/interface into single files and deploy as static resources.

  3. Use lwc:dom="manual" for the custom element. This is required because LWC templates cannot directly reference custom elements from static resources.

  4. Test in Lightning Experience. Locker Service behavior differs between Lightning Experience, the Salesforce mobile app, and development environments. Always test in the target environment.

  5. Handle Worker fallback. Expect SearchWorkerManager to use the InvertedIndex fallback. Design your search UX to work synchronously.

  6. Avoid large event sets. Without Web Workers, search and recurrence expansion run on the main thread. Keep event counts manageable (under 5000) for responsive UI.