Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions dotnet/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load("//dotnet:selenium-dotnet-version.bzl", "SUPPORTED_DEVTOOLS_VERSIONS")
load("//dotnet/private:dotnet_nunit_test_suite.bzl", _dotnet_nunit_test_suite = "dotnet_nunit_test_suite")
load("//dotnet/private:framework.bzl", _framework = "framework")
load("//dotnet/private:generate_devtools.bzl", _generate_devtools = "generate_devtools")
load("//dotnet/private:generate_resources.bzl", _generated_resource_utilities = "generated_resource_utilities")
load("//dotnet/private:generated_assembly_info.bzl", _generated_assembly_info = "generated_assembly_info")
load("//dotnet/private:nuget_pack.bzl", _nuget_pack = "nuget_pack")
load("//dotnet/private:nunit_test.bzl", _nunit_test = "nunit_test")
Expand All @@ -19,6 +20,7 @@ csharp_test = _csharp_test
dotnet_nunit_test_suite = _dotnet_nunit_test_suite
framework = _framework
generate_devtools = _generate_devtools
generated_resource_utilities = _generated_resource_utilities
generated_assembly_info = _generated_assembly_info
nuget_pack = _nuget_pack
nunit_test = _nunit_test
4 changes: 4 additions & 0 deletions dotnet/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
py_binary(
name = "generate_resources_tool",
srcs = ["generate_resources_tool.py"],
)
43 changes: 43 additions & 0 deletions dotnet/private/generate_resources.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Generate C# partial class with embedded JS resources via a Python tool."""

def _generate_resource_utilities_impl(ctx):
"""Invoke a Python script to generate ResourceUtilities.cs from input files.

The mapping from C# property name to JS file is provided explicitly via the
'resources' attribute as a dict: { "PropertyName": label }.
"""

args = ctx.actions.args()
args.add("--output", ctx.outputs.out)

inputs = []
for target, name in ctx.attr.resources.items():
files = target.files.to_list()
if len(files) != 1:
fail("Each resource label must produce exactly one file, got {} for {}".format(len(files), name))
src = files[0]
inputs.append(src)
args.add("--input")
args.add("%s=%s" % (name, src.path))

ctx.actions.run(
inputs = inputs,
outputs = [ctx.outputs.out],
executable = ctx.executable._tool,
arguments = [args],
mnemonic = "GenerateResourceUtilities",
progress_message = "Generating C# ResourceUtilities partial class",
)

generated_resource_utilities = rule(
implementation = _generate_resource_utilities_impl,
attrs = {
"resources": attr.label_keyed_string_dict(allow_files = True),
"out": attr.output(mandatory = True),
"_tool": attr.label(
default = Label("//dotnet/private:generate_resources_tool"),
executable = True,
cfg = "exec",
),
},
)
88 changes: 88 additions & 0 deletions dotnet/private/generate_resources_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Generate C# ResourceUtilities partial class with embedded JS resources.

Usage:
generate_resources_tool.py --output path/to/ResourceUtilities.g.cs \
--input Ident1=path/to/file1.js \
--input Ident2=path/to/file2.js ...

Each identifier becomes a const string in ResourceUtilities class.
The content is emitted as a C# raw string literal using 5-quotes.

TODO:
It would be nice to convert this small single-file utility to .NET10/C#,
so it would work like `dotnet run generate_resources.cs -- <args>`.
Meaning .NET developers can easily support it.
"""

import argparse
import os
import sys
from typing import List, Tuple


def parse_args(argv: List[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True)
parser.add_argument("--input", action="append", default=[], help="IDENT=path")
return parser.parse_args(argv)


def parse_input_spec(spec: str) -> Tuple[str, str]:
if "=" not in spec:
raise ValueError(f"Invalid --input value, expected IDENT=path, got: {spec}")
ident, path = spec.split("=", 1)
ident = ident.strip()
path = path.strip()
if not ident:
raise ValueError(f"Empty identifier in --input value: {spec}")
if not path:
raise ValueError(f"Empty path in --input value: {spec}")
return ident, path


def generate(output: str, inputs: List[Tuple[str, str]]) -> None:
props: List[str] = []
for prop_name, path in inputs:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
# Use a C# raw string literal with five quotes. For a valid raw
# literal, the content must start on a new line and the closing
# quotes must be on their own line as well. We assume the content
# does not contain a sequence of five consecutive double quotes.
#
# Resulting C# will look like:
# """""
# <content>
# """""
literal = '"""""\n' + content + '\n"""""'
props.append(f" internal const string {prop_name} = {literal};")

lines: List[str] = []
lines.append("// <auto-generated />")
lines.append("namespace OpenQA.Selenium.Internal;")
lines.append("")
lines.append("internal static partial class ResourceUtilities")
lines.append("{")
for p in props:
lines.append(p)
lines.append("}")
lines.append("")

