@breakside/jskit 2026.10.0 → 2026.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  3. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  4. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  5. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  6. package/Frameworks/Foundation.jsframework/JS/JSNodeHTTPClient.js +34 -14
  7. package/Frameworks/Foundation.jsframework/JS/JSNodeURLSessionDataTask.js +2 -2
  8. package/Frameworks/Foundation.jsframework/JS/JSURLRequest.js +3 -0
  9. package/Frameworks/Foundation.jsframework/JS/JSZip.js +155 -1
  10. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  11. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  12. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  13. package/Info.json +2 -2
  14. package/Node/Docs/DocClass.js +38 -1
  15. package/Node/Docs/DocComponent.js +26 -1
  16. package/Node/Docs/DocDictionaryProperty.js +8 -0
  17. package/Node/Docs/DocEnum.js +35 -2
  18. package/Node/Docs/DocEnumOption.js +3 -0
  19. package/Node/Docs/DocExtension.js +130 -0
  20. package/Node/Docs/DocFunction.js +8 -0
  21. package/Node/Docs/DocMethod.js +3 -0
  22. package/Node/Docs/DocProperty.js +11 -0
  23. package/Node/Docs/DocProtocol.js +4 -0
  24. package/Node/Docs/DocSpec.js +4 -0
  25. package/Node/Docs/DocSpecProperty.js +8 -0
  26. package/Node/Docs/DocTopicBasedComponent.js +75 -65
  27. package/Node/Documentation.js +73 -4
  28. package/Node/HTMLProjectServer.js +1 -1
  29. package/Node/Markdown.js +2 -2
  30. package/Node/io.breakside.jskit-bundle.js +3 -3
  31. package/Node/jskit +1 -0
  32. package/Resources/doc-default.css +2 -2
  33. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  34. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  35. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  36. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  37. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  38. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  39. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  40. package/Root/Frameworks/DOM/Info.yaml +1 -1
  41. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  42. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  43. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  44. package/Root/Frameworks/Foundation/JSNodeHTTPClient.js +34 -14
  45. package/Root/Frameworks/Foundation/JSNodeURLSessionDataTask.js +2 -2
  46. package/Root/Frameworks/Foundation/JSURLRequest.js +3 -0
  47. package/Root/Frameworks/Foundation/JSZip.js +155 -1
  48. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  49. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  50. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  51. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  52. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  53. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  54. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  55. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  56. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  57. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  58. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  59. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  60. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  61. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.DOM",
4
- "JSBundleVersion": "2026.10.0",
4
+ "JSBundleVersion": "2026.11.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "DOM+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
12
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
13
13
  }
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.DOM'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.DOM",
6
- "JSBundleVersion": "2026.10.0",
6
+ "JSBundleVersion": "2026.11.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.DOM'] = {
11
11
  "node": "DOM+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
14
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
15
15
  },
16
16
  "Resources": [],
17
17
  "ResourceLookup": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.FontKit",
4
- "JSBundleVersion": "2026.10.0",
4
+ "JSBundleVersion": "2026.11.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
8
8
  "html": "FontKit+HTML.js"
9
9
  },
10
10
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
11
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
11
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
12
12
  }
@@ -3,14 +3,14 @@ JSBundle.bundles['io.breakside.JSKit.FontKit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.FontKit",
6
- "JSBundleVersion": "2026.10.0",
6
+ "JSBundleVersion": "2026.11.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
10
10
  "html": "FontKit+HTML.js"
11
11
  },
12
12
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
13
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
13
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
14
14
  },
15
15
  "Resources": [],
16
16
  "ResourceLookup": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.Foundation",
4
- "JSBundleVersion": "2026.10.0",
4
+ "JSBundleVersion": "2026.11.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "Foundation+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
12
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
13
13
  }
@@ -28,7 +28,10 @@ var https = require('https');
28
28
 
