BrowserSm Developer Guide
This guide covers contributing to BrowserSm development, extending the VM, and building applications on the platform.
Table of Contents
- Development Environment
- Project Structure
- Contributing Guidelines
- VM Development
- Browser Application Development
- Testing
- Performance Optimization
Development Environment
Prerequisites
- Squeak 6.0+ for desktop development and testing
- Node.js 16+ for VM development tools
- Git for version control
- Modern browser for testing (Chrome 90+, Firefox 88+, Safari 14+)
Setup
1
2
3
4
5
6
7
8
9
10
11
12
| # Clone repository
git clone https://github.com/pauljbernard/browsersm.git
cd browsersm
# Install development dependencies
npm install
# Start development server
npm run dev
# Run tests
npm test
|
Directory Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| browsersm/
├── src/ # Smalltalk source code
│ ├── BrowserSm-Core.st # Core browser classes
│ ├── BrowserSm-DOM.st # DOM implementation
│ ├── BrowserSm-HTML.st # HTML parser
│ ├── BrowserSm-CSS.st # CSS engine
│ └── BrowserSm-*.st # Other packages
├── vm/ # JavaScript VM implementation
│ ├── src/
│ │ ├── interpreter.js # Bytecode interpreter
│ │ ├── memory.js # Object memory
│ │ ├── primitives.js # Primitive operations
│ │ └── platform.js # Browser integration
│ ├── tests/ # VM unit tests
│ └── dist/ # Compiled VM
├── images/ # Squeak image files
├── examples/ # Example applications
├── docs/ # Documentation
└── tools/ # Development tools
|
Project Structure
Smalltalk Packages
Core Architecture
1
2
3
4
5
6
| BrowserSm-Core Core browser classes and configuration
BrowserSm-DOM W3C DOM implementation
BrowserSm-HTML HTML5 parser and tokenizer
BrowserSm-CSS CSS3 engine with layout algorithms
BrowserSm-Script Smalltalk script runtime and sandbox
BrowserSm-WebAPIs Web platform APIs (Fetch, Storage, etc.)
|
Advanced Features
1
2
3
4
| BrowserSm-AdvancedCSS Advanced CSS features (Grid, Animations)
BrowserSm-AdvancedDOM Advanced DOM features (Shadow DOM, etc.)
BrowserSm-Developer Developer tools and debugging
BrowserSm-Extensions Plugin architecture
|
Testing
1
2
3
4
5
| BrowserSm-Core-Tests Unit tests for core classes
BrowserSm-DOM-Tests DOM compliance tests
BrowserSm-HTML-Tests HTML parser tests
BrowserSm-CSS-Tests CSS engine tests
BrowserSm-Integration-Tests End-to-end browser tests
|
JavaScript VM Components
Core VM
1
2
3
4
5
6
| vm/src/
├── interpreter.js // Bytecode interpreter
├── memory.js // Object memory management
├── primitives.js // Primitive operation table
├── image.js // Image loading/saving
└── vm.js // Main VM class
|
1
2
3
4
5
6
| vm/src/platform/
├── events.js // Event handling
├── graphics.js // Canvas/WebGL integration
├── network.js // HTTP/WebSocket support
├── storage.js // Local/session storage
└── audio.js // Web Audio API
|
Contributing Guidelines
Code Standards
Smalltalk Code Style
Follow the BrowserSm Constitution strictly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| "Class definition"
Object subclass: #BSExampleClass
instanceVariableNames: 'instanceVar anotherVar'
classVariableNames: 'ClassVar'
poolDictionaries: ''
category: 'BrowserSm-Examples'!
"Method with proper formatting"
BSExampleClass>>exampleMethod: aParameter withAnother: anotherParameter
"Method comment explaining purpose and parameters"
| temporaryVariable result |
temporaryVariable := self processParameter: aParameter.
result := temporaryVariable combineWith: anotherParameter.
^ result
|
Requirements:
- Methods MUST NOT exceed 25 lines
- Classes MUST have single responsibility
- All public methods MUST have comments
- Use descriptive names, avoid abbreviations
JavaScript Code Style
For VM development:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // Use modern ES6+ features
class SqueakInterpreter {
constructor(vm) {
this.vm = vm;
this.pc = 0;
this.sp = 0;
}
// Methods should be focused and testable
fetchBytecode() {
const method = this.activeMethod;
const bytecode = method.bytecodes[this.pc];
this.pc++;
return bytecode;
}
// Use JSDoc for documentation
/**
* Execute a single bytecode instruction
* @param {number} bytecode - The bytecode to execute
* @returns {boolean} - True if execution should continue
*/
executeBytecode(bytecode) {
const operation = this.operations[bytecode];
return operation ? operation.call(this) : this.unknownBytecode(bytecode);
}
}
|
Contribution Workflow
1. Issue Creation
1
2
3
4
| Before coding, create/claim an issue:
- Bug fixes: Describe problem, steps to reproduce
- Features: Reference constitutional article and specification
- Improvements: Justify need and approach
|
2. Branch Management
1
2
3
4
5
6
7
8
9
10
11
12
| # Create feature branch
git checkout -b feature/dom-shadow-implementation
git checkout -b fix/css-flexbox-bug-123
git checkout -b refactor/reduce-method-length-article-vii
# Make focused commits
git commit -m "Implement Shadow DOM attachment methods
- Add attachShadow method to BSDOMElement
- Implement ShadowRoot class with proper encapsulation
- Follow W3C Shadow DOM specification sections 4.1-4.3
- Addresses issue #456"
|
3. Testing Requirements
All contributions MUST include tests:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| "Test case for new functionality"
TestCase subclass: #BSShadowDOMTest
instanceVariableNames: 'element shadowRoot'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-AdvancedDOM-Tests'.
BSShadowDOMTest>>setUp
element := BSDOMElement tag: 'div'.
shadowRoot := element attachShadow: 'open'.
BSShadowDOMTest>>testShadowRootAttachment
self assert: shadowRoot notNil.
self assert: element shadowRoot equals: shadowRoot.
self assert: shadowRoot host equals: element.
|
4. Pull Request Process
1
2
3
4
5
6
| 1. Ensure all tests pass
2. Update documentation
3. Reference constitutional compliance
4. Request review from maintainer
5. Address feedback
6. Squash commits before merge
|
VM Development
Bytecode Interpreter
Adding New Bytecodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Define bytecode operation
function pushInstVar(interpreter, bytecode) {
const varIndex = bytecode & 0x0F; // Extract variable index
const receiver = interpreter.receiver();
const value = receiver.instVarAt(varIndex);
interpreter.push(value);
return true; // Continue execution
}
// Add to bytecode table
BytecodeTable[0x08] = pushInstVar; // pushInstVar0
BytecodeTable[0x09] = function(interp, bc) {
return pushInstVar(interp, bc | 0x01);
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class OptimizedInterpreter {
constructor() {
// Use typed arrays for stack
this.stack = new Int32Array(1000);
this.stackPointer = 0;
// Cache frequently accessed objects
this.cachedMethods = new Map();
this.cachedClasses = new Map();
}
// Inline common operations
pushSmallInteger(value) {
this.stack[this.stackPointer++] = (value << 1) | 1;
}
popSmallInteger() {
const oop = this.stack[--this.stackPointer];
return oop >> 1;
}
}
|
Primitive Operations
Implementing New Primitives
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Browser-specific primitive
function primitiveCanvasDrawText(vm, args) {
const [contextOop, textOop, xOop, yOop] = args;
// Unwrap Smalltalk objects
const context = vm.unwrapCanvasContext(contextOop);
const text = vm.fetchString(textOop);
const x = vm.fetchSmallInteger(xOop);
const y = vm.fetchSmallInteger(yOop);
// Perform operation
context.fillText(text, x, y);
// Return success
return contextOop;
}
// Register primitive
PrimitiveTable.primitives[350] = primitiveCanvasDrawText;
|
Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| function primitiveWithErrorHandling(vm, args) {
try {
// Validate arguments
if (args.length !== 2) {
return vm.primitiveFailure();
}
const [receiverOop, argumentOop] = args;
// Type checking
if (!vm.isInteger(argumentOop)) {
return vm.primitiveFailure();
}
// Perform operation
const result = performOperation(receiverOop, argumentOop);
return result;
} catch (error) {
console.error('Primitive error:', error);
return vm.primitiveFailure();
}
}
|
Memory Management
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Define custom object layout
class CanvasContextObject extends SqueakObject {
constructor(domContext) {
super();
this.format = 12; // External object format
this.domContext = domContext;
this.properties = new Map();
}
// Custom slot access
fetchSlot(index) {
const property = this.getPropertyName(index);
return this.wrapProperty(this.domContext[property]);
}
storeSlot(index, value) {
const property = this.getPropertyName(index);
this.domContext[property] = this.unwrapValue(value);
}
}
|
Browser Application Development
Extending the DOM Engine
Custom HTML Elements
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| "Define custom element class"
BSDOMElement subclass: #BSCustomVideo
instanceVariableNames: 'videoElement controls autoplay'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-CustomElements'.
BSCustomVideo class>>tagName
^ 'custom-video'
BSCustomVideo>>initialize
super initialize.
self tagName: self class tagName.
self createVideoElement.
self setupEventHandlers.
BSCustomVideo>>createVideoElement
videoElement := HTMLVideoElement new.
self appendChild: videoElement.
BSCustomVideo>>play
videoElement play.
BSCustomVideo>>pause
videoElement pause.
"Register with parser"
BSHTMLParser registerCustomElement: 'custom-video' class: BSCustomVideo.
|
CSS Engine Extensions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| "Custom CSS property"
BSCSSProperty subclass: #BSCSSTransform3D
instanceVariableNames: 'matrix3D'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-CustomCSS'.
BSCSSTransform3D class>>propertyName
^ 'transform-3d'
BSCSSTransform3D>>parseValue: valueString
"Parse 3D transformation matrix"
matrix3D := self parseMatrix3D: valueString.
BSCSSTransform3D>>applyToElement: element
"Apply 3D transformation"
element morphicMorph transform3D: matrix3D.
|
Plugin Architecture
Creating Browser Plugins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| "Base plugin class"
Object subclass: #BSBrowserPlugin
instanceVariableNames: 'browser enabled'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-Extensions'.
BSBrowserPlugin>>initialize
super initialize.
enabled := true.
BSBrowserPlugin>>install: aBrowser
browser := aBrowser.
self registerHooks.
"Example: Ad blocker plugin"
BSBrowserPlugin subclass: #BSAdBlockerPlugin
instanceVariableNames: 'blockedDomains rules'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-Extensions'.
BSAdBlockerPlugin>>registerHooks
browser registerRequestFilter: self.
BSAdBlockerPlugin>>filterRequest: request
^ (self shouldBlock: request)
ifTrue: [ nil "Block request" ]
ifFalse: [ request "Allow request" ].
BSAdBlockerPlugin>>shouldBlock: request
^ blockedDomains includes: request domain.
|
Testing
Test Categories
Unit Tests
Test individual classes and methods:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| TestCase subclass: #BSDOMElementTest
instanceVariableNames: 'element'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-DOM-Tests'.
BSDOMElementTest>>setUp
element := BSDOMElement tag: 'div'.
BSDOMElementTest>>testBasicProperties
self assert: element tagName equals: 'DIV'.
self assert: element nodeType equals: 1.
self assert: element parentNode isNil.
BSDOMElementTest>>testAttributeAccess
element setAttribute: 'id' value: 'test'.
self assert: (element getAttribute: 'id') equals: 'test'.
self assert: (element hasAttribute: 'id').
|
Integration Tests
Test component interactions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| TestCase subclass: #BSHTMLParsingTest
instanceVariableNames: 'parser'
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-Integration-Tests'.
BSHTMLParsingTest>>testCompletePageParsing
| html document |
html := '<html><head><title>Test</title></head><body><p>Hello</p></body></html>'.
parser := BSHTMLParser new.
document := parser parse: html.
self assert: document documentElement tagName equals: 'HTML'.
self assert: document title equals: 'Test'.
self assert: (document querySelector: 'p') textContent equals: 'Hello'.
|
Measure and verify performance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| TestCase subclass: #BSPerformanceTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'BrowserSm-Performance-Tests'.
BSPerformanceTest>>testLargeDocumentParsing
| html startTime endTime duration |
html := self generateLargeHTML: 10000. "10K elements"
startTime := Time millisecondClockValue.
document := BSHTMLParser new parse: html.
endTime := Time millisecondClockValue.
duration := endTime - startTime.
self assert: duration < 1000. "Parse in under 1 second"
|
Running Tests
Desktop Squeak
1
2
3
4
5
6
7
8
| "Run all tests"
TestRunner open.
"Run specific test suite"
(TestSuite named: 'BrowserSm Tests') run.
"Run individual test"
BSDOMElementTest suite run.
|
VM Tests (JavaScript)
1
2
3
4
5
6
7
8
9
10
11
| # Run VM unit tests
npm test
# Run specific test file
npm test -- interpreter.test.js
# Run with coverage
npm run test:coverage
# Run performance benchmarks
npm run benchmark
|
Continuous Integration
Test Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # .github/workflows/test.yml
name: BrowserSm Tests
on: [push, pull_request]
jobs:
test-smalltalk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Squeak
run: ./scripts/setup-squeak.sh
- name: Load BrowserSm
run: ./scripts/load-browsersm.sh
- name: Run Tests
run: ./scripts/run-tests.sh
test-vm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
- run: npm test
- run: npm run test:browser
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| class VMProfiler {
constructor(vm) {
this.vm = vm;
this.samples = [];
this.enabled = false;
}
startProfiling() {
this.enabled = true;
this.startTime = performance.now();
}
sampleExecution() {
if (!this.enabled) return;
this.samples.push({
timestamp: performance.now(),
method: this.vm.activeContext.method,
pc: this.vm.instructionPointer,
stackDepth: this.vm.stackPointer
});
}
generateReport() {
const hotMethods = this.analyzeHotMethods();
const memoryUsage = this.analyzeMemoryUsage();
const gcStats = this.analyzeGCStats();
return { hotMethods, memoryUsage, gcStats };
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| Object subclass: #BSPerformanceMonitor
instanceVariableNames: 'renderTimes layoutTimes paintTimes'
classVariableNames: 'Instance'
poolDictionaries: ''
category: 'BrowserSm-Performance'.
BSPerformanceMonitor>>measureRender: aBlock
| startTime endTime |
startTime := Time millisecondClockValue.
aBlock value.
endTime := Time millisecondClockValue.
renderTimes add: endTime - startTime.
BSPerformanceMonitor>>averageRenderTime
^ renderTimes isEmpty
ifTrue: [ 0 ]
ifFalse: [ renderTimes sum / renderTimes size ].
|
Optimization Techniques
Smalltalk Optimizations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| "Use efficient collection operations"
"Instead of:"
result := OrderedCollection new.
collection do: [ :each |
(each satisfies: condition)
ifTrue: [ result add: (each transform) ]
].
"Use:"
result := collection
select: [ :each | each satisfies: condition ]
thenCollect: [ :each | each transform ].
"Cache expensive computations"
BSCSSEngine>>computedStyleFor: element
^ self cachedStyles
at: element
ifAbsentPut: [ self calculateStyleFor: element ].
|
JavaScript Optimizations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // Use object pooling for frequently created objects
class ObjectPool {
constructor(createFn, resetFn, maxSize = 100) {
this.create = createFn;
this.reset = resetFn;
this.pool = [];
this.maxSize = maxSize;
}
get() {
if (this.pool.length > 0) {
return this.pool.pop();
}
return this.create();
}
release(obj) {
if (this.pool.length < this.maxSize) {
this.reset(obj);
this.pool.push(obj);
}
}
}
// Use for context objects
const contextPool = new ObjectPool(
() => new ExecutionContext(),
(ctx) => ctx.reset(),
50
);
|
This developer guide provides comprehensive information for contributing to BrowserSm, whether you’re working on the JavaScript VM implementation or the Smalltalk browser application. Remember to follow the constitutional principles and maintain the high standards that make BrowserSm a unique and powerful platform.