os.makedirs(os.path.dirname(output), exist_ok=True)
with open(output, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(lines))


def main(argv: List[str]) -> int:
args = parse_args(argv)
inputs: List[Tuple[str, str]] = []
for spec in args.input:
ident, path = parse_input_spec(spec)
inputs.append((ident, path))
generate(args.output, inputs)
return 0


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
50 changes: 21 additions & 29 deletions dotnet/src/webdriver/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
load("//common:defs.bzl", "copy_file")
load("//dotnet:defs.bzl", "csharp_library", "devtools_version_targets", "framework", "generated_assembly_info", "nuget_pack")
load("//dotnet:defs.bzl", "csharp_library", "devtools_version_targets", "framework", "generated_assembly_info", "generated_resource_utilities", "nuget_pack")
load(
"//dotnet:selenium-dotnet-version.bzl",
"ASSEMBLY_COMPANY",
Expand All @@ -25,10 +25,23 @@ generated_assembly_info(
version = ASSEMBLY_VERSION,
)

generated_resource_utilities(
name = "resource-utilities",
out = "ResourceUtilities.g.cs",
resources = {
"//javascript/atoms/fragments:find-elements.js": "FindElementsAtom",
"//javascript/atoms/fragments:is-displayed.js": "IsDisplayedAtom",
"//javascript/cdp-support:mutation-listener.js": "MutationListenerAtom",
"//javascript/webdriver/atoms:get-attribute.js": "GetAttributeAtom",
"//third_party/js/selenium:webdriver_json": "WebDriverPrefsJson",
},
)

csharp_library(
name = "webdriver-netstandard2.0",
srcs = [
":assembly-info",
":resource-utilities",
] + glob([
"**/*.cs",
]) + devtools_version_targets(),
Expand All @@ -38,13 +51,7 @@ csharp_library(
],
langversion = "12.0",
nullable = "enable",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
"//javascript/cdp-support:mutation-listener.js",
"//javascript/webdriver/atoms:get-attribute.js",
"//third_party/js/selenium:webdriver_json",
],
resources = [],
target_frameworks = [
"netstandard2.0",
],
Expand All @@ -66,6 +73,7 @@ csharp_library(
name = "webdriver-net8.0",
srcs = [
":assembly-info",
":resource-utilities",
] + glob([
"**/*.cs",
]) + devtools_version_targets(),
Expand All @@ -78,13 +86,7 @@ csharp_library(
],
langversion = "12.0",
nullable = "enable",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
"//javascript/cdp-support:mutation-listener.js",
"//javascript/webdriver/atoms:get-attribute.js",
"//third_party/js/selenium:webdriver_json",
],
resources = [],
target_frameworks = [
"net8.0",
],
Expand All @@ -99,20 +101,15 @@ csharp_library(
name = "webdriver-netstandard2.0-strongnamed",
srcs = [
":assembly-info",
":resource-utilities",
] + glob([
"**/*.cs",
]) + devtools_version_targets(),
out = "WebDriver.StrongNamed",
keyfile = "//dotnet:Selenium.snk",
langversion = "12.0",
nullable = "enable",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
"//javascript/cdp-support:mutation-listener.js",
"//javascript/webdriver/atoms:get-attribute.js",
"//third_party/js/selenium:webdriver_json",
],
resources = [],
target_frameworks = [
"netstandard2.0",
],
Expand All @@ -134,6 +131,7 @@ csharp_library(
name = "webdriver-net8.0-strongnamed",
srcs = [
":assembly-info",
":resource-utilities",
] + glob([
"**/*.cs",
]) + devtools_version_targets(),
Expand All @@ -144,13 +142,7 @@ csharp_library(
keyfile = "//dotnet:Selenium.snk",
langversion = "12.0",
nullable = "enable",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
"//javascript/cdp-support:mutation-listener.js",
"//javascript/webdriver/atoms:get-attribute.js",
"//third_party/js/selenium:webdriver_json",
],
resources = [],
target_frameworks = [
"net8.0",
],
Expand Down
24 changes: 2 additions & 22 deletions dotnet/src/webdriver/Firefox/FirefoxExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text;
using System.Text.Json.Nodes;
using System.Xml;

Expand All @@ -38,7 +38,6 @@ public class FirefoxExtension
private const string JsonManifestFileName = "manifest.json";

private readonly string extensionFileName;
private readonly string extensionResourceId;

/// <summary>
/// Initializes a new instance of the <see cref="FirefoxExtension"/> class.
Expand All @@ -49,27 +48,8 @@ public class FirefoxExtension
/// then using the full path to the file, if a full path is provided.</remarks>
/// <exception cref="ArgumentNullException">If <paramref name="fileName"/> is <see langword="null"/>.</exception>
public FirefoxExtension(string fileName)
: this(fileName, string.Empty)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FirefoxExtension"/> class.
/// </summary>
/// <param name="fileName">The name of the file containing the Firefox extension.</param>
/// <param name="resourceId">The ID of the resource within the assembly containing the extension
/// if the file is not present in the file system.</param>
/// <remarks>WebDriver attempts to resolve the <paramref name="fileName"/> parameter
/// by looking first for the specified file in the directory of the calling assembly,
/// then using the full path to the file, if a full path is provided. If the file is
/// not found in the file system, WebDriver attempts to locate a resource in the
/// executing assembly with the name specified by the <paramref name="resourceId"/>
/// parameter.</remarks>
/// <exception cref="ArgumentNullException">If <paramref name="fileName"/> or <paramref name="resourceId"/> are <see langword="null"/>.</exception>
internal FirefoxExtension(string fileName, string resourceId)
{
this.extensionFileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
this.extensionResourceId = resourceId ?? throw new ArgumentNullException(nameof(resourceId));
}

/// <summary>
Expand All @@ -89,7 +69,7 @@ public void Install(string profileDirectory)

// First, expand the .xpi archive into a temporary location.
Directory.CreateDirectory(tempFileName);
Stream zipFileStream = ResourceUtilities.GetResourceStream(this.extensionFileName, $"{Assembly.GetExecutingAssembly().GetName().Name}.{this.extensionResourceId}");
using Stream zipFileStream = new MemoryStream(Encoding.UTF8.GetBytes(ResourceUtilities.WebDriverPrefsJson));
using (ZipArchive extensionZipArchive = new ZipArchive(zipFileStream, ZipArchiveMode.Read))
{
extensionZipArchive.ExtractToDirectory(tempFileName);
Expand Down
12 changes: 4 additions & 8 deletions dotnet/src/webdriver/Firefox/FirefoxProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text.Json;

namespace OpenQA.Selenium.Firefox;
Expand Down Expand Up @@ -298,15 +297,12 @@ private void UpdateUserPreferences(string profileDirectory)

private Preferences ReadDefaultPreferences()
{
using (Stream defaultPrefsStream = ResourceUtilities.GetResourceStream("webdriver_prefs.json", $"{Assembly.GetExecutingAssembly().GetName().Name}.webdriver_prefs.json"))
{
using JsonDocument defaultPreferences = JsonDocument.Parse(defaultPrefsStream);
using JsonDocument defaultPreferences = JsonDocument.Parse(ResourceUtilities.WebDriverPrefsJson);

JsonElement immutableDefaultPreferences = defaultPreferences.RootElement.GetProperty("frozen");
JsonElement editableDefaultPreferences = defaultPreferences.RootElement.GetProperty("mutable");
JsonElement immutableDefaultPreferences = defaultPreferences.RootElement.GetProperty("frozen");
JsonElement editableDefaultPreferences = defaultPreferences.RootElement.GetProperty("mutable");

return new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
}
return new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/webdriver/Internal/ResourceUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace OpenQA.Selenium.Internal;
/// <summary>
/// Encapsulates methods for finding and extracting WebDriver resources.
/// </summary>
internal static class ResourceUtilities
internal static partial class ResourceUtilities
{
private static string? productVersion;
private static string? platformFamily;
Expand Down
18 changes: 1 addition & 17 deletions dotnet/src/webdriver/JavaScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;

Expand Down Expand Up @@ -142,7 +140,7 @@ public void StopEventMonitoring()
public async Task EnableDomMutationMonitoring()
{
// Execute the script to have it enabled on the currently loaded page.
string script = GetMutationListenerScript();
string script = ResourceUtilities.MutationListenerAtom;
await this.session.Value.Domains.JavaScript.Evaluate(script).ConfigureAwait(false);

await this.AddScriptCallbackBinding(MonitorBindingName).ConfigureAwait(false);
Expand Down Expand Up @@ -409,20 +407,6 @@ private async Task EnableDomains()
}
}

private static string GetMutationListenerScript()
{
string listenerScript = string.Empty;
using (Stream resourceStream = ResourceUtilities.GetResourceStream("mutation-listener.js", $"{Assembly.GetExecutingAssembly().GetName().Name}.mutation-listener.js"))
{
using (StreamReader resourceReader = new StreamReader(resourceStream))
{
listenerScript = resourceReader.ReadToEnd();
}
}

return listenerScript;
}

private void OnScriptBindingCalled(object? sender, BindingCalledEventArgs e)
{
if (e.Name == MonitorBindingName)
Expand Down
Loading