29
29
  JSClass("JSNodeHTTPClient", JSObject, {
30
30
 
31
- send: function(request, callbacks, target){
31
+ send: function(request, callbacks, target, allowedRedirects){
32
+ if (allowedRedirects === undefined){
33
+ allowedRedirects = 5;
34
+ }
32
35
  var completed = false;
33
36
  var complete = function(error){
34
37
  if (completed){
@@ -41,6 +44,7 @@ JSClass("JSNodeHTTPClient", JSObject, {
41
44
  };
42
45
  try{
43
46
  var response = null;
47
+ var client = this;
44
48
 
45
49
  // Create node request
46
50
  var options = {
@@ -66,20 +70,36 @@ JSClass("JSNodeHTTPClient", JSObject, {
66
70
  }
67
71
  response._headerMap.parse(rawHeaders.join("\r\n"));
68
72
  response.statusText = message.statusMessage;
69
- if (callbacks.response){
70
- callbacks.response.call(target, response);
71
- }
72
- var chunks = [];
73
- message.on('data', function(chunk){
74
- chunks.push(JSData.initWithNodeBuffer(chunk));
75
- });
76
- message.on('end', function(){
77
- if (completed){
78
- return;
73
+ if (allowedRedirects > 0 && (response.statusCode === JSURLResponse.StatusCode.movedPermanently || response.statusCode === JSURLResponse.StatusCode.found)){
74
+ message.on('data', function(chunk){
75
+ });
76
+ message.on('end', function(){
77
+ if (completed){
78
+ return;
79
+ }
80
+ request = request.redirectedRequestToURL(response.location);
81
+ if (callbacks.redirect){
82
+ callbacks.redirect(request);
83
+ }
84
+ client.send(request, callbacks, target, allowedRedirects - 1);
85
+ // TODO: send new request
86
+ });
87
+ }else{
88
+ if (callbacks.response){
89
+ callbacks.response.call(target, response);
79
90
  }
80
- response.data = JSData.initWithChunks(chunks);
81
- complete(null);
82
- });
91
+ var chunks = [];
92
+ message.on('data', function(chunk){
93
+ chunks.push(JSData.initWithNodeBuffer(chunk));
94
+ });
95
+ message.on('end', function(){
96
+ if (completed){
97
+ return;
98
+ }
99
+ response.data = JSData.initWithChunks(chunks);
100
+ complete(null);
101
+ });
102
+ }
83
103
  });
84
104
  nodeRequest.on('error', function(error){
85
105
  complete(error);
@@ -38,8 +38,8 @@ JSClass("JSNodeURLSessionDataTask", JSURLSessionDataTask, {
38
38
  }
39
39
  if (this.cancelation === null){
40
40
  this.cancelation = JSNodeHTTPClient.shared.send(this.currentRequest, {
41
- redirect: function(url){
42
- this._currentRequest = this._originalRequest.redirectedRequestToURL(url);
41
+ redirect: function(request){
42
+ this._currentRequest = request;
43
43
  },
44
44
  uploadProgress: function(sent, total){
45
45
  this.session._taskDidSendBodyData(this, sent, total);
@@ -46,6 +46,9 @@ JSClass("JSURLRequest", JSObject, {
46
46
  },
47
47
 
48
48
  redirectedRequestToURL: function(url){
49
+ if (!url.isAbsolute){
50
+ url.resolveToBaseURL(this.url);
51
+ }
49
52
  var request = JSURLRequest.initWithURL(url);
50
53
  request._method = this._method;
51
54
  request._headerMap = this._headerMap;
@@ -11,9 +11,160 @@ JSClass("JSZip", JSObject, {
11
11
  init: function(){
12
12
  this.chunks = [];
13
13
  this.directory = [];
14
+ this.directoryIndexByFilename = {};
14
15
  this.offset = 0;
15
16
  },
16
17
 
18
+ initWithData: function(data){
19
+ if (data === null || data === undefined){
20
+ return null;
21
+ }
22
+ if (data.length < 22){
23
+ return null;
24
+ }
25
+ this._data = data;
26
+ this.directory = [];
27
+ this.directoryIndexByFilename = {};
28
+ var offset = this._data.length - 22;
29
+ var minOffset = Math.max(0, offset - 0xFFFF);
30
+ while (offset >= minOffset && !(this._data[offset] === 0x50 && this._data[offset+1] === 0x4b && this._data[offset+2] === 0x05 && this._data[offset+3] === 0x06)){
31
+ offset -= 1;
32
+ }
33
+ if (offset < minOffset){
34
+ return null;
35
+ }
36
+ var end = this._data.subdataInRange(JSRange(offset, this._data.length - offset));
37
+ var dataView = end.dataView();
38
+ // End of Central Directory Record (APPNOTE.txt 4.3.16)
39
+ // everything is little-endian
40
+ var signature = dataView.getUint32(0, true);
41
+ if (signature !== 0x06054b50){
42
+ return null;
43
+ }
44
+ var diskNumber = dataView.getUint16(4, true);
45
+ var diskNumberWithDirectoryStart = dataView.getUint16(6, true);
46
+ var numberOfItemsOnDisk = dataView.getUint16(8, true);
47
+ var numberOfItemsTotal = dataView.getUint16(10, true);
48
+ var sizeOfDirectory = dataView.getUint32(12, true);
49
+ var offsetOfDirectoryStart = dataView.getUint32(16, true);
50
+ var commentLength = dataView.getUint16(20, true);
51
+ if (diskNumber !== 0 || diskNumberWithDirectoryStart !== 0){
52
+ return null;
53
+ }
54
+ if (offsetOfDirectoryStart + sizeOfDirectory > this._data.length){
55
+ return null;
56
+ }
57
+ var directory = this._data.subdataInRange(JSRange(offsetOfDirectoryStart, sizeOfDirectory));
58
+ dataView = directory.dataView();
59
+ var crc;
60
+ var compressedLength;
61
+ var uncompressedLength;
62
+ var nameLength;
63
+ var extraFieldLength;
64
+ var name;
65
+ var headerOffset;
66
+ var offset = 0;
67
+ for (var i = 0; i < numberOfItemsTotal; ++i){
68
+ // Central Directory File Header (APPNOTE.txt 4.3.12)
69
+ // Everything is little-endian
70
+ if (offset + 46 > sizeOfDirectory){
71
+ return null;
72
+ }
73
+ signature = dataView.getUint32(offset + 0, true);
74
+ if (signature !== 0x02014b50){
75
+ return null;
76
+ }
77
+ crc = dataView.getUint32(offset + 16, true);
78
+ compressedLength = dataView.getUint32(offset + 20, true);
79
+ uncompressedLength = dataView.getUint32(offset + 24, true);
80
+ nameLength = dataView.getUint16(offset + 28, true);
81
+ extraFieldLength = dataView.getUint16(offset + 30, true);
82
+ commentLength = dataView.getUint16(offset + 32, true);
83
+ headerOffset = dataView.getUint32(offset + 42, true);
84
+ if (offset + 46 + nameLength > sizeOfDirectory){
85
+ return null;
86
+ }
87
+ name = directory.subdataInRange(JSRange(offset + 46, nameLength)).stringByDecodingUTF8();
88
+ this.directory.push({
89
+ name: name,
90
+ offset: headerOffset,
91
+ header: this.data.subdataInRange(JSRange(headerOffset, 30 + nameLength + extraFieldLength)),
92
+ crc: crc,
93
+ compressedLength: compressedLength,
94
+ uncompressedLength: uncompressedLength
95
+ });
96
+ this.directoryIndexByFilename[name] = i;
97
+ offset += 46 + nameLength + extraFieldLength + commentLength;
98
+ }
99
+
100
+ },
101
+
102
+ directoryIndexByFilename: null,
103
+ filenames: JSReadOnlyProperty(),
104
+
105
+ getFilenames: function(){
106
+ var filenames = [];
107
+ for (var i = 0, l = this.directory.length; i < l; ++i){
108
+ filenames.push(this.directory[i].name);
109
+ }
110
+ return filenames;
111
+ },
112
+
113
+ dataForFilename: function(filename){
114
+ var index = this.directoryIndexByFilename[filename];
115
+ if (index === undefined){
116
+ return null;
117
+ }
118
+ var file = this.directory[index];
119
+ var dataView = file.header.dataView();
120
+ var signature = dataView.getUint32(0, true);
121
+ if (signature !== 0x04034b50){
122
+ return null;
123
+ }
124
+ var version = dataView.getUint16(4, true);
125
+ var flags = dataView.getUint16(6, true);
126
+ var method = dataView.getUint16(8, true);
127
+ var crc = dataView.getUint32(14, true);
128
+ var compressedLength = dataView.getUint32(18, true);
129
+ var uncompressedLength = dataView.getUint32(22, true);
130
+ var nameLength = dataView.getUint16(26, true);
131
+ var extraFieldLength = dataView.getUint16(28, true);
132
+ var offset = file.offset + 30 + nameLength + extraFieldLength;
133
+ if (version > 20){
134
+ return null;
135
+ }
136
+ if (method !== 8){
137
+ return null;
138
+ }
139
+ if ((flags & 0x8) !== 0){
140
+ crc = file.crc;
141
+ compressedLength = file.compressedLength;
142
+ uncompressedLength = file.uncompressedLength;
143
+ }
144
+ if (offset + compressedLength > this.data.length){
145
+ return null;
146
+ }
147
+ if (compressedLength === 0 || uncompressedLength === 0){
148
+ return null;
149
+ }
150
+ var compressedData = this.data.subdataInRange(JSRange(offset, compressedLength));
151
+ var data = JSData.initWithLength(uncompressedLength);
152
+ var stream = DeflateStream();
153
+ stream.input = compressedData;
154
+ stream.output = data;
155
+ stream.inflate(true);
156
+ if (stream.state !== DeflateStream.State.done){
157
+ data = null;
158
+ }
159
+ if (data === null){
160
+ return;
161
+ }
162
+ if (JSCRC32(data) !== crc){
163
+ return null;
164
+ }
165
+ return data;
166
+ },
167
+
17
168
  offset: 0,
18
169
 
19
170
  addFile: function(file, completion, target){
@@ -83,7 +234,10 @@ JSClass("JSZip", JSObject, {
83
234
  this.directory.push({
84
235
  name: name,
85
236
  offset: this.offset,
86
- header: header
237
+ header: header,
238
+ crc: crc,
239
+ compressedLength: storedData.length,
240
+ uncompressedLength: data.length
87
241
  });
88
242
  this.chunks.push(header);
89
243
  this.chunks.push(storedData);
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.Foundation'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.Foundation",
6
- "JSBundleVersion": "2026.10.0",
6
+ "JSBundleVersion": "2026.11.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.Foundation'] = {
11
11
  "node": "Foundation+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
14
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
15
15
  },
16
16
  "Resources": [
17
17
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "JSBundleType": "framework",
3
3
  "JSBundleIdentifier": "io.breakside.JSKit.SecurityKit",
4
- "JSBundleVersion": "2026.10.0",
4
+ "JSBundleVersion": "2026.11.0",
5
5
  "JSDevelopmentLanguage": "en",
6
6
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
7
7
  "JSBundleEnvironments": {
@@ -9,5 +9,5 @@
9
9
  "node": "SecurityKit+Node.js"
10
10
  },
11
11
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
12
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
12
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
13
13
  }
@@ -3,7 +3,7 @@ JSBundle.bundles['io.breakside.JSKit.SecurityKit'] = {
3
3
  "Info": {
4
4
  "JSBundleType": "framework",
5
5
  "JSBundleIdentifier": "io.breakside.JSKit.SecurityKit",
6
- "JSBundleVersion": "2026.10.0",
6
+ "JSBundleVersion": "2026.11.0",
7
7
  "JSDevelopmentLanguage": "en",
8
8
  "JSCopyright": "Copyright © 2020 Breakside Inc.",
9
9
  "JSBundleEnvironments": {
@@ -11,7 +11,7 @@ JSBundle.bundles['io.breakside.JSKit.SecurityKit'] = {
11
11
  "node": "SecurityKit+Node.js"
12
12
  },
13
13
  "JSLicenseNotice": "Licensed under the Breakside Public License, Version 1.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nIf a copy of the License was not distributed with this file, you may\nobtain a copy at\n\n http://breakside.io/licenses/LICENSE-1.0.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n",
14
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
14
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
15
15
  },
16
16
  "Resources": [],
17
17
  "ResourceLookup": {
package/Info.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "JSBundleType": "node",
3
3
  "JSBundleIdentifier": "io.breakside.jskit",
4
- "JSBundleVersion": "2026.10.0",
4
+ "JSBundleVersion": "2026.11.0",
5
5
  "JSExecutableName": "jskit",
6
6
  "NPMOrganization": "breakside",
7
7
  "JSResources": [
8
8
  "Tests/HTMLTestRunner.js",
9
9
  "Tests/NodeTestRunner.js"
10
10
  ],
11
- "GitRevision": "4f9f54d7fd9b56bd8c893ac8689a8efee1958ee5"
11
+ "GitRevision": "812100aa4c3e5b1ec1d0d432be903bba4db01b0e"
12
12
  }
@@ -35,11 +35,34 @@
35
35
  if (info.implements){
36
36
  this.implements = info.implements;
37
37
  }
38
+
38
39
  },
39
40
 
40
41
  inherits: null,
41
42
  anonymous: false,
42
43
  implements: null,
44
+ extensions: null,
45
+
46
+ addExtension: function(extension){
47
+ if (this.extensions === null){
48
+ this.extensions = [];
49
+ }
50
+ this.extensions.push(extension);
51
+ },
52
+
53
+ extensionChildForName: function(name){
54
+ if (this.extensions === null){
55
+ return null;
56
+ }
57
+ for (let i = 0, l = this.extensions.length; i < l; ++i){
58
+ let extension = this.extensions[i];
59
+ let component = extension.childForName(name);
60
+ if (component !== null){
61
+ return component;
62
+ }
63
+ }
64
+ return null;
65
+ },
43
66
 
44
67
  // --------------------------------------------------------------------
45
68
  // MARK: - Naming
@@ -62,6 +85,12 @@
62
85
  elements.splice(1, 0, declaration);
63
86
  }
64
87
 
88
+ if (this.extensions !== null){
89
+ for (let extension of this.extensions){
90
+ elements = elements.concat(this.htmlArticleTopicsElements(document, extension.extensionName + " Extensions", extension.topics));
91
+ }
92
+ }
93
+
65
94
  if (this.inherits){
66
95
  let inherits = document.createElement('section');
67
96
  elements.push(inherits);
@@ -76,6 +105,10 @@
76
105
  if (url !== null){
77
106
  let a = code.appendChild(document.createElement("a"));
78
107
  a.setAttribute("href", url.encodedString);
108
+ if (url.isAbsolute){
109
+ a.setAttribute("target", "_blank");
110
+ a.setAttribute("rel", "noopener noreferrer");
111
+ }
79
112
  a.appendChild(document.createTextNode(this.inherits));
80
113
  }else{
81
114
  code.appendChild(document.createTextNode(this.inherits));
@@ -99,6 +132,10 @@
99
132
  if (url !== null){
100
133
  let a = code.appendChild(document.createElement("a"));
101
134
  a.setAttribute("href", url.encodedString);
135
+ if (url.isAbsolute){
136
+ a.setAttribute("target", "_blank");
137
+ a.setAttribute("rel", "noopener noreferrer");
138
+ }
102
139
  a.appendChild(document.createTextNode(protocol));
103
140
  }else{
104
141
  code.appendChild(document.createTextNode(protocol));
@@ -129,6 +166,6 @@
129
166
  }
130
167
  }
131
168
  return component;
132
- },
169
+ }
133
170
 
134
171
  });
@@ -313,6 +313,10 @@ JSClass("DocComponent", JSObject, {
313
313
  if (url){
314
314
  let a = document.createElement('a');
315
315
  a.setAttribute('href', url.encodedString);
316
+ if (url.isAbsolute){
317
+ a.setAttribute("target", "_blank");
318
+ a.setAttribute("rel", "noopener noreferrer");
319
+ }
316
320
  a.appendChild(document.createTextNode(name));
317
321
  code.appendChild(a);
318
322
  }else{
@@ -619,6 +623,10 @@ JSClass("DocComponent", JSObject, {
619
623
  if (url !== null){
620
624
  span = line.appendChild(document.createElement('a'));
621
625
  span.setAttribute("href", url.encodedString);
626
+ if (url.isAbsolute){
627
+ span.setAttribute("target", "_blank");
628
+ span.setAttribute("rel", "noopener noreferrer");
629
+ }
622
630
  }
623
631
  }
624
632
  if (span === null){
@@ -628,7 +636,7 @@ JSClass("DocComponent", JSObject, {
628
636
  if (token.className){
629
637
  span.setAttribute("class", token.className);
630
638
  }
631
- if (i < l - 1 && tokens[i + 1].value != ','){
639
+ if (i < l - 1 && tokens[i + 1].value != ',' && tokens[i + 1].value[0] != '.'){
632
640
  line.appendChild(document.createTextNode(' '));
633
641
  }
634
642
  }
@@ -669,6 +677,12 @@ JSClass("DocComponent", JSObject, {
669
677
  };
670
678
  },
671
679
 
680
+ populateRelationships: function(){
681
+ for (let child of this.children){
682
+ child.populateRelationships();
683
+ }
684
+ },
685
+
672
686
  output: async function(documentation){
673
687
  let document = this.htmlDocument(documentation);
674
688
  let indexURL = this.outputURL;
@@ -777,6 +791,13 @@ JSClass("DocComponent", JSObject, {
777
791
  if (candidates.length > 0){
778
792
  return candidates[0];
779
793
  }
794
+
795
+ // Extensions
796
+ let component = this.extensionChildForName(name);
797
+ if (component !== null){
798
+ return component;
799
+ }
800
+
780
801
  return null;
781
802
  },
782
803
 
@@ -827,6 +848,10 @@ JSClass("DocComponent", JSObject, {
827
848
  return null;
828
849
  },
829
850
 
851
+ extensionChildForName: function(name){
852
+ return null;
853
+ },
854
+
830
855
  componentForSourceURL: function(sourceURL){
831
856
  if (this.parent !== null){
832
857
  return this.parent.componentForSourceURL(sourceURL);
@@ -53,6 +53,10 @@
53
53
  if (url){
54
54
  let a = code.appendChild(document.createElement("a"));
55
55
  a.setAttribute("href", url.encodedString);
56
+ if (url.isAbsolute){
57
+ a.setAttribute("target", "_blank");
58
+ a.setAttribute("rel", "noopener noreferrer");
59
+ }
56
60
  a.appendChild(document.createTextNode(this.valueType));
57
61
  }else{
58
62
  code.appendChild(document.createTextNode(this.valueType));
@@ -76,6 +80,10 @@
76
80
  let li = ul.appendChild(document.createElement("li"));
77
81
  li.setAttribute("class", variation.kind);
78
82
  let a = document.createElement("a");
83
+ if (url.isAbsolute){
84
+ a.setAttribute("target", "_blank");
85
+ a.setAttribute("rel", "noopener noreferrer");
86
+ }
79
87
  a.setAttribute("href", url.encodedString);
80
88
  let code = li.appendChild(document.createElement('code'));
81
89
  code.appendChild(a);
@@ -36,6 +36,29 @@
36
36
  }
37
37
  },
38
38
 
39
+ extensions: null,
40
+
41
+ addExtension: function(extension){
42
+ if (this.extensions === null){
43
+ this.extensions = [];
44
+ }
45
+ this.extensions.push(extension);
46
+ },
47
+
48
+ extensionChildForName: function(name){
49
+ if (this.extensions === null){
50
+ return null;
51
+ }
52
+ for (let i = 0, l = this.extensions.length; i < l; ++i){
53
+ let extension = this.extensions[i];
54
+ let component = extension.childForName(name);
55
+ if (component !== null){
56
+ return component;
57
+ }
58
+ }
59
+ return null;
60
+ },
61
+
39
62
  suffixForMember: function(member){
40
63
  if (member.isKindOfClass(DocEnumOption) && member.value !== null){
41
64
  return ": %s".sprintf(member.value);
@@ -61,12 +84,22 @@
61
84
  var declaration = this.codeSectionElement(document, "Declaration", this.declarationCode());
62
85
  declaration.setAttribute("class", "declaration");
63
86
  elements.splice(index++, 0, declaration);
87
+ if (this.extensions !== null){
88
+ for (let extension of this.extensions){
89
+ elements = elements.concat(this.htmlArticleTopicsElements(document, extension.extensionName + " Extensions", extension.topics));
90
+ }
91
+ }
64
92
  return elements;
65
93
  },
66
94
 
67
95
  declarationCode: function(){
68
- if (this.parent && this.parent.kind == 'class' || this.parent.kind == 'protocol'){
69
- return ["%s.%s = { ... }".sprintf(this.parent.name, this.name)];
96
+ if (this.parent){
97
+ if (this.parent.kind == 'class' || this.parent.kind == 'protocol'){
98
+ return ["%s.%s = { ... }".sprintf(this.parent.name, this.name)];
99
+ }
100
+ if (this.parent.kind === "extension"){
101
+ return ["%s.%s = { ... }".sprintf(this.parent.extends, this.name)];
102
+ }
70
103
  }
71
104
  return ["%s = { ... }".sprintf(this.name)];
72
105
  },
@@ -35,6 +35,9 @@
35
35
  },
36
36
 
37
37
  getTitle: function(){
38
+ if (this.parent.kind === "extension"){
39
+ return "%s.%s".sprintf(this.parent.extends, this.name);
40
+ }
38
41
  var title = "%s.%s".sprintf(this.parent.name, this.name);
39
42
  if (this.parent.parent && this.parent.parent.kind == 'class' || this.parent.parent.kind == 'protocol'){
40
43
  title = "%s.%s".sprintf(this.parent.parent.name, title);