From f086ec57b453fc0af763564eb80fea4b5b7f7296 Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Sat, 15 Apr 2023 05:23:28 +0900 Subject: [PATCH] fix(core): Use safe primordials wrappers (#18687) --- core/01_core.js | 6 +++--- ext/console/02_console.js | 12 +++++------ ext/crypto/00_crypto.js | 4 ++-- ext/fetch/21_formdata.js | 8 ++++--- ext/fetch/26_fetch.js | 4 ++-- ext/ffi/00_ffi.js | 4 ++-- ext/http/01_http.js | 7 +++--- ext/web/00_infra.js | 33 +++++++++++++++++++++++------ ext/web/01_mimesniff.js | 4 ++-- ext/web/02_event.js | 4 ++-- ext/web/02_structured_clone.js | 4 ++-- ext/web/02_timers.js | 4 ++-- ext/web/03_abort_signal.js | 4 ++-- ext/web/06_streams.js | 10 ++++----- ext/web/09_file.js | 4 ++-- ext/web/10_filereader.js | 4 ++-- ext/web/12_location.js | 4 ++-- ext/webidl/00_webidl.js | 4 ++-- ext/websocket/01_websocket.js | 4 ++-- ext/websocket/02_websocketstream.js | 4 ++-- runtime/js/10_permissions.js | 4 ++-- runtime/js/40_signals.js | 7 +++--- 22 files changed, 83 insertions(+), 60 deletions(-) diff --git a/core/01_core.js b/core/01_core.js index 6231c0766c..a2cd9e14b3 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -9,7 +9,6 @@ ArrayPrototypePush, Error, ErrorCaptureStackTrace, - Map, MapPrototypeDelete, MapPrototypeGet, MapPrototypeHas, @@ -22,6 +21,7 @@ RangeError, ReferenceError, SafeArrayIterator, + SafeMap, SafePromisePrototypeFinally, setQueueMicrotask, StringPrototypeSlice, @@ -65,7 +65,7 @@ registerErrorClass("URIError", URIError); let nextPromiseId = 1; - const promiseMap = new Map(); + const promiseMap = new SafeMap(); const RING_SIZE = 4 * 1024; const NO_PROMISE = null; // Alias to null is faster than plain nulls const promiseRing = ArrayPrototypeFill(new Array(RING_SIZE), NO_PROMISE); @@ -74,7 +74,7 @@ const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); let opCallTracingEnabled = false; - const opCallTraces = new Map(); + const opCallTraces = new SafeMap(); function enableOpCallTracing() { opCallTracingEnabled = true; diff --git a/ext/console/02_console.js b/ext/console/02_console.js index 85a0f784fe..3e55efb749 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -54,6 +54,7 @@ const { RegExpPrototypeTest, RegExpPrototypeToString, SafeArrayIterator, + SafeMap, SafeStringIterator, SafeSet, SafeRegExp, @@ -85,7 +86,6 @@ const { ArrayPrototypeFind, FunctionPrototypeBind, FunctionPrototypeToString, - Map, MapPrototype, MapPrototypeHas, MapPrototypeGet, @@ -658,7 +658,7 @@ let circular; function handleCircular(value, cyan) { let index = 1; if (circular === undefined) { - circular = new Map(); + circular = new SafeMap(); MapPrototypeSet(circular, value, index); } else { index = MapPrototypeGet(circular, value); @@ -1016,7 +1016,7 @@ function inspectError(value, cyan) { } } - const refMap = new Map(); + const refMap = new SafeMap(); for (let i = 0; i < causes.length; ++i) { const cause = causes[i]; if (circular !== undefined) { @@ -1405,7 +1405,7 @@ function inspectObject(value, inspectOptions, proxyDetails) { } } -const colorKeywords = new Map([ +const colorKeywords = new SafeMap([ ["black", "#000000"], ["silver", "#c0c0c0"], ["gray", "#808080"], @@ -1990,8 +1990,8 @@ function inspectArgs(args, inspectOptions = {}) { return string; } -const countMap = new Map(); -const timerMap = new Map(); +const countMap = new SafeMap(); +const timerMap = new SafeMap(); const isConsoleInstance = Symbol("isConsoleInstance"); function getConsoleInspectOptions() { diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index c4905a1350..4d54b52a9d 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -33,6 +33,7 @@ const { StringPrototypeCharCodeAt, StringFromCharCode, SafeArrayIterator, + SafeWeakMap, Symbol, SymbolFor, SyntaxError, @@ -43,7 +44,6 @@ const { TypedArrayPrototypeGetSymbolToStringTag, TypeError, Uint8Array, - WeakMap, WeakMapPrototypeGet, WeakMapPrototypeSet, } = primordials; @@ -391,7 +391,7 @@ function usageIntersection(a, b) { // TODO(lucacasonato): this should be moved to rust /** @type {WeakMap} */ -const KEY_STORE = new WeakMap(); +const KEY_STORE = new SafeWeakMap(); function getKeyLength(algorithm) { switch (algorithm.name) { diff --git a/ext/fetch/21_formdata.js b/ext/fetch/21_formdata.js index 92c914b8c1..1961643d2e 100644 --- a/ext/fetch/21_formdata.js +++ b/ext/fetch/21_formdata.js @@ -22,12 +22,12 @@ const { ArrayPrototypePush, ArrayPrototypeSlice, ArrayPrototypeSplice, - Map, MapPrototypeGet, MapPrototypeSet, MathRandom, ObjectFreeze, ObjectPrototypeIsPrototypeOf, + SafeMap, SafeRegExp, Symbol, StringFromCharCode, @@ -346,13 +346,15 @@ function formDataToBlob(formData) { }); } +const QUOTE_CONTENT_PATTERN = new SafeRegExp(/^"([^"]*)"$/); + /** * @param {string} value * @returns {Map} */ function parseContentDisposition(value) { /** @type {Map} */ - const params = new Map(); + const params = new SafeMap(); // Forced to do so for some Map constructor param mismatch const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); for (let i = 0; i < values.length; i++) { @@ -361,7 +363,7 @@ function parseContentDisposition(value) { MapPrototypeSet( params, entries[0], - StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"), + StringPrototypeReplace(entries[1], QUOTE_CONTENT_PATTERN, "$1"), ); } } diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js index 18cb47a592..42e1ae962a 100644 --- a/ext/fetch/26_fetch.js +++ b/ext/fetch/26_fetch.js @@ -42,13 +42,13 @@ const { PromisePrototypeThen, PromisePrototypeCatch, SafeArrayIterator, + SafeWeakMap, String, StringPrototypeStartsWith, StringPrototypeToLowerCase, TypeError, Uint8Array, Uint8ArrayPrototype, - WeakMap, WeakMapPrototypeDelete, WeakMapPrototypeGet, WeakMapPrototypeHas, @@ -62,7 +62,7 @@ const REQUEST_BODY_HEADER_NAMES = [ "content-type", ]; -const requestBodyReaders = new WeakMap(); +const requestBodyReaders = new SafeWeakMap(); /** * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index a8f05dfcaf..f36690226e 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -30,8 +30,8 @@ const { MathCeil, SafeMap, SafeArrayIterator, + SafeWeakMap, SymbolFor, - WeakMap, } = primordials; import { pathFromURL } from "ext:deno_web/00_infra.js"; @@ -204,7 +204,7 @@ const OUT_BUFFER = new Uint32Array(2); const OUT_BUFFER_64 = new BigInt64Array( TypedArrayPrototypeGetBuffer(OUT_BUFFER), ); -const POINTER_TO_BUFFER_WEAK_MAP = new WeakMap(); +const POINTER_TO_BUFFER_WEAK_MAP = new SafeWeakMap(); class UnsafePointer { static create(value) { return ops.op_ffi_ptr_create(value); diff --git a/ext/http/01_http.js b/ext/http/01_http.js index 51347ebede..5bfa58655e 100644 --- a/ext/http/01_http.js +++ b/ext/http/01_http.js @@ -47,8 +47,9 @@ const { ArrayPrototypePush, Error, ObjectPrototypeIsPrototypeOf, + PromisePrototypeCatch, + SafeSet, SafeSetIterator, - Set, SetPrototypeAdd, SetPrototypeDelete, SetPrototypeClear, @@ -56,8 +57,6 @@ const { StringPrototypeIncludes, StringPrototypeToLowerCase, StringPrototypeSplit, - SafeSet, - PromisePrototypeCatch, Symbol, SymbolAsyncIterator, TypeError, @@ -79,7 +78,7 @@ class HttpConn { // that were created during lifecycle of this request. // When the connection is closed these resources should be closed // as well. - managedResources = new Set(); + managedResources = new SafeSet(); constructor(rid, remoteAddr, localAddr) { this.#rid = rid; diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js index efe7217de9..e9d5dd48b5 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -18,6 +18,7 @@ const { JSONStringify, NumberPrototypeToString, ObjectPrototypeIsPrototypeOf, + RegExpPrototypeTest, SafeArrayIterator, SafeRegExp, String, @@ -26,6 +27,7 @@ const { StringPrototypeMatch, StringPrototypePadStart, StringPrototypeReplace, + StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeSubstring, StringPrototypeToLowerCase, @@ -274,17 +276,24 @@ function addPaddingToBase64url(base64url) { return base64url; } +const BASE64URL_PATTERN = new SafeRegExp(/^[-_A-Z0-9]*?={0,2}$/i); + /** * @param {string} base64url * @returns {string} */ function convertBase64urlToBase64(base64url) { - if (!/^[-_A-Z0-9]*?={0,2}$/i.test(base64url)) { + if (!RegExpPrototypeTest(BASE64URL_PATTERN, base64url)) { // Contains characters not part of base64url spec. throw new TypeError("Failed to decode base64url: invalid character"); } - return addPaddingToBase64url(base64url).replace(/\-/g, "+").replace( - /_/g, + return StringPrototypeReplaceAll( + StringPrototypeReplaceAll( + addPaddingToBase64url(base64url), + "-", + "+", + ), + "_", "/", ); } @@ -295,9 +304,21 @@ function convertBase64urlToBase64(base64url) { * @returns {string} */ function forgivingBase64UrlEncode(data) { - return forgivingBase64Encode( - typeof data === "string" ? new TextEncoder().encode(data) : data, - ).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); + return StringPrototypeReplaceAll( + StringPrototypeReplaceAll( + StringPrototypeReplaceAll( + forgivingBase64Encode( + typeof data === "string" ? new TextEncoder().encode(data) : data, + ), + "=", + "", + ), + "+", + "-", + ), + "/", + "_", + ); } /** diff --git a/ext/web/01_mimesniff.js b/ext/web/01_mimesniff.js index 147ee095e8..ad89f33cd7 100644 --- a/ext/web/01_mimesniff.js +++ b/ext/web/01_mimesniff.js @@ -9,11 +9,11 @@ const primordials = globalThis.__bootstrap.primordials; const { ArrayPrototypeIncludes, - Map, MapPrototypeGet, MapPrototypeHas, MapPrototypeSet, RegExpPrototypeTest, + SafeMap, SafeMapIterator, StringPrototypeReplaceAll, StringPrototypeToLowerCase, @@ -92,7 +92,7 @@ function parseMimeType(input) { type: StringPrototypeToLowerCase(type), subtype: StringPrototypeToLowerCase(subtype), /** @type {Map} */ - parameters: new Map(), + parameters: new SafeMap(), }; // 11. diff --git a/ext/web/02_event.js b/ext/web/02_event.js index fe4f3e1982..34b3502a7c 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -24,7 +24,6 @@ const { DateNow, Error, FunctionPrototypeCall, - Map, MapPrototypeGet, MapPrototypeSet, ObjectCreate, @@ -34,6 +33,7 @@ const { ReflectDefineProperty, ReflectHas, SafeArrayIterator, + SafeMap, StringPrototypeStartsWith, Symbol, SymbolFor, @@ -1443,7 +1443,7 @@ function defineEventHandler( } if (!this[_eventHandlers]) { - this[_eventHandlers] = new Map(); + this[_eventHandlers] = new SafeMap(); } let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); if (handlerWrapper) { diff --git a/ext/web/02_structured_clone.js b/ext/web/02_structured_clone.js index 6dd1d4d031..b0c7559c01 100644 --- a/ext/web/02_structured_clone.js +++ b/ext/web/02_structured_clone.js @@ -20,12 +20,12 @@ const { DataViewPrototypeGetByteLength, DataViewPrototypeGetByteOffset, ObjectPrototypeIsPrototypeOf, + SafeWeakMap, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetByteOffset, TypedArrayPrototypeGetLength, TypedArrayPrototypeGetSymbolToStringTag, TypeErrorPrototype, - WeakMap, WeakMapPrototypeSet, Int8Array, Int16Array, @@ -40,7 +40,7 @@ const { Float64Array, } = primordials; -const objectCloneMemo = new WeakMap(); +const objectCloneMemo = new SafeWeakMap(); function cloneArrayBuffer( srcBuffer, diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index 753848af19..78cf06e445 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -7,7 +7,6 @@ const { ArrayPrototypePush, ArrayPrototypeShift, FunctionPrototypeCall, - Map, MapPrototypeDelete, MapPrototypeGet, MapPrototypeHas, @@ -18,6 +17,7 @@ const { NumberPOSITIVE_INFINITY, PromisePrototypeThen, SafeArrayIterator, + SafeMap, SymbolFor, TypedArrayPrototypeGetBuffer, TypeError, @@ -76,7 +76,7 @@ function handleTimerMacrotask() { * * @type {Map} */ -const activeTimers = new Map(); +const activeTimers = new SafeMap(); let nextId = 1; diff --git a/ext/web/03_abort_signal.js b/ext/web/03_abort_signal.js index 5ad52443d6..2122d642ef 100644 --- a/ext/web/03_abort_signal.js +++ b/ext/web/03_abort_signal.js @@ -14,8 +14,8 @@ import { const primordials = globalThis.__bootstrap.primordials; const { SafeArrayIterator, + SafeSet, SafeSetIterator, - Set, SetPrototypeAdd, SetPrototypeDelete, Symbol, @@ -69,7 +69,7 @@ class AbortSignal extends EventTarget { return; } if (this[abortAlgos] === null) { - this[abortAlgos] = new Set(); + this[abortAlgos] = new SafeSet(); } SetPrototypeAdd(this[abortAlgos], algorithm); } diff --git a/ext/web/06_streams.js b/ext/web/06_streams.js index b49d7ecd76..c516063658 100644 --- a/ext/web/06_streams.js +++ b/ext/web/06_streams.js @@ -34,7 +34,6 @@ const { DataViewPrototypeGetByteOffset, Float32Array, Float64Array, - FinalizationRegistry, Int8Array, Int16Array, Int32Array, @@ -56,7 +55,9 @@ const { queueMicrotask, RangeError, ReflectHas, + SafeFinalizationRegistry, SafePromiseAll, + SafeWeakMap, // TODO(lucacasonato): add SharedArrayBuffer to primordials // SharedArrayBufferPrototype Symbol, @@ -73,7 +74,6 @@ const { Uint16Array, Uint32Array, Uint8ClampedArray, - WeakMap, WeakMapPrototypeGet, WeakMapPrototypeHas, WeakMapPrototypeSet, @@ -695,7 +695,7 @@ function isReadableStreamDisturbed(stream) { const DEFAULT_CHUNK_SIZE = 64 * 1024; // 64 KiB // A finalization registry to clean up underlying resources that are GC'ed. -const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { +const RESOURCE_REGISTRY = new SafeFinalizationRegistry((rid) => { core.tryClose(rid); }); @@ -4743,7 +4743,7 @@ webidl.configurePrototype(ByteLengthQueuingStrategy); const ByteLengthQueuingStrategyPrototype = ByteLengthQueuingStrategy.prototype; /** @type {WeakMap number>} */ -const byteSizeFunctionWeakMap = new WeakMap(); +const byteSizeFunctionWeakMap = new SafeWeakMap(); function initializeByteLengthSizeFunction(globalObject) { if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { @@ -4800,7 +4800,7 @@ webidl.configurePrototype(CountQueuingStrategy); const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; /** @type {WeakMap 1>} */ -const countSizeFunctionWeakMap = new WeakMap(); +const countSizeFunctionWeakMap = new SafeWeakMap(); /** @param {typeof globalThis} globalObject */ function initializeCountSizeFunction(globalObject) { diff --git a/ext/web/09_file.js b/ext/web/09_file.js index cb42c8c728..dccb206112 100644 --- a/ext/web/09_file.js +++ b/ext/web/09_file.js @@ -27,13 +27,13 @@ const { DataViewPrototypeGetByteOffset, Date, DatePrototypeGetTime, - FinalizationRegistry, MathMax, MathMin, ObjectPrototypeIsPrototypeOf, RegExpPrototypeTest, // TODO(lucacasonato): add SharedArrayBuffer to primordials // SharedArrayBufferPrototype + SafeFinalizationRegistry, SafeRegExp, StringPrototypeCharAt, StringPrototypeToLowerCase, @@ -549,7 +549,7 @@ webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( // A finalization registry to deallocate a blob part when its JS reference is // garbage collected. -const registry = new FinalizationRegistry((uuid) => { +const registry = new SafeFinalizationRegistry((uuid) => { ops.op_blob_remove_part(uuid); }); diff --git a/ext/web/10_filereader.js b/ext/web/10_filereader.js index c59f009bb2..897ac7e937 100644 --- a/ext/web/10_filereader.js +++ b/ext/web/10_filereader.js @@ -23,12 +23,12 @@ const { ArrayPrototypePush, ArrayPrototypeReduce, FunctionPrototypeCall, - Map, MapPrototypeGet, MapPrototypeSet, ObjectDefineProperty, queueMicrotask, SafeArrayIterator, + SafeMap, Symbol, TypedArrayPrototypeSet, TypedArrayPrototypeGetBuffer, @@ -273,7 +273,7 @@ class FileReader extends EventTarget { webidl.assertBranded(this, FileReaderPrototype); if (!this[handlerSymbol]) { - this[handlerSymbol] = new Map(); + this[handlerSymbol] = new SafeMap(); } let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); if (handlerWrapper) { diff --git a/ext/web/12_location.js b/ext/web/12_location.js index b80e7ef561..680f3d53b7 100644 --- a/ext/web/12_location.js +++ b/ext/web/12_location.js @@ -8,11 +8,11 @@ const primordials = globalThis.__bootstrap.primordials; const { Error, ObjectDefineProperties, + SafeWeakMap, Symbol, SymbolFor, SymbolToStringTag, TypeError, - WeakMap, WeakMapPrototypeGet, WeakMapPrototypeSet, } = primordials; @@ -206,7 +206,7 @@ ObjectDefineProperties(Location.prototype, { }, }); -const workerLocationUrls = new WeakMap(); +const workerLocationUrls = new SafeWeakMap(); class WorkerLocation { constructor(href = null, key = null) { diff --git a/ext/webidl/00_webidl.js b/ext/webidl/00_webidl.js index 5f4a69240c..4398609e52 100644 --- a/ext/webidl/00_webidl.js +++ b/ext/webidl/00_webidl.js @@ -60,7 +60,7 @@ const { ReflectOwnKeys, RegExpPrototypeTest, SafeRegExp, - Set, + SafeSet, SetPrototypeEntries, SetPrototypeForEach, SetPrototypeKeys, @@ -752,7 +752,7 @@ function createDictionaryConverter(name, ...dictionaries) { // https://heycam.github.io/webidl/#es-enumeration function createEnumConverter(name, values) { - const E = new Set(values); + const E = new SafeSet(values); return function (V, opts = {}) { const S = String(V); diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index a40263249c..2c6bf46b27 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -33,7 +33,7 @@ const { ObjectPrototypeIsPrototypeOf, PromisePrototypeThen, RegExpPrototypeTest, - Set, + SafeSet, SetPrototypeGetSize, // TODO(lucacasonato): add SharedArrayBuffer to primordials // SharedArrayBufferPrototype @@ -223,7 +223,7 @@ class WebSocket extends EventTarget { if ( protocols.length !== SetPrototypeGetSize( - new Set( + new SafeSet( ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), ), ) diff --git a/ext/websocket/02_websocketstream.js b/ext/websocket/02_websocketstream.js index dd8e0d3629..0ee7a70aa0 100644 --- a/ext/websocket/02_websocketstream.js +++ b/ext/websocket/02_websocketstream.js @@ -21,7 +21,7 @@ const { ObjectPrototypeIsPrototypeOf, PromisePrototypeCatch, PromisePrototypeThen, - Set, + SafeSet, SetPrototypeGetSize, StringPrototypeEndsWith, StringPrototypeToLowerCase, @@ -118,7 +118,7 @@ class WebSocketStream { if ( options.protocols.length !== SetPrototypeGetSize( - new Set( + new SafeSet( ArrayPrototypeMap( options.protocols, (p) => StringPrototypeToLowerCase(p), diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js index 20894beb0f..79cbb76321 100644 --- a/runtime/js/10_permissions.js +++ b/runtime/js/10_permissions.js @@ -10,7 +10,6 @@ const { ArrayPrototypeIncludes, ArrayPrototypeMap, ArrayPrototypeSlice, - Map, MapPrototypeGet, MapPrototypeHas, MapPrototypeSet, @@ -19,6 +18,7 @@ const { PromiseReject, ReflectHas, SafeArrayIterator, + SafeMap, Symbol, SymbolFor, TypeError, @@ -113,7 +113,7 @@ class PermissionStatus extends EventTarget { } /** @type {Map} */ -const statusCache = new Map(); +const statusCache = new SafeMap(); /** * @param {Deno.PermissionDescriptor} desc diff --git a/runtime/js/40_signals.js b/runtime/js/40_signals.js index 4ae3101515..51d6bb349f 100644 --- a/runtime/js/40_signals.js +++ b/runtime/js/40_signals.js @@ -4,8 +4,9 @@ const core = globalThis.Deno.core; const ops = core.ops; const primordials = globalThis.__bootstrap.primordials; const { + SafeSet, SafeSetIterator, - Set, + SetPrototypeAdd, SetPrototypeDelete, SymbolFor, TypeError, @@ -32,7 +33,7 @@ const signalData = {}; /** Gets the signal handlers and resource data of the given signal */ function getSignalData(signo) { return signalData[signo] ?? - (signalData[signo] = { rid: undefined, listeners: new Set() }); + (signalData[signo] = { rid: undefined, listeners: new SafeSet() }); } function checkSignalListenerType(listener) { @@ -47,7 +48,7 @@ function addSignalListener(signo, listener) { checkSignalListenerType(listener); const sigData = getSignalData(signo); - sigData.listeners.add(listener); + SetPrototypeAdd(sigData.listeners, listener); if (!sigData.rid) { // If signal resource doesn't exist, create it.