Developer Guide

BrowserSm Developer Guide

This guide covers contributing to BrowserSm development, extending the VM, and building applications on the platform.

Table of Contents

  1. Development Environment
  2. Project Structure
  3. Contributing Guidelines
  4. VM Development
  5. Browser Application Development
  6. Testing
  7. 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

Platform Integration

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);
};

Optimizing Performance

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

Custom Object Formats

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'.

Performance Tests

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

Performance Optimization

Profiling Tools

VM Performance

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 };
    }
}

Browser Performance

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.