Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3885bb4
Add validate-dnr-rules tool for checking DNR rulesets against WebKit'…
chrmod Apr 10, 2026
d0067c9
Fix validate-dnr-rules to use locally-built WebKit at runtime
chrmod Apr 10, 2026
8b3eb0d
Add standalone validate-dnr-rules tool and distribution plans
chrmod Apr 10, 2026
857381a
Remove planning docs — plans are now implemented
chrmod Apr 10, 2026
442c021
Add CMake build and GitHub Actions CI for cross-platform validate-dnr…
chrmod Apr 10, 2026
db47289
Fix CMake module path and split CI configure per platform
chrmod Apr 10, 2026
4c74679
Build validate-dnr-rules via WebKit's root CMake with shallow checkout
chrmod Apr 10, 2026
1c49702
Install full GTK build deps for Linux CI
chrmod Apr 10, 2026
871edcb
Use WebKit's own install-dependencies script for Linux CI
chrmod Apr 10, 2026
fa5b3fd
Fix Linux deps and add CMake build cache
chrmod Apr 10, 2026
ed1983b
Remove unavailable WPE packages from Linux CI
chrmod Apr 10, 2026
676fc29
Add missing ATSPI and gi-docgen deps for GTK configure
chrmod Apr 10, 2026
a43b999
Disable optional GTK features to minimize CI dependencies
chrmod Apr 10, 2026
2b5e46c
Use JSCOnly port for CI — only needs ICU, no GTK/platform deps
chrmod Apr 10, 2026
5c4b19b
Fix WTF include paths for CMake build
chrmod Apr 10, 2026
5a48b34
Fix JavaScriptCore include bridge to point to yarr/ subdirectory
chrmod Apr 10, 2026
88ff238
Linux build working: JSCOnly port + system malloc + Gigacage stub
chrmod Apr 11, 2026
c6d9885
Add macOS arm64 build to CI, fix bmalloc stubs for cross-platform
chrmod Apr 11, 2026
2880b64
Use macos-15 runner for newer Clang with asm constraint support
chrmod Apr 11, 2026
1bf5e3e
Create GitHub Release with binaries on merge to ghostery branch
chrmod Apr 11, 2026
99f8d74
Also trigger on ghostery/* branches for testing
chrmod Apr 11, 2026
d34b047
Use 'validator' as the release branch name
chrmod Apr 11, 2026
0e04e84
Ad-hoc codesign macOS binary for Apple Silicon Gatekeeper
chrmod Apr 11, 2026
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
121 changes: 121 additions & 0 deletions .github/workflows/validate-dnr-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Build validate-dnr-rules

on:
push:
branches: [validator, ghostery/*]
workflow_dispatch:

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
name: linux-x64
deps: cmake ninja-build pkg-config ruby unifdef libicu-dev g++ perl python3
- os: macos-15
name: macos-arm64
deps: cmake ninja icu4c pkg-config
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ${{ matrix.deps }}

- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install ${{ matrix.deps }}

- name: Cache CMake build
uses: actions/cache@v4
with:
path: build
key: cmake-${{ matrix.name }}-${{ hashFiles('Source/WTF/**', 'Source/WebCore/contentextensions/**', 'ghostery/validate-dnr-rules/**') }}
restore-keys: |
cmake-${{ matrix.name }}-

- name: Configure
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DPORT=JSCOnly \
-DUSE_SYSTEM_MALLOC=ON \
.
env:
CMAKE_PREFIX_PATH: ${{ runner.os == 'macOS' && '/opt/homebrew/opt/icu4c' || '' }}

- name: Build
run: cmake --build build --target validate-dnr-rules

- name: Test
run: |
cat > /tmp/test-rules.json << 'RULES'
[
{"id":1,"priority":1,"action":{"type":"block"},"condition":{"regexFilter":"ad[0-9]{2}\\.js"}},
{"id":2,"priority":1,"action":{"type":"block"},"condition":{"regexFilter":"(?:ads|tracking)\\.com"}},
{"id":3,"priority":1,"action":{"type":"block"},"condition":{"regexFilter":"tracker\\d+\\.js"}},
{"id":4,"priority":1,"action":{"type":"block"},"condition":{"regexFilter":"pixel\\b"}}
]
RULES
./build/bin/validate-dnr-rules /tmp/test-rules.json

- name: Sign binary (macOS)
if: runner.os == 'macOS'
run: codesign --sign - --force build/bin/validate-dnr-rules

- name: Prepare artifact
run: |
cp build/bin/validate-dnr-rules validate-dnr-rules-${{ matrix.name }}
chmod +x validate-dnr-rules-${{ matrix.name }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: validate-dnr-rules-${{ matrix.name }}
path: validate-dnr-rules-${{ matrix.name }}

release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts

- name: Create release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
TAG="validate-dnr-rules-$(date +%Y%m%d)-${GITHUB_SHA::8}"

# Delete existing release with same tag if re-running
gh release delete "$TAG" --yes 2>/dev/null || true

gh release create "$TAG" \
--title "validate-dnr-rules $(date +%Y-%m-%d)" \
--notes "Automated build from commit ${GITHUB_SHA::8}.

## Downloads
- **Linux x64**: \`validate-dnr-rules-linux-x64\`
- **macOS arm64**: \`validate-dnr-rules-macos-arm64\` (Intel Macs: run via Rosetta)

## Usage
\`\`\`
chmod +x validate-dnr-rules-*
./validate-dnr-rules-linux-x64 path/to/dnr-rules.json
\`\`\`" \
artifacts/validate-dnr-rules-linux-x64/validate-dnr-rules-linux-x64 \
artifacts/validate-dnr-rules-macos-arm64/validate-dnr-rules-macos-arm64
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ if (DEVELOPER_MODE)
add_subdirectory(PerformanceTests)
endif ()

# -----------------------------------------------------------------------------
# Ghostery tools
# -----------------------------------------------------------------------------
if (EXISTS "${CMAKE_SOURCE_DIR}/ghostery/validate-dnr-rules/CMakeLists.txt")
add_subdirectory(ghostery/validate-dnr-rules)
endif ()

# -----------------------------------------------------------------------------
# Print the features list last, for maximum visibility.
# -----------------------------------------------------------------------------
Expand Down
203 changes: 203 additions & 0 deletions Tools/Scripts/validate-dnr-rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#!/bin/bash
# validate-dnr-rules - Validate Declarative Net Request rulesets using WebKit's translator
#
# Usage: validate-dnr-rules [--compile] <rules.json> [<rules.json> ...]
#
# Runs DNR rule files through WebKit's translation pipeline and reports errors.
# With --compile, also compiles translated rules to content blocker bytecode.
#
# Requires: WebKit built with Tools/Scripts/build-webkit --debug

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SOURCE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

COMPILE=0
FILES=()

for arg in "$@"; do
case "$arg" in
--compile) COMPILE=1 ;;
--help|-h)
echo "Usage: validate-dnr-rules [--compile] <rules.json> [<rules.json> ...]"
echo ""
echo "Validates DNR rulesets using WebKit's native translator pipeline."
echo "Reports translation errors (unsupported regex, invalid rules, etc)."
echo ""
echo "Options:"
echo " --compile Also compile translated rules to content blocker bytecode"
echo " --help Show this help"
exit 0
;;
*) FILES+=("$arg") ;;
esac
done

if [ ${#FILES[@]} -eq 0 ]; then
echo "Error: No input files specified. Use --help for usage." >&2
exit 1
fi

BUILD_DIR="$("$SCRIPT_DIR/webkit-build-directory" --configuration=Debug --top-level 2>/dev/null || true)"
if [ -z "$BUILD_DIR" ]; then
BUILD_DIR="$SOURCE_ROOT/WebKitBuild"
fi

FRAMEWORK_DIR="$BUILD_DIR/Debug"

if [ ! -d "$FRAMEWORK_DIR/WebKit.framework" ]; then
echo "Error: WebKit.framework not found at $FRAMEWORK_DIR" >&2
echo "Build WebKit first: Tools/Scripts/build-webkit --debug" >&2
exit 1
fi

TOOL_SRC=$(mktemp /tmp/validate-dnr-XXXXXX.mm)
TOOL_BIN=$(mktemp /tmp/validate-dnr-XXXXXX)

trap "rm -f '$TOOL_SRC' '$TOOL_BIN'" EXIT

cat > "$TOOL_SRC" << 'OBJC_SOURCE'
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#import <WebKit/_WKWebExtensionDeclarativeNetRequestTranslator.h>
#import <WebKit/_WKWebExtensionDeclarativeNetRequestRule.h>
#import <WebKit/WKContentRuleListPrivate.h>

int main(int argc, const char *argv[]) {
@autoreleasepool {
BOOL doCompile = NO;
NSMutableArray<NSString *> *files = [NSMutableArray array];

for (int i = 1; i < argc; i++) {
NSString *arg = [NSString stringWithUTF8String:argv[i]];
if ([arg isEqualToString:@"--compile"])
doCompile = YES;
else
[files addObject:arg];
}

int totalErrors = 0;

for (NSString *filePath in files) {
NSData *data = [NSData dataWithContentsOfFile:filePath];
if (!data) {
fprintf(stderr, "ERROR: Cannot read file: %s\n", filePath.UTF8String);
totalErrors++;
continue;
}

NSString *rulesetID = [[filePath lastPathComponent] stringByDeletingPathExtension];
NSDictionary<NSString *, NSData *> *jsonDataDict = @{ rulesetID: data };

printf("=== %s ===\n", filePath.UTF8String);
printf("File size: %lu bytes\n", (unsigned long)data.length);

// Step 1: JSON deserialization
NSArray<NSString *> *jsonErrors = nil;
NSDictionary *allJSONObjects = [_WKWebExtensionDeclarativeNetRequestTranslator jsonObjectsFromData:jsonDataDict errorStrings:&jsonErrors];

if (jsonErrors.count > 0) {
printf("JSON deserialization errors: %lu\n", (unsigned long)jsonErrors.count);
for (NSString *error in jsonErrors) {
printf(" ERROR: %s\n", error.UTF8String);
totalErrors++;
}
}

NSUInteger ruleCount = 0;
for (NSString *key in allJSONObjects)
ruleCount += [allJSONObjects[key] count];
printf("Rules parsed: %lu\n", (unsigned long)ruleCount);

// Step 2: Translation (DNR -> WebKit content blocker format)
NSArray<NSString *> *translationErrors = nil;
NSArray *convertedRules = [_WKWebExtensionDeclarativeNetRequestTranslator translateRules:allJSONObjects errorStrings:&translationErrors];

printf("Rules translated: %lu\n", (unsigned long)convertedRules.count);

if (translationErrors.count > 0) {
printf("Translation errors: %lu\n", (unsigned long)translationErrors.count);
for (NSString *error in translationErrors) {
printf(" ERROR: %s\n", error.UTF8String);
totalErrors++;
}
}

// Step 3: Optional compilation
if (doCompile && convertedRules.count > 0) {
NSError *jsonSerializationError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:convertedRules options:0 error:&jsonSerializationError];
if (jsonSerializationError) {
printf(" ERROR: JSON serialization failed: %s\n", jsonSerializationError.localizedDescription.UTF8String);
totalErrors++;
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
printf("Content blocker JSON: %lu bytes\n", (unsigned long)jsonString.length);
printf("Compiling...");
fflush(stdout);

__block BOOL done = NO;
__block BOOL success = NO;
__block NSString *compilationError = nil;

NSDate *startTime = [NSDate date];

[[WKContentRuleListStore defaultStore] compileContentRuleListForIdentifier:rulesetID encodedContentRuleList:jsonString completionHandler:^(WKContentRuleList *ruleList, NSError *error) {
success = (ruleList != nil);
if (error)
compilationError = error.localizedDescription;
done = YES;
}];

while (!done)
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

double elapsed = -[startTime timeIntervalSinceNow];

if (success)
printf(" OK (%.1f seconds)\n", elapsed);
else {
printf(" FAILED (%.1f seconds): %s\n", elapsed, compilationError.UTF8String);
totalErrors++;
}

[[WKContentRuleListStore defaultStore] removeContentRuleListForIdentifier:rulesetID completionHandler:^(NSError *error) {}];
}
}

printf("\n");
}

if (totalErrors == 0)
printf("OK: All rules validated successfully.\n");
else
printf("FAILED: %d error(s) found.\n", totalErrors);

return totalErrors > 0 ? 1 : 0;
}
}
OBJC_SOURCE

clang -ObjC++ -std=c++20 -fobjc-arc -w \
-F"$FRAMEWORK_DIR" \
-framework WebKit -framework Foundation \
-lc++ \
-Wl,-rpath,"$FRAMEWORK_DIR" \
-o "$TOOL_BIN" "$TOOL_SRC" 2>&1

if [ $? -ne 0 ]; then
echo "Error: Failed to compile validation tool" >&2
exit 1
fi

ARGS=()
if [ "$COMPILE" -eq 1 ]; then
ARGS+=(--compile)
fi

for f in "${FILES[@]}"; do
ARGS+=("$(cd "$(dirname "$f")" && pwd)/$(basename "$f")")
done

DYLD_FRAMEWORK_PATH="$FRAMEWORK_DIR" exec "$TOOL_BIN" "${ARGS[@]}"
1 change: 1 addition & 0 deletions ghostery/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ghostery/validate-dnr-rules/build/
Loading
Loading