-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
minor: Store BaseObject reference in JavaScript and use BaseObject to…
… invoke functions in the object reference.
- Loading branch information
1 parent
3110a0f
commit db2e8c8
Showing
7 changed files
with
211 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
namespace WebExtension.Net | ||
{ | ||
/// <summary> | ||
/// Base API class. | ||
/// </summary> | ||
public class BaseAPI : BaseObject | ||
{ | ||
/// <summary> | ||
/// Gets the WebExtensionJsRuntime instance. | ||
/// </summary> | ||
protected WebExtensionJSRuntime webExtensionJSRuntime; | ||
|
||
internal BaseAPI(WebExtensionJSRuntime webExtensionJSRuntime, string apiNamespace) | ||
{ | ||
this.webExtensionJSRuntime = webExtensionJSRuntime; | ||
Initialize(webExtensionJSRuntime, "browser", apiNamespace); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,100 @@ | ||
namespace WebExtension.Net | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace WebExtension.Net | ||
{ | ||
/// <summary> | ||
/// Base object returned from JavaScript. | ||
/// </summary> | ||
public class BaseObject | ||
public class BaseObject : IDisposable | ||
{ | ||
internal bool IsInitialized; | ||
private WebExtensionJSRuntime webExtensionJSRuntime; | ||
private string referenceId; | ||
private string accessPath; | ||
|
||
internal void Initialize(WebExtensionJSRuntime webExtensionJSRuntime, string referenceId, string accessPath) | ||
{ | ||
if (!IsInitialized) | ||
{ | ||
IsInitialized = true; | ||
this.webExtensionJSRuntime = webExtensionJSRuntime; | ||
this.referenceId = referenceId; | ||
this.accessPath = accessPath; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Initialize property if it is a base object | ||
/// </summary> | ||
/// <param name="propertyName"></param> | ||
/// <param name="propertyValue"></param> | ||
protected void InitializeProperty(string propertyName, object propertyValue) | ||
{ | ||
if (propertyValue is BaseObject baseObject && !baseObject.IsInitialized) | ||
{ | ||
var propertyAccessPath = string.IsNullOrEmpty(accessPath) ? propertyName : $"{accessPath}.{propertyName}"; | ||
baseObject.Initialize(webExtensionJSRuntime, referenceId, propertyAccessPath); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Invokes the specified JavaScript function asynchronously. | ||
/// </summary> | ||
/// <param name="propertyName">The function to invoke.</param> | ||
/// <param name="args">JSON-serializable arguments.</param> | ||
/// <returns>An instance of TValue obtained by JSON-deserializing the return value.</returns> | ||
protected ValueTask<TValue> GetPropertyAsync<TValue>(string propertyName, params object[] args) | ||
{ | ||
var functionIdentifier = string.IsNullOrEmpty(accessPath) ? propertyName : $"{accessPath}.{propertyName}"; | ||
return webExtensionJSRuntime.InvokeAsync<TValue>("WebExtensionNet.InvokeOnObjectReference", new InvokeObjectReferenceOption(referenceId, functionIdentifier, false), args); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the WebExtensionJsRuntime instance. | ||
/// Invokes the specified JavaScript function asynchronously. | ||
/// </summary> | ||
protected WebExtensionJSRuntime webExtensionJSRuntime; | ||
/// <param name="function">The function to invoke.</param> | ||
/// <param name="args">JSON-serializable arguments.</param> | ||
/// <returns>An instance of TValue obtained by JSON-deserializing the return value.</returns> | ||
protected ValueTask<TValue> InvokeAsync<TValue>(string function, params object[] args) | ||
{ | ||
var functionIdentifier = string.IsNullOrEmpty(accessPath) ? function : $"{accessPath}.{function}"; | ||
return webExtensionJSRuntime.InvokeAsync<TValue>("WebExtensionNet.InvokeOnObjectReference", new InvokeObjectReferenceOption(referenceId, functionIdentifier, true), args); | ||
} | ||
|
||
/// <summary> | ||
/// Invokes the specified JavaScript function asynchronously. | ||
/// </summary> | ||
/// <param name="function">The function to invoke.</param> | ||
/// <param name="args">JSON-serializable arguments.</param> | ||
/// <returns>A System.Threading.Tasks.ValueTask that represents the asynchronous invocation operation.</returns> | ||
protected ValueTask InvokeVoidAsync(string function, params object[] args) | ||
{ | ||
var functionIdentifier = string.IsNullOrEmpty(accessPath) ? function : $"{accessPath}.{function}"; | ||
return webExtensionJSRuntime.InvokeVoidAsync("WebExtensionNet.InvokeOnObjectReference", new InvokeObjectReferenceOption(referenceId, functionIdentifier, true), args); | ||
} | ||
|
||
/// <summary> | ||
/// Dispose the object | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
if (!string.IsNullOrEmpty(referenceId) && webExtensionJSRuntime != null) | ||
{ | ||
#pragma warning disable CA2012 // Use ValueTasks correctly - Waiting is not supported in runtime | ||
webExtensionJSRuntime.InvokeVoidAsync("WebExtensionNet.RemoveObjectReference", new InvokeObjectReferenceOption(referenceId)); | ||
#pragma warning restore CA2012 // Use ValueTasks correctly | ||
referenceId = null; | ||
GC.SuppressFinalize(this); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Sets the WebExtensionJsRuntime instance. | ||
/// Finalizer to call Dispose() | ||
/// </summary> | ||
/// <param name="webExtensionJSRuntime">The WebExtensionJsRuntime instance.</param> | ||
public void SetClient(WebExtensionJSRuntime webExtensionJSRuntime) | ||
~BaseObject() | ||
{ | ||
this.webExtensionJSRuntime = webExtensionJSRuntime; | ||
Dispose(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
namespace WebExtension.Net | ||
{ | ||
/// <summary> | ||
/// Invoke JavaScript object options. | ||
/// </summary> | ||
public class InvokeObjectReferenceOption : InvokeOption | ||
{ | ||
internal InvokeObjectReferenceOption(string referenceId) : this(referenceId, null) { } | ||
|
||
internal InvokeObjectReferenceOption(string referenceId, string targetPath) : this(referenceId, targetPath, false) { } | ||
|
||
internal InvokeObjectReferenceOption(string referenceId, string targetPath, bool isFunction) | ||
{ | ||
ReferenceId = referenceId; | ||
TargetPath = targetPath; | ||
IsFunction = isFunction; | ||
} | ||
|
||
/// <summary> | ||
/// Reference ID of the JavaScript object. | ||
/// </summary> | ||
public string ReferenceId { get; set; } | ||
|
||
/// <summary> | ||
/// The target path of the JavaScript object. | ||
/// </summary> | ||
public string TargetPath { get; set; } | ||
|
||
/// <summary> | ||
/// The indicator if the target is a function. | ||
/// </summary> | ||
public bool IsFunction { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace WebExtension.Net | ||
{ | ||
/// <summary> | ||
/// Invoke JavaScript options. | ||
/// </summary> | ||
public abstract class InvokeOption | ||
{ | ||
/// <summary> | ||
/// The reference ID to be used as a key for the JavaScript object returned. | ||
/// </summary> | ||
public string ReturnObjectReferenceId { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 36 additions & 16 deletions
52
src/WebExtension.Net/content/WebExtensionScripts/WebExtensionNet.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,43 @@ | ||
(function (global) { | ||
global.WebExtensionNet = { | ||
Execute: async function (api, ...args) { | ||
let target = global.browser; | ||
api.split(".").forEach(api => target = target?.[api]); | ||
const objectReferences = { | ||
}; | ||
let objectReferencesCount = 0; | ||
async function invokeOnObjectReference({ referenceId, targetPath, isFunction, returnObjectReferenceId }, ...args) { | ||
let target = objectReferences[referenceId]; | ||
if (referenceId === "browser") { | ||
target = global.browser; | ||
} | ||
targetPath.split(".").forEach(functionName => target = target?.[functionName]); | ||
|
||
if (!target) { | ||
throw new Error(`Unable to find API browser.${api}`); | ||
} | ||
if (isFunction && !target) { | ||
throw new Error(`Unable to find function ${targetPath} on object '${referenceId}'.`); | ||
} | ||
|
||
try { | ||
let result = target.apply(target, args); | ||
if (result instanceof Promise) { | ||
result = await result; | ||
} | ||
return result; | ||
} catch (error) { | ||
console.error(api, args, target); | ||
throw new Error(`Failed to execute browser.${api}: ${error.message}`); | ||
try { | ||
let result = isFunction ? target.apply(target, args) : target; | ||
if (result instanceof Promise) { | ||
result = await result; | ||
} | ||
if (returnObjectReferenceId) { | ||
objectReferencesCount++; | ||
objectReferences[returnObjectReferenceId] = result; | ||
} | ||
return result; | ||
} catch (error) { | ||
console.error(referenceId, targetPath, args, target); | ||
throw new Error(`Failed to execute function ${targetPath} on object '${referenceId}': ${error.message}`); | ||
} | ||
} | ||
async function removeObjectReference({ referenceId }) { | ||
if (objectReferences[referenceId] !== null) { | ||
objectReferencesCount--; | ||
objectReferences[referenceId] = null; | ||
} | ||
} | ||
global.WebExtensionNet = { | ||
InvokeOnObjectReference: invokeOnObjectReference, | ||
RemoveObjectReference: removeObjectReference, | ||
GetObjectReferences: () => objectReferences, | ||
GetObjectReferencesCount: () => objectReferencesCount | ||
}; | ||
})(globalThis); |