Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"keywords": [
"ember-addon"
],
"sideEffects": false,
"exports": {
"./*": {
"development": "./dist/dev/packages/*",
Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/-internals/deprecations/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DeprecationOptions } from '@ember/debug';
import { ENV } from '@ember/-internals/environment';
import { ENV } from '@ember/-internals/environment/lib/env';
import { VERSION } from '@ember/version';
import { deprecate, assert } from '@ember/debug';
import { dasherize } from '../string/index';
Expand Down
4 changes: 2 additions & 2 deletions packages/@ember/-internals/environment/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './lib/context';
export * from './lib/env';
export { context, getLookup, setLookup, type GlobalContext } from './lib/context';
export { ENV, getENV } from './lib/env';
9 changes: 2 additions & 7 deletions packages/@ember/-internals/glimmer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,8 @@ export {
htmlSafe,
isHTMLSafe,
} from './lib/utils/string';
export {
Renderer,
_resetRenderers,
renderSettled,
renderComponent,
type View,
} from './lib/renderer';
export { Renderer, type View } from './lib/renderer';
export { _resetRenderers, renderSettled, renderComponent } from './lib/render-component';
export {
getTemplate,
setTemplate,
Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/-internals/glimmer/lib/component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { View } from '@ember/-internals/glimmer';
import type { View } from './renderer';
import {
descriptorForProperty,
get,
Expand Down
138 changes: 25 additions & 113 deletions packages/@ember/-internals/glimmer/lib/environment.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,41 @@
import { ENV } from '@ember/-internals/environment';
/**
* Full Ember-specific global context registration.
*
* This module registers the heavy Ember implementations (metal get/set,
* EmberArray iteration, proxy truthiness, HTMLSafe style checking, etc.)
* into the lightweight global context set up by glimmer-global-context.ts.
*
* It is imported by setup-registry.ts (full Ember app boot path) but NOT
* by the standalone renderComponent path, enabling tree-shaking of metal,
* @ember/array, and @ember/-internals/views for apps that only use
* renderComponent.
*/
import { get, set, _getProp, _setProp } from '@ember/-internals/metal';
import type { InternalOwner } from '@ember/-internals/owner';
import { getDebugName } from '@ember/-internals/utils';
import { constructStyleDeprecationMessage } from '@ember/-internals/views';
import { assert, deprecate, warn } from '@ember/debug';
import type { DeprecationOptions } from '@ember/debug';
import { schedule, _backburner } from '@ember/runloop';
import { DEBUG } from '@glimmer/env';
import setGlobalContext from '@glimmer/global-context';
import type { EnvironmentDelegate } from '@glimmer/runtime';
import { debug } from '@glimmer/validator';
import toIterator from './utils/iterator';
import { isHTMLSafe } from './utils/string';
import toBool from './utils/to-bool';

///////////
import { flushAsyncObservers } from '@ember/-internals/metal';
import { registerFlushAsyncObservers } from '@ember/runloop';
import { registerEmberGlobalContextImplementations } from './glimmer-global-context';

// Setup global context

setGlobalContext({
FEATURES: {
DEFAULT_HELPER_MANAGER: true,
},

scheduleRevalidate() {
_backburner.ensureInstance();
},
registerFlushAsyncObservers(flushAsyncObservers);

registerEmberGlobalContextImplementations({
_getProp,
_setProp,
get,
set,
toBool,
toIterator,

getProp: _getProp,
setProp: _setProp,
getPath: get,
setPath: set,

scheduleDestroy(destroyable, destructor) {
schedule('actions', null, destructor, destroyable);
},

scheduleDestroyed(finalizeDestructor) {
schedule('destroy', null, finalizeDestructor);
},

warnIfStyleNotTrusted(value: unknown) {
warn(
constructStyleDeprecationMessage(String(value)),
(() => {
if (value === null || value === undefined || isHTMLSafe(value)) {
return true;
}
return false;
})(),
{ id: 'ember-htmlbars.style-xss-warning' }
);
},

assert(test: unknown, msg: string, options?: { id: string }) {
if (DEBUG) {
let id = options?.id;

let override = VM_ASSERTION_OVERRIDES.filter((o) => o.id === id)[0];

assert(override?.message ?? msg, test);
}
},

deprecate(msg: string, test: unknown, options: { id: string }) {
if (DEBUG) {
let { id } = options;

if (id === 'argument-less-helper-paren-less-invocation') {
throw new Error(
`A resolved helper cannot be passed as a named argument as the syntax is ` +
`ambiguously a pass-by-reference or invocation. Use the ` +
`\`{{helper 'foo-helper}}\` helper to pass by reference or explicitly ` +
`invoke the helper with parens: \`{{(fooHelper)}}\`.`
);
}

let override = VM_DEPRECATION_OVERRIDES.filter((o) => o.id === id)[0];

if (!override) throw new Error(`deprecation override for ${id} not found`);

// allow deprecations to be disabled in the VM_DEPRECATION_OVERRIDES array below
if (!override.disabled) {
deprecate(override.message ?? msg, Boolean(test), override);
}
}
},
isHTMLSafe,
});

// Override the debug tracking message with the richer getDebugName
if (DEBUG) {
debug?.setTrackingTransactionEnv?.({
debugMessage(obj, keyName) {
Expand All @@ -103,38 +48,5 @@ if (DEBUG) {
});
}

///////////

// VM Assertion/Deprecation overrides

const VM_DEPRECATION_OVERRIDES: (DeprecationOptions & {
disabled?: boolean;
message?: string;
})[] = [
{
id: 'setting-on-hash',
until: '4.4.0',
for: 'ember-source',
since: {
available: '3.28.0',
enabled: '3.28.0',
},
},
];

const VM_ASSERTION_OVERRIDES: { id: string; message: string }[] = [];

///////////

// Define environment delegate

export class EmberEnvironmentDelegate implements EnvironmentDelegate {
public enableDebugTooling: boolean = ENV._DEBUG_RENDER_TREE;

constructor(
public owner: InternalOwner,
public isInteractive: boolean
) {}

onTransactionCommit(): void {}
}
// Re-export warnIfStyleNotTrusted using the full implementation
export { constructStyleDeprecationMessage };
154 changes: 154 additions & 0 deletions packages/@ember/-internals/glimmer/lib/glimmer-global-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Lightweight Glimmer VM global context setup.
*
* The heavy Ember-specific implementations (metal get/set, EmberArray iteration,
* proxy truthiness, HTMLSafe style checking) are registered lazily by
* environment.ts during full Ember app boot. For standalone renderComponent
* usage, the simple fallbacks here are sufficient.
*/
import { assert, deprecate, warn } from '@ember/debug';
import type { DeprecationOptions } from '@ember/debug';
import { schedule, _backburner } from '@ember/runloop';
import { DEBUG } from '@glimmer/env';
import setGlobalContext from '@glimmer/global-context';
import { debug } from '@glimmer/validator';

///////////

// Lazy integration for heavy Ember-specific implementations.
// Simple fallbacks are used until registerEmberGlobalContextImplementations()
// is called by environment.ts (only loaded in full Ember apps).

let _getProp: (obj: object, key: string) => unknown = (obj, key) =>
(obj as Record<string, unknown>)[key];
let _setProp: (obj: object, key: string, value: unknown) => void = (obj, key, value) => {
(obj as Record<string, unknown>)[key] = value;
};
let _getPath: (obj: object, key: string) => unknown = (obj, key) =>
(obj as Record<string, unknown>)[key];
let _setPath: (obj: object, key: string, value: unknown) => unknown = (obj, key, value) => {
(obj as Record<string, unknown>)[key] = value;
return value;
};
let _toBool: (value: unknown) => boolean = Boolean;
let _toIterator: (value: unknown) => any = () => null;
let _isHTMLSafe: (value: unknown) => boolean = () => false;

export function registerEmberGlobalContextImplementations(impls: {
_getProp: typeof _getProp;
_setProp: typeof _setProp;
get: typeof _getPath;
set: typeof _setPath;
toBool: typeof _toBool;
toIterator: typeof _toIterator;
isHTMLSafe: typeof _isHTMLSafe;
}) {
_getProp = impls._getProp;
_setProp = impls._setProp;
_getPath = impls.get;
_setPath = impls.set;
_toBool = impls.toBool;
_toIterator = impls.toIterator;
_isHTMLSafe = impls.isHTMLSafe;
}

///////////

const VM_DEPRECATION_OVERRIDES: (DeprecationOptions & {
disabled?: boolean;
message?: string;
})[] = [
{
id: 'setting-on-hash',
until: '4.4.0',
for: 'ember-source',
since: {
available: '3.28.0',
enabled: '3.28.0',
},
},
];

const VM_ASSERTION_OVERRIDES: { id: string; message: string }[] = [];

///////////

setGlobalContext({
FEATURES: {
DEFAULT_HELPER_MANAGER: true,
},

scheduleRevalidate() {
_backburner.ensureInstance();
},

toBool: (value: unknown) => _toBool(value),
toIterator: (value: unknown) => _toIterator(value),

getProp: (obj: object, key: string) => _getProp(obj, key),
setProp: (obj: object, key: string, value: unknown) => _setProp(obj, key, value),
getPath: (obj: object, key: string) => _getPath(obj, key),
setPath: (obj: object, key: string, value: unknown) => _setPath(obj, key, value),

scheduleDestroy(destroyable, destructor) {
schedule('actions', null, destructor, destroyable);
},

scheduleDestroyed(finalizeDestructor) {
schedule('destroy', null, finalizeDestructor);
},

warnIfStyleNotTrusted(value: unknown) {
warn(
`Binding style attributes may introduce cross-site scripting vulnerabilities; please ensure that the \`${String(value)}\` string is trusted.`,
(() => {
if (value === null || value === undefined || _isHTMLSafe(value)) {
return true;
}
return false;
})(),
{ id: 'ember-htmlbars.style-xss-warning' }
);
},

assert(test: unknown, msg: string, options?: { id: string }) {
if (DEBUG) {
let id = options?.id;
let override = VM_ASSERTION_OVERRIDES.filter((o) => o.id === id)[0];
assert(override?.message ?? msg, test);
}
},

deprecate(msg: string, test: unknown, options: { id: string }) {
if (DEBUG) {
let { id } = options;

if (id === 'argument-less-helper-paren-less-invocation') {
throw new Error(
`A resolved helper cannot be passed as a named argument as the syntax is ` +
`ambiguously a pass-by-reference or invocation. Use the ` +
`\`{{helper 'foo-helper}}\` helper to pass by reference or explicitly ` +
`invoke the helper with parens: \`{{(fooHelper)}}\`.`
);
}

let override = VM_DEPRECATION_OVERRIDES.filter((o) => o.id === id)[0];

if (!override) throw new Error(`deprecation override for ${id} not found`);

if (!override.disabled) {
deprecate(override.message ?? msg, Boolean(test), override);
}
}
},
});

if (DEBUG) {
debug?.setTrackingTransactionEnv?.({
debugMessage(obj, keyName) {
let dirtyString = keyName ? `\`${keyName}\` on \`${String(obj)}\`` : `\`${String(obj)}\``;

return `You attempted to update ${dirtyString}, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.`;
},
});
}
Loading
Loading