mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-11 01:12:06 -08:00
feat: WebGL fingerprint spoofing
Experimental WebGL fingerprint injection. - Allows the ability to set all WebGL parameters, supported extension list, shader precision formats, & context attributes. - Added a demo website under scripts/examples/webgl.html that can be used to generate Camoufox config data
This commit is contained in:
parent
df755def7f
commit
02bc15161a
10 changed files with 880 additions and 58 deletions
2
Makefile
2
Makefile
|
|
@ -109,7 +109,7 @@ build:
|
||||||
@if [ ! -f $(cf_source_dir)/_READY ]; then \
|
@if [ ! -f $(cf_source_dir)/_READY ]; then \
|
||||||
make dir; \
|
make dir; \
|
||||||
fi
|
fi
|
||||||
cd $(cf_source_dir) && ./mach build
|
cd $(cf_source_dir) && ./mach build $(_ARGS)
|
||||||
|
|
||||||
edits:
|
edits:
|
||||||
python ./scripts/developer.py $(version) $(release)
|
python ./scripts/developer.py $(version) $(release)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Written by daijro.
|
||||||
#include "mozilla/glue/Debug.h"
|
#include "mozilla/glue/Debug.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
|
|
@ -76,11 +77,9 @@ inline const nlohmann::json& GetJson() {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool HasKey(const std::string& key, nlohmann::json& data) {
|
inline bool HasKey(const std::string& key, nlohmann::json& data) {
|
||||||
if (!data.contains(key)) {
|
// printf_stderr("Property: %s\n", key.c_str());
|
||||||
// printf_stderr("WARNING: Key not found: %s\n", key.c_str());
|
// printf_stderr("WARNING: Key not found: %s\n", key.c_str());
|
||||||
return false;
|
return data.contains(key);
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::optional<std::string> GetString(const std::string& key) {
|
inline std::optional<std::string> GetString(const std::string& key) {
|
||||||
|
|
@ -163,8 +162,8 @@ inline std::optional<std::array<uint32_t, 4>> GetRect(
|
||||||
const std::string& height) {
|
const std::string& height) {
|
||||||
// Make top and left default to 0
|
// Make top and left default to 0
|
||||||
std::array<std::optional<uint32_t>, 4> values = {
|
std::array<std::optional<uint32_t>, 4> values = {
|
||||||
GetUint32(left).value_or(0), GetUint32(top).value_or(0),
|
GetUint32(left).value_or(0), GetUint32(top).value_or(0), GetUint32(width),
|
||||||
GetUint32(width), GetUint32(height)};
|
GetUint32(height)};
|
||||||
|
|
||||||
// If height or width is std::nullopt, return std::nullopt
|
// If height or width is std::nullopt, return std::nullopt
|
||||||
if (!values[2].has_value() || !values[3].has_value()) {
|
if (!values[2].has_value() || !values[3].has_value()) {
|
||||||
|
|
@ -197,4 +196,88 @@ inline std::optional<std::array<int32_t, 4>> GetInt32Rect(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers for WebGL
|
||||||
|
|
||||||
|
inline std::optional<nlohmann::json> GetNested(const std::string& domain,
|
||||||
|
std::string keyStr) {
|
||||||
|
auto data = GetJson();
|
||||||
|
if (!data.contains(domain)) return std::nullopt;
|
||||||
|
|
||||||
|
if (!data[domain].contains(keyStr)) return std::nullopt;
|
||||||
|
|
||||||
|
return data[domain][keyStr];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline std::optional<T> GetAttribute(const std::string attrib, bool isWebGL2) {
|
||||||
|
auto value = MaskConfig::GetNested(
|
||||||
|
isWebGL2 ? "webgl2:contextAttributes" : "webgl:contextAttributes",
|
||||||
|
attrib);
|
||||||
|
if (!value) return std::nullopt;
|
||||||
|
return value.value().get<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::optional<std::variant<int64_t, bool, double, std::string>> GLParam(
|
||||||
|
uint32_t pname, bool isWebGL2) {
|
||||||
|
auto value =
|
||||||
|
MaskConfig::GetNested(isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
|
||||||
|
std::to_string(pname));
|
||||||
|
if (!value) return std::nullopt;
|
||||||
|
auto data = value.value();
|
||||||
|
// cast the data and return
|
||||||
|
if (data.is_number_integer()) return data.get<int64_t>();
|
||||||
|
if (data.is_boolean()) return data.get<bool>();
|
||||||
|
if (data.is_number_float()) return data.get<double>();
|
||||||
|
if (data.is_string()) return data.get<std::string>();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T MParamGL(uint32_t pname, T defaultValue, bool isWebGL2) {
|
||||||
|
if (auto value = MaskConfig::GetNested(
|
||||||
|
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
|
||||||
|
std::to_string(pname));
|
||||||
|
value.has_value()) {
|
||||||
|
return value.value().get<T>();
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline std::vector<T> MParamGLVector(uint32_t pname,
|
||||||
|
std::vector<T> defaultValue,
|
||||||
|
bool isWebGL2) {
|
||||||
|
if (auto value = MaskConfig::GetNested(
|
||||||
|
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
|
||||||
|
std::to_string(pname)); value.has_value()) {
|
||||||
|
if (value.value().is_array()) {
|
||||||
|
std::array<T, 4UL> result = value.value().get<std::array<T, 4UL>>();
|
||||||
|
return std::vector<T>(result.begin(), result.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::optional<std::array<int32_t, 3UL>> MShaderData(
|
||||||
|
uint32_t shaderType, uint32_t precisionType, bool isWebGL2) {
|
||||||
|
std::string valueName =
|
||||||
|
std::to_string(shaderType) + "," + std::to_string(precisionType);
|
||||||
|
if (auto value =
|
||||||
|
MaskConfig::GetNested(isWebGL2 ? "webgl2:shaderPrecisionFormats"
|
||||||
|
: "webgl:shaderPrecisionFormats",
|
||||||
|
valueName)) {
|
||||||
|
// Convert {rangeMin: int, rangeMax: int, precision: int} to array
|
||||||
|
auto data = value.value();
|
||||||
|
// Assert rangeMin, rangeMax, and precision are present
|
||||||
|
if (!data.contains("rangeMin") || !data.contains("rangeMax") ||
|
||||||
|
!data.contains("precision")) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return std::array<int32_t, 3U>{data["rangeMin"].get<int32_t>(),
|
||||||
|
data["rangeMax"].get<int32_t>(),
|
||||||
|
data["precision"].get<int32_t>()};
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MaskConfig
|
} // namespace MaskConfig
|
||||||
|
|
@ -463,52 +463,6 @@ index 3a90c93c01..91d673039b 100644
|
||||||
+# DOM Mask
|
+# DOM Mask
|
||||||
+LOCAL_INCLUDES += ["/camoucfg"]
|
+LOCAL_INCLUDES += ["/camoucfg"]
|
||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
|
|
||||||
index 4e43425ef9..26bbfdc868 100644
|
|
||||||
--- a/dom/canvas/ClientWebGLContext.cpp
|
|
||||||
+++ b/dom/canvas/ClientWebGLContext.cpp
|
|
||||||
@@ -4,6 +4,7 @@
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#include "ClientWebGLContext.h"
|
|
||||||
+#include "MaskConfig.hpp"
|
|
||||||
|
|
||||||
#include <bitset>
|
|
||||||
|
|
||||||
@@ -2399,6 +2400,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
|
||||||
|
|
||||||
switch (pname) {
|
|
||||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
|
|
||||||
+ if (auto value = MaskConfig::GetString("webGl:renderer")) {
|
|
||||||
+ ret = Some(value.value());
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
ret = GetUnmaskedRenderer();
|
|
||||||
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
|
|
||||||
*ret = webgl::SanitizeRenderer(*ret);
|
|
||||||
@@ -2406,6 +2411,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
|
|
||||||
+ if (auto value = MaskConfig::GetString("webGl:vendor")) {
|
|
||||||
+ ret = Some(value.value());
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
ret = GetUnmaskedVendor();
|
|
||||||
break;
|
|
||||||
|
|
||||||
diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build
|
|
||||||
index 3a533d36d1..41d74d9b3f 100644
|
|
||||||
--- a/dom/canvas/moz.build
|
|
||||||
+++ b/dom/canvas/moz.build
|
|
||||||
@@ -221,3 +221,6 @@ if CONFIG["CC_TYPE"] == "gcc":
|
|
||||||
|
|
||||||
# Add libFuzzer configuration directives
|
|
||||||
include("/tools/fuzzing/libfuzzer-config.mozbuild")
|
|
||||||
+
|
|
||||||
+# DOM Mask
|
|
||||||
+LOCAL_INCLUDES += ["/camoucfg"]
|
|
||||||
\ No newline at end of file
|
|
||||||
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
|
diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp
|
||||||
index b05e409a84..8e0f7ea657 100644
|
index b05e409a84..8e0f7ea657 100644
|
||||||
--- a/dom/workers/WorkerNavigator.cpp
|
--- a/dom/workers/WorkerNavigator.cpp
|
||||||
|
|
|
||||||
360
patches/webgl-spoofing.patch
Normal file
360
patches/webgl-spoofing.patch
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
|
||||||
|
index db60868f65..afed6eeb7c 100644
|
||||||
|
--- a/dom/canvas/ClientWebGLContext.cpp
|
||||||
|
+++ b/dom/canvas/ClientWebGLContext.cpp
|
||||||
|
@@ -4,6 +4,8 @@
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "ClientWebGLContext.h"
|
||||||
|
+#include "MaskConfig.hpp"
|
||||||
|
+#include <algorithm>
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
@@ -744,6 +746,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
|
||||||
|
Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
+bool ClientWebGLContext::MBoolVal(const std::string& key, bool defaultValue) {
|
||||||
|
+ if (auto value = MaskConfig::GetAttribute<bool>(key, mIsWebGL2);
|
||||||
|
+ value.has_value())
|
||||||
|
+ return value.value();
|
||||||
|
+ return defaultValue;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void ClientWebGLContext::GetContextAttributes(
|
||||||
|
dom::Nullable<dom::WebGLContextAttributes>& retval) {
|
||||||
|
retval.SetNull();
|
||||||
|
@@ -754,14 +763,38 @@ void ClientWebGLContext::GetContextAttributes(
|
||||||
|
|
||||||
|
const auto& options = mNotLost->info.options;
|
||||||
|
|
||||||
|
- result.mAlpha.Construct(options.alpha);
|
||||||
|
- result.mDepth = options.depth;
|
||||||
|
- result.mStencil = options.stencil;
|
||||||
|
- result.mAntialias.Construct(options.antialias);
|
||||||
|
- result.mPremultipliedAlpha = options.premultipliedAlpha;
|
||||||
|
- result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
|
||||||
|
- result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
|
||||||
|
- result.mPowerPreference = options.powerPreference;
|
||||||
|
+ result.mAlpha.Construct(MBoolVal("alpha", options.alpha));
|
||||||
|
+ result.mDepth = MBoolVal("depth", options.depth);
|
||||||
|
+ result.mStencil = MBoolVal("stencil", options.stencil);
|
||||||
|
+ result.mAntialias.Construct(MBoolVal("antialias", options.antialias));
|
||||||
|
+ result.mPremultipliedAlpha = MBoolVal(
|
||||||
|
+ "webgl:contextAttributes.premultipliedAlpha", options.premultipliedAlpha);
|
||||||
|
+ result.mPreserveDrawingBuffer =
|
||||||
|
+ MBoolVal("preserveDrawingBuffer", options.preserveDrawingBuffer);
|
||||||
|
+ result.mFailIfMajorPerformanceCaveat = MBoolVal(
|
||||||
|
+ "failIfMajorPerformanceCaveat", options.failIfMajorPerformanceCaveat);
|
||||||
|
+ if (auto value =
|
||||||
|
+ MaskConfig::GetAttribute<std::string>("powerPreference", mIsWebGL2);
|
||||||
|
+ value.has_value()) {
|
||||||
|
+ // Convert to enum
|
||||||
|
+ switch (value.value()[0]) {
|
||||||
|
+ case 'd':
|
||||||
|
+ result.mPowerPreference = dom::WebGLPowerPreference::Default;
|
||||||
|
+ break;
|
||||||
|
+ case 'l':
|
||||||
|
+ result.mPowerPreference = dom::WebGLPowerPreference::Low_power;
|
||||||
|
+ break;
|
||||||
|
+ case 'h':
|
||||||
|
+ result.mPowerPreference = dom::WebGLPowerPreference::High_performance;
|
||||||
|
+ break;
|
||||||
|
+ default:
|
||||||
|
+ // Invalid value
|
||||||
|
+ result.mPowerPreference = options.powerPreference;
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ result.mPowerPreference = options.powerPreference;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------
|
||||||
|
@@ -979,18 +1012,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
|
||||||
|
std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
|
||||||
|
auto ret = std::unordered_map<GLenum, bool>{};
|
||||||
|
|
||||||
|
- ret[LOCAL_GL_BLEND] = false;
|
||||||
|
- ret[LOCAL_GL_CULL_FACE] = false;
|
||||||
|
- ret[LOCAL_GL_DEPTH_TEST] = false;
|
||||||
|
- ret[LOCAL_GL_DITHER] = true;
|
||||||
|
- ret[LOCAL_GL_POLYGON_OFFSET_FILL] = false;
|
||||||
|
- ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = false;
|
||||||
|
- ret[LOCAL_GL_SAMPLE_COVERAGE] = false;
|
||||||
|
- ret[LOCAL_GL_SCISSOR_TEST] = false;
|
||||||
|
- ret[LOCAL_GL_STENCIL_TEST] = false;
|
||||||
|
+ ret[LOCAL_GL_BLEND] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_BLEND, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_CULL_FACE] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_CULL_FACE, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_DEPTH_TEST] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_DEPTH_TEST, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_DITHER] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_DITHER, true, webgl2);
|
||||||
|
+ ret[LOCAL_GL_POLYGON_OFFSET_FILL] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_POLYGON_OFFSET_FILL, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = MaskConfig::MParamGL<bool>(
|
||||||
|
+ LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_SAMPLE_COVERAGE] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_SAMPLE_COVERAGE, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_SCISSOR_TEST] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_SCISSOR_TEST, false, webgl2);
|
||||||
|
+ ret[LOCAL_GL_STENCIL_TEST] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_STENCIL_TEST, false, webgl2);
|
||||||
|
|
||||||
|
if (webgl2) {
|
||||||
|
- ret[LOCAL_GL_RASTERIZER_DISCARD] = false;
|
||||||
|
+ ret[LOCAL_GL_RASTERIZER_DISCARD] =
|
||||||
|
+ MaskConfig::MParamGL<bool>(LOCAL_GL_RASTERIZER_DISCARD, false, webgl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
@@ -2058,6 +2101,28 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||||
|
const auto& state = State();
|
||||||
|
|
||||||
|
// -
|
||||||
|
+ std::optional<std::variant<int64_t, bool, double, std::string>> data;
|
||||||
|
+ data = MaskConfig::GLParam(pname, mIsWebGL2);
|
||||||
|
+
|
||||||
|
+ if (data.has_value()) {
|
||||||
|
+ const auto& value = data.value();
|
||||||
|
+ if (std::holds_alternative<int64_t>(value)) {
|
||||||
|
+ retval.set(JS::NumberValue(double(std::get<int64_t>(value))));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (std::holds_alternative<double>(value)) {
|
||||||
|
+ retval.set(JS::NumberValue(std::get<double>(value)));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (std::holds_alternative<bool>(value)) {
|
||||||
|
+ retval.set(JS::BooleanValue(std::get<bool>(value)));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (std::holds_alternative<std::string>(value)) {
|
||||||
|
+ retval.set(StringValue(cx, std::get<std::string>(value), rv));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
|
||||||
|
const auto fnSetRetval_Buffer = [&](const GLenum target) {
|
||||||
|
const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
|
||||||
|
@@ -2163,49 +2228,84 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||||
|
|
||||||
|
// 2 floats
|
||||||
|
case LOCAL_GL_DEPTH_RANGE:
|
||||||
|
- retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
|
||||||
|
+ retval.set(Create<dom::Float32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<float, 2UL>>(pname, state.mDepthRange,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
|
||||||
|
- retval.set(
|
||||||
|
- Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
|
||||||
|
+ retval.set(Create<dom::Float32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<float, 2UL>>(
|
||||||
|
+ pname, limits.pointSizeRange, mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
|
||||||
|
- retval.set(
|
||||||
|
- Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
|
||||||
|
+ retval.set(Create<dom::Float32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<float, 2UL>>(
|
||||||
|
+ pname, limits.lineWidthRange, mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 4 floats
|
||||||
|
case LOCAL_GL_COLOR_CLEAR_VALUE:
|
||||||
|
- retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
|
||||||
|
+ retval.set(Create<dom::Float32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<float, 4UL>>(pname, state.mClearColor,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case LOCAL_GL_BLEND_COLOR:
|
||||||
|
- retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
|
||||||
|
+ retval.set(Create<dom::Float32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<float, 4UL>>(pname, state.mBlendColor,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2 ints
|
||||||
|
case LOCAL_GL_MAX_VIEWPORT_DIMS: {
|
||||||
|
auto maxViewportDim = BitwiseCast<int32_t>(limits.maxViewportDim);
|
||||||
|
const auto dims = std::array<int32_t, 2>{maxViewportDim, maxViewportDim};
|
||||||
|
- retval.set(Create<dom::Int32Array>(cx, this, dims, rv));
|
||||||
|
+ retval.set(Create<dom::Int32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<int32_t, 2UL>>(pname, dims,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 ints
|
||||||
|
case LOCAL_GL_SCISSOR_BOX:
|
||||||
|
- retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
|
||||||
|
+ retval.set(Create<dom::Int32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<int32_t, 4UL>>(pname, state.mScissor,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case LOCAL_GL_VIEWPORT:
|
||||||
|
- retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
|
||||||
|
+ retval.set(Create<dom::Int32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGL<std::array<int32_t, 4UL>>(pname, state.mViewport,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
|
||||||
|
- // any
|
||||||
|
case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
|
||||||
|
- retval.set(Create<dom::Uint32Array>(cx, this,
|
||||||
|
- state.mCompressedTextureFormats, rv));
|
||||||
|
+ std::vector<uint32_t> compressedTextureUint32(
|
||||||
|
+ state.mCompressedTextureFormats.begin(),
|
||||||
|
+ state.mCompressedTextureFormats.end());
|
||||||
|
+ retval.set(Create<dom::Uint32Array>(
|
||||||
|
+ cx, this,
|
||||||
|
+ MaskConfig::MParamGLVector<uint32_t>(pname, compressedTextureUint32,
|
||||||
|
+ mIsWebGL2),
|
||||||
|
+ rv));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -2385,6 +2485,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||||
|
|
||||||
|
switch (pname) {
|
||||||
|
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
|
||||||
|
+ if (auto value = MaskConfig::GetString("webGl:renderer")) {
|
||||||
|
+ ret = Some(value.value());
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
ret = GetUnmaskedRenderer();
|
||||||
|
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
|
||||||
|
*ret = webgl::SanitizeRenderer(*ret);
|
||||||
|
@@ -2392,6 +2496,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||||
|
break;
|
||||||
|
|
||||||
|
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
|
||||||
|
+ if (auto value = MaskConfig::GetString("webGl:vendor")) {
|
||||||
|
+ ret = Some(value.value());
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
ret = GetUnmaskedVendor();
|
||||||
|
break;
|
||||||
|
|
||||||
|
@@ -2482,7 +2590,9 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
|
||||||
|
case LOCAL_GL_COLOR_WRITEMASK: {
|
||||||
|
const auto mask = uint8_t(*maybe);
|
||||||
|
const auto bs = std::bitset<4>(mask);
|
||||||
|
- const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
|
||||||
|
+ const auto src = MaskConfig::MParamGL<std::array<bool, 4>>(
|
||||||
|
+ pname, std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]},
|
||||||
|
+ mIsWebGL2);
|
||||||
|
JS::Rooted<JS::Value> arr(cx);
|
||||||
|
if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
|
||||||
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
@@ -2865,6 +2975,24 @@ ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
|
||||||
|
const GLenum precisiontype) {
|
||||||
|
if (IsContextLost()) return nullptr;
|
||||||
|
const auto info = [&]() {
|
||||||
|
+ // Check for spoofed value
|
||||||
|
+ if (auto value =
|
||||||
|
+ MaskConfig::MShaderData(shadertype, precisiontype, mIsWebGL2)) {
|
||||||
|
+ const auto& format = value.value();
|
||||||
|
+ return Some(webgl::ShaderPrecisionFormat{
|
||||||
|
+ format[0], // rangeMin
|
||||||
|
+ format[1], // rangeMax
|
||||||
|
+ format[2] // precision
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+ // Check if block if not defined is on
|
||||||
|
+ if (MaskConfig::GetBool(
|
||||||
|
+ mIsWebGL2 ? "webgl2:shaderPrecisionFormats:blockIfNotDefined"
|
||||||
|
+ : "webgl:shaderPrecisionFormats:blockIfNotDefined")) {
|
||||||
|
+ Maybe<webgl::ShaderPrecisionFormat> ret;
|
||||||
|
+ return ret;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
const auto& inProcess = mNotLost->inProcess;
|
||||||
|
if (inProcess) {
|
||||||
|
return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
|
||||||
|
@@ -5822,6 +5950,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (std::vector<std::string> maskValues =
|
||||||
|
+ MaskConfig::GetStringList(mIsWebGL2 ? "webgl2:supportedExtensions"
|
||||||
|
+ : "webgl:supportedExtensions");
|
||||||
|
+ !maskValues.empty()) {
|
||||||
|
+ if (std::find(maskValues.begin(), maskValues.end(),
|
||||||
|
+ GetExtensionName(ext)) != maskValues.end()) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
const auto& limits = Limits();
|
||||||
|
return limits.supportedExtensions[ext];
|
||||||
|
}
|
||||||
|
@@ -5833,6 +5972,17 @@ void ClientWebGLContext::GetSupportedExtensions(
|
||||||
|
if (!mNotLost) return;
|
||||||
|
|
||||||
|
auto& retarr = retval.SetValue();
|
||||||
|
+
|
||||||
|
+ // Implement separately to prevent O(n^2) timing
|
||||||
|
+ if (std::vector<std::string> maskValues =
|
||||||
|
+ MaskConfig::GetStringList("webgl:supportedExtensions");
|
||||||
|
+ !maskValues.empty()) {
|
||||||
|
+ for (const auto& ext : maskValues) {
|
||||||
|
+ retarr.AppendElement(NS_ConvertUTF8toUTF16(ext));
|
||||||
|
+ }
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
|
||||||
|
if (!IsSupported(i, callerType)) continue;
|
||||||
|
|
||||||
|
diff --git a/dom/canvas/ClientWebGLContext.h b/dom/canvas/ClientWebGLContext.h
|
||||||
|
index 9e8b3c0f00..16e3fc7aa5 100644
|
||||||
|
--- a/dom/canvas/ClientWebGLContext.h
|
||||||
|
+++ b/dom/canvas/ClientWebGLContext.h
|
||||||
|
@@ -1067,6 +1067,9 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
|
||||||
|
|
||||||
|
// -
|
||||||
|
|
||||||
|
+ // Helper to get booleans set in MaskConfig
|
||||||
|
+ bool MBoolVal(const std::string& key, bool defaultValue);
|
||||||
|
+
|
||||||
|
void GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
|
||||||
|
|
||||||
|
private:
|
||||||
|
diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build
|
||||||
|
index 3a533d36d1..41d74d9b3f 100644
|
||||||
|
--- a/dom/canvas/moz.build
|
||||||
|
+++ b/dom/canvas/moz.build
|
||||||
|
@@ -221,3 +221,6 @@ if CONFIG["CC_TYPE"] == "gcc":
|
||||||
|
|
||||||
|
# Add libFuzzer configuration directives
|
||||||
|
include("/tools/fuzzing/libfuzzer-config.mozbuild")
|
||||||
|
+
|
||||||
|
+# DOM Mask
|
||||||
|
+LOCAL_INCLUDES += ["/camoucfg"]
|
||||||
|
\ No newline at end of file
|
||||||
63
scripts/examples/buttonclick.html
Normal file
63
scripts/examples/buttonclick.html
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Random Button Clicker</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
position: absolute;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
#cursor-highlight {
|
||||||
|
position: fixed;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(255, 0, 0, 0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
function createButton() {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.classList.add("button");
|
||||||
|
button.textContent = "Click me!";
|
||||||
|
|
||||||
|
const maxX = window.innerWidth - 100;
|
||||||
|
const maxY = window.innerHeight - 50;
|
||||||
|
const randomX = Math.floor(Math.random() * maxX);
|
||||||
|
const randomY = Math.floor(Math.random() * maxY);
|
||||||
|
|
||||||
|
button.style.left = `${randomX}px`;
|
||||||
|
button.style.top = `${randomY}px`;
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
button.remove();
|
||||||
|
createButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
body.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the first button
|
||||||
|
createButton();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
scripts/examples/serve.sh
Normal file
1
scripts/examples/serve.sh
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
python -m http.server
|
||||||
345
scripts/examples/webgl.html
Normal file
345
scripts/examples/webgl.html
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>WebGL Fingerprinting - Camoufox</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #569cd6;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
#output {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #252526;
|
||||||
|
border: 1px solid #3e3e42;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-family: "Consolas", "Courier New", monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
background-color: #0e639c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 3px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #1177bb;
|
||||||
|
}
|
||||||
|
.string {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
.number {
|
||||||
|
color: #b5cea8;
|
||||||
|
}
|
||||||
|
.boolean {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
.null {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
.key {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
#hash {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-family: "Consolas", "Courier New", monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
#warning {
|
||||||
|
background-color: #ff9800;
|
||||||
|
color: #000;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
|
position: relative;
|
||||||
|
transition: opacity 0.3s ease-out, max-height 0.3s ease-out, padding 0.3s ease-out;
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#warning.hide {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#close-warning {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 5px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #000;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Your WebGL Information (in Camoufox format)</h1>
|
||||||
|
<div id="warning">Warning: This browser is not Firefox. The fingerprint below will not run properly on Camoufox.<button id="close-warning">×</button></div>
|
||||||
|
<div id="hash"></div>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
<div class="button-container">
|
||||||
|
<button id="copy-btn" class="btn">Copy data</button>
|
||||||
|
<button id="copy-minified-btn" class="btn">Copy data (minified)</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const dumpWebGLCore = async (
|
||||||
|
webglContextId,
|
||||||
|
experimentalWebglContextId
|
||||||
|
) => {
|
||||||
|
function getWebGLContext() {
|
||||||
|
const canvas = new OffscreenCanvas(256, 256);
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result =
|
||||||
|
canvas.getContext(webglContextId, {
|
||||||
|
preserveDrawingBuffer: false,
|
||||||
|
}) ||
|
||||||
|
canvas.getContext(experimentalWebglContextId, {
|
||||||
|
preserveDrawingBuffer: false,
|
||||||
|
});
|
||||||
|
} catch (ex) {}
|
||||||
|
|
||||||
|
result || (result = null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webglContext = getWebGLContext();
|
||||||
|
|
||||||
|
if (!webglContext) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxAnisotropy(ctx) {
|
||||||
|
if (ctx) {
|
||||||
|
const ext =
|
||||||
|
ctx.getExtension("EXT_texture_filter_anisotropic") ||
|
||||||
|
ctx.getExtension("WEBKIT_EXT_texture_filter_anisotropic") ||
|
||||||
|
ctx.getExtension("MOZ_EXT_texture_filter_anisotropic");
|
||||||
|
|
||||||
|
if (ext) {
|
||||||
|
return ctx.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glEnums = [
|
||||||
|
2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961,
|
||||||
|
2962, 2963, 2964, 2965, 2966, 2967, 2968, 2978, 3024, 3042, 3088,
|
||||||
|
3089, 3106, 3107, 3317, 3333, 3379, 3386, 3408, 3410, 3411, 3412,
|
||||||
|
3413, 3414, 3415, 7936, 7937, 7938, 10752, 32773, 32777, 32823, 32824,
|
||||||
|
32873, 32883, 32936, 32937, 32938, 32939, 32968, 32969, 32970, 32971,
|
||||||
|
33170, 33901, 33902, 34016, 34024, 34045, 34047, 34068, 34076, 34467,
|
||||||
|
34816, 34817, 34818, 34819, 34852, 34877, 34921, 34930, 34964, 34965,
|
||||||
|
35071, 35076, 35077, 35371, 35373, 35374, 35375, 35376, 35377, 35379,
|
||||||
|
35380, 35657, 35658, 35659, 35660, 35661, 35724, 35725, 35968, 35978,
|
||||||
|
35979, 36003, 36004, 36005, 36006, 36007, 36063, 36183, 36347, 36348,
|
||||||
|
36349, 37154, 37157, 37440, 37441, 37443, 37444, 37445, 37446,
|
||||||
|
];
|
||||||
|
|
||||||
|
let rendererInfo = null;
|
||||||
|
const debug_ext = webglContext.getExtension(
|
||||||
|
"WEBGL_debug_renderer_info"
|
||||||
|
);
|
||||||
|
if (debug_ext) {
|
||||||
|
rendererInfo = {
|
||||||
|
webglVendor: webglContext.getParameter(
|
||||||
|
debug_ext.UNMASKED_VENDOR_WEBGL
|
||||||
|
),
|
||||||
|
webglRenderer: webglContext.getParameter(
|
||||||
|
debug_ext.UNMASKED_RENDERER_WEBGL
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
[`${webglContextId}:renderer`]: rendererInfo
|
||||||
|
? rendererInfo.webglRenderer
|
||||||
|
: "Not available",
|
||||||
|
[`${webglContextId}:vendor`]: rendererInfo
|
||||||
|
? rendererInfo.webglVendor
|
||||||
|
: "Not available",
|
||||||
|
[`${webglContextId}:contextAttributes`]:
|
||||||
|
webglContext.getContextAttributes(),
|
||||||
|
[`${webglContextId}:supportedExtensions`]:
|
||||||
|
webglContext.getSupportedExtensions() || [],
|
||||||
|
[`${webglContextId}:parameters`]: {},
|
||||||
|
[`${webglContextId}:shaderPrecisionFormats`]: {},
|
||||||
|
};
|
||||||
|
for (const glEnum of glEnums) {
|
||||||
|
try {
|
||||||
|
const parmValue = webglContext.getParameter(glEnum);
|
||||||
|
if (
|
||||||
|
Array.isArray(parmValue) ||
|
||||||
|
[Float32Array, Int32Array, Uint32Array].some(
|
||||||
|
(type) => parmValue instanceof type
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const arrayValue = Array.from(parmValue);
|
||||||
|
result[`${webglContextId}:parameters`][glEnum] =
|
||||||
|
arrayValue.length > 0 ? arrayValue : null;
|
||||||
|
} else {
|
||||||
|
result[`${webglContextId}:parameters`][glEnum] = parmValue;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaderTypes = [
|
||||||
|
webglContext.VERTEX_SHADER,
|
||||||
|
webglContext.FRAGMENT_SHADER,
|
||||||
|
];
|
||||||
|
const precisionTypes = [
|
||||||
|
webglContext.LOW_FLOAT,
|
||||||
|
webglContext.MEDIUM_FLOAT,
|
||||||
|
webglContext.HIGH_FLOAT,
|
||||||
|
webglContext.LOW_INT,
|
||||||
|
webglContext.MEDIUM_INT,
|
||||||
|
webglContext.HIGH_INT,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let shaderType of shaderTypes) {
|
||||||
|
for (let precisionType of precisionTypes) {
|
||||||
|
const r = webglContext.getShaderPrecisionFormat(
|
||||||
|
shaderType,
|
||||||
|
precisionType
|
||||||
|
);
|
||||||
|
const key = `${shaderType},${precisionType}`;
|
||||||
|
result[`${webglContextId}:shaderPrecisionFormats`][key] = {
|
||||||
|
rangeMin: r.rangeMin,
|
||||||
|
rangeMax: r.rangeMax,
|
||||||
|
precision: r.precision,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function syntaxHighlight(json) {
|
||||||
|
json = json
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
return json.replace(
|
||||||
|
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
||||||
|
function (match) {
|
||||||
|
var cls = "number";
|
||||||
|
if (/^"/.test(match)) {
|
||||||
|
if (/:$/.test(match)) {
|
||||||
|
cls = "key";
|
||||||
|
} else {
|
||||||
|
cls = "string";
|
||||||
|
}
|
||||||
|
} else if (/true|false/.test(match)) {
|
||||||
|
cls = "boolean";
|
||||||
|
} else if (/null/.test(match)) {
|
||||||
|
cls = "null";
|
||||||
|
}
|
||||||
|
return '<span class="' + cls + '">' + match + "</span>";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sha256(message) {
|
||||||
|
const msgBuffer = new TextEncoder().encode(message);
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
const hashHex = hashArray
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"DOMContentLoaded",
|
||||||
|
async () => {
|
||||||
|
// Check if the user agent is Firefox
|
||||||
|
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||||
|
if (!isFirefox) {
|
||||||
|
document.getElementById('warning').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for close button
|
||||||
|
document.getElementById('close-warning').addEventListener('click', function() {
|
||||||
|
document.getElementById('warning').classList.add('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
const [webglDetail, webgl2Detail] = await Promise.all([
|
||||||
|
dumpWebGLCore("webgl", "experimental-webgl"),
|
||||||
|
dumpWebGLCore("webgl2", "experimental-webgl2"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const combinedInfo = { ...webglDetail, ...webgl2Detail };
|
||||||
|
const jsonString = JSON.stringify(combinedInfo, null, 2);
|
||||||
|
const minifiedJson = JSON.stringify(combinedInfo);
|
||||||
|
const hashValue = await sha256(minifiedJson);
|
||||||
|
|
||||||
|
document.getElementById("hash").textContent = `SHA256: ${hashValue}`;
|
||||||
|
document.getElementById("output").innerHTML =
|
||||||
|
syntaxHighlight(jsonString);
|
||||||
|
|
||||||
|
document.getElementById("copy-btn").addEventListener("click", () => {
|
||||||
|
navigator.clipboard.writeText(jsonString).then(() => {
|
||||||
|
alert("Copied formatted JSON to clipboard!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("copy-minified-btn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
navigator.clipboard.writeText(minifiedJson).then(() => {
|
||||||
|
alert("Copied minified JSON to clipboard!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -194,6 +194,9 @@ pref("layout.css.always_underline_links", true);
|
||||||
|
|
||||||
// No WebGL
|
// No WebGL
|
||||||
pref("webgl.disabled", true);
|
pref("webgl.disabled", true);
|
||||||
|
// Force software rendering
|
||||||
|
pref("webgl.forbid-hardware", true);
|
||||||
|
pref("webgl.forbid-software", false);
|
||||||
|
|
||||||
// Debloat pt.2 (from Librewolf)
|
// Debloat pt.2 (from Librewolf)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,6 @@
|
||||||
{ "property": "webrtc:ipv4", "type": "str" },
|
{ "property": "webrtc:ipv4", "type": "str" },
|
||||||
{ "property": "webrtc:ipv6", "type": "str" },
|
{ "property": "webrtc:ipv6", "type": "str" },
|
||||||
{ "property": "pdfViewerEnabled", "type": "bool" },
|
{ "property": "pdfViewerEnabled", "type": "bool" },
|
||||||
{ "property": "webGl:renderer", "type": "str" },
|
|
||||||
{ "property": "webGl:vendor", "type": "str" },
|
|
||||||
{ "property": "battery:charging", "type": "bool" },
|
{ "property": "battery:charging", "type": "bool" },
|
||||||
{ "property": "battery:chargingTime", "type": "double" },
|
{ "property": "battery:chargingTime", "type": "double" },
|
||||||
{ "property": "battery:dischargingTime", "type": "double" },
|
{ "property": "battery:dischargingTime", "type": "double" },
|
||||||
|
|
@ -64,6 +62,21 @@
|
||||||
{ "property": "locale:script", "type": "str" },
|
{ "property": "locale:script", "type": "str" },
|
||||||
{ "property": "humanize", "type": "bool" },
|
{ "property": "humanize", "type": "bool" },
|
||||||
{ "property": "humanize:maxTime", "type": "double" },
|
{ "property": "humanize:maxTime", "type": "double" },
|
||||||
|
{ "property": "humanize:minTime", "type": "double" },
|
||||||
{ "property": "showcursor", "type": "bool" },
|
{ "property": "showcursor", "type": "bool" },
|
||||||
|
{ "property": "AudioContext:sampleRate", "type": "uint" },
|
||||||
|
{ "property": "AudioContext:outputLatency", "type": "double" },
|
||||||
|
{ "property": "AudioContext:maxChannelCount", "type": "uint" },
|
||||||
|
{ "property": "webGl:renderer", "type": "str" },
|
||||||
|
{ "property": "webGl:vendor", "type": "str" },
|
||||||
|
{ "property": "webgl:supportedExtensions", "type": "array" },
|
||||||
|
{ "property": "webgl:parameters", "type": "dict" },
|
||||||
|
{ "property": "webgl2:parameters", "type": "dict" },
|
||||||
|
{ "property": "webgl:shaderPrecisionFormats", "type": "dict" },
|
||||||
|
{ "property": "webgl:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" },
|
||||||
|
{ "property": "webgl2:shaderPrecisionFormats", "type": "dict" },
|
||||||
|
{ "property": "webgl2:shaderPrecisionFormats:blockIfNotDefined", "type": "bool" },
|
||||||
|
{ "property": "webgl:contextAttributes", "type": "dict" },
|
||||||
|
{ "property": "webgl2:contextAttributes", "type": "dict" },
|
||||||
{ "property": "debug", "type": "bool" }
|
{ "property": "debug", "type": "bool" }
|
||||||
]
|
]
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
version=130.0.1
|
version=130.0.1
|
||||||
release=beta.11
|
release=beta.12
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue