Skip to content

Conversation

@RicardoTM05
Copy link
Contributor

Added OnFinishAction and OnPageLoadAction
Fixed a race condition when creating webview2.
See commits for more info on what changed.

Ps.
OnFinishAction and OnPageLoadAction names are not final and can be changed if you want.

OnFinishAction is triggered when CreateWebView2 finishes.
OnPageLoadAction is triggered whenever the current page finishes loading.

Added isCreationInProgress boolean and onFinishAction and onPageLoadAction
Added OnFinishAction and OnPageLoadAction handling and fixed a race condition when creating WebView by using the new isCreationInProgress boolean.
Added checks to prevent multiple creation attempts of WebView2 environment and controller. 
Removed unnecessary global meter and redraw bangs. 
Changed Initialized notice to debug.
Added onPageLoadAction and onFinishAction.
Re-ordered creation check
added check for initialization
added missing reset when hr fails.
callbackResult = result;

// Trigger Rainmeter redraw after callback completes
if (skin)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What for? I would strongly suggest to avoid triggering global meter updates and redraws from the plugin. That could lead to unwanted behavior for skin authors.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask why this was removed? This logic ensures that the skin redraws on the initial page load and that the initialization callback is executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there’s no need to hard code it. Skin authors can manually add those bangs if and when necessary. Some skin authors (including me) prefer to have total control of when their meters update and the skin redraws, hard coding to force update all meters and redraw can cause confusion and unwanted behavior.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic ensures that the skin redraws on the initial page load

If this (updating all meters and redraw the skin) is absolutely critical to correctly painting the skin, its meters, and the WebView control, then keep it. If not and that takes care on its own anyway (like it seems from the plugin version adjusted by RicardoTM, which works the same as yours in that regard after I tested it), then remove it.

Personally, I too think that users should have the freedom to control the updates / redraws as they see fit, if such actions are not esssential for having a functional code / plugin displaying correctly in the skin. Especially if the user is in fact offered the possibility of doing that, like here. If both efficiency and functionality are possible, then great!

So, I think that this revolves around having a specific case where not having all meters updated and the skin redrawn built into the plugin produced any kind of glitches / malfunction / unexpected behavior. If such a case doesn't exist, then this part of the code isn't really necessary. My two cents about it, anyway...

Copy link
Contributor Author

@RicardoTM05 RicardoTM05 Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for all tests I did, no, it doesn’t ensure anything. All demo skins load and work perfectly fine without those hard coded bangs. They are only executed if window.OnInitialize() is used (not empty).

In such case, the user can add them manually to the callback if needed (which they are not on such example):

window.OnInitialize = function() {
console.log("🚀 WebView initialized!");
RainmeterAPI.Bang('[!UpdateMeter *][!Redraw]');
return "Ready!"; // This becomes the measure's value
};

On the current plugin state, those bangs on the example above would execute twice, first the ones the user coded on the example above, then the hard coded ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I can see why you may think it is important. Because without it, on the JSInteraction.ini demo skin, the plugin's string value will not be displayed on the [MeterStatus] as soon as it's ready. However, the way I added OnPageLoadAction helps doing that if necessary.

The OnPageLoadAction waits and executes after the OnInitialize function, so simply adding
OnPageLoadAction=[!UpdateMeter *][!Redraw] will function exactly the same way for your example skin, without needing to hard code that at the plugin level.

isCreationInProgress = false;

if (rm)
RmLog(rm, LOG_NOTICE, L"WebView2: Initialized successfully with COM Host Objects");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using debug is less annoying than using notice, that way you only see it when debug mode is enabled.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I plan to change this to a Debug-level log in the final release. Right now, the default Rainmeter debug messages appear alongside ours, which makes the log harder to read and understand

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I agree to that decision 👍🏼


if (wcslen(onFinishAction.c_str()) > 0)
{
RmExecute(skin, onFinishAction.c_str());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm triggering it here, but it could be moved to the Reload() function after CreateWebView2() finishes, that way It would trigger whether CreateWebView() succeeds or not.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am planning to add support for OnPageCreateAction and OnPageReloadAction

Copy link
Contributor Author

@RicardoTM05 RicardoTM05 Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the OnFinishAction has nothing to do with the page state, it is more about when WebView has finished being created (or re-created).

It helps triggering an action when the measure number value goes from 0 to 1. It also allows to self update the measure (manually) when WebView2 is ready.

For example, when Update=-1 one can do:

[Rainmeter]
Update=-1

[WebView]

OnFinishAction=[!UpdateMeasure #CURRENTSECTION#][!Log “WebView2 has been initalized”]

However, it will only trigger if webview is created successfully, so it can either be left like that, and add an additional OnErrorAction which would trigger if WebView is not initialized for whatever reason (like missing webview2 runtime). Or, as I mentioned, it can be moved so OnFinishAction triggers either WebView initializes or not (I would recommend the first option for better control).

The other one I added is OnPageLoadAction. That one triggers every time the page loads, either first load or on reload. That way we can trigger the same bangs on every page load.

i’m ok with adding OnPageCreateAction, however not sure what would that be useful for.

OnPageReloadAction I think is unnecessary, when having OnPageLoadAction but I guess it doesn’t hurt to add.

I guess OnPageCreateAction would be triggered only on the page’s first load (I would suggest naming it OnFirstPageLoadAction instead if that is the case), OnPageReloadAction on the following reloads, and OnPageLoadAction on every load.
Those 3 would bring enough control for loading indeed.

@NSTechBytes
Copy link
Owner

Thanks for this PR. However, I have some concerns about it.

@RicardoTM05 RicardoTM05 marked this pull request as draft December 8, 2025 19:48
Removed redundant redraw calls after callback completion.
This was causing CallJS to execute twice..
{
RmExecute(measure->skin, L"!UpdateMeter *");
RmExecute(measure->skin, L"!Redraw");
}
Copy link
Contributor Author

@RicardoTM05 RicardoTM05 Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, we need to avoid these unnecessary global updates.
This was causing CallJS to execute twice when called. See JSInteraction.ini, it's being logged twice. Removing this fixed it.

Note: The 1 update delay on the result on that demo skin may be unavoidable. Other ways to fix that may be investigated anyway. However, I find it unlikely due to the calls being async, so we may have to live with it (not a big deal anyways).

We could try doing it synchronously instead, unfortunately WebView2 doesn't offer a synchronous ExecuteScript function, however, someone offered a solution that we could try. Yep, we would risk blocking rainmeter for heavy scripts, but I think lua does block rainmeter too when scripts are heavy, so it may not be such a big deal. We would do it synchronously only for ExecuteScript calls by the way.

Here’s another, pretty long conversation about the same topic.

@NSTechBytes
Copy link
Owner

NSTechBytes commented Dec 9, 2025

Notes on Global Updates and JS Interaction

I agree that avoiding global updates is necessary, but this leads to several issues:

  1. Using window.OnInitialize in JavaScript
    When calling window.OnInitialize and reading values from the section variables of the measure, we are not able to retrieve the latest data.

  2. Using window.OnUpdate in JavaScript
    When enabling window.OnUpdate and reading the section variables, the skin values do not match the values returned in JavaScript.

You can test all of these behaviors using the following file:

https://github.com/NSTechBytes/WebView2/blob/main/Resources/Skins/WebView2/%40Resources/JSInteraction/script.js

How to Test

  1. Comment out window.OnUpdate

    • Observe the behavior of window.OnInitialize.
    • Note that it does not return the latest values.
  2. Uncomment window.OnUpdate

    • Observe whether the values match the ones returned in Js

I want to see is it happening or solved.

@RicardoTM05
Copy link
Contributor Author

No, it’s not fixed. That can’t be fixed unless we somehow call ExecuteScript synchronously, which is not possible out of the box. See my note here: #7 (comment)

Not even your workaround fixes it. You thought it was fixed because it was redrawing the whole skin twice, which “fakes” a solution, but if you look at the actual string value of the measure, it is still delayed. Your workaround was actually causing a meter update and redraw loophole.

Ok, here's my attempt to solve the sync issue.

Unfortunately, all my attempts to add a similar solution for window.OnInitialize and CallJS failed, both create a deadlock when implemented this way.

However, this appears to solve the issue on window.OnUpdate which I think is the most important.

Please build my Working2 branch and test it, you should see now both Status: Update #N on Rainmeter and on WebView are correctly synced. As well as the Measure's string value.

Also add
OnPageLoadAction=[!UpdateMeter *][!Redraw]

on the WebView measure to correctly see the "Initialized!" message on the status string.
Copy link
Contributor Author

@RicardoTM05 RicardoTM05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, here's my attempt to solve the sync issue.

Unfortunately, all my attempts to add a similar solution for window.OnInitialize and CallJS failed, both create a deadlock when implemented this way.

However, this appears to solve the issue on window.OnUpdate which I think is the most important.

Please build my Working2 branch and test it, you should see now both Status: Update #N on Rainmeter and on WebView are correctly synced. As well as the Measure's string value.

Also add
OnPageLoadAction=[!UpdateMeter *][!Redraw]

on the WebView measure to correctly see the "Initialized!" message on the status string.

Screenshot 2025-12-08 232739

Note: It is very prompt to crash Rainmeter, so I don't really see it as a solution but as an experiment. My honest solution would be to actually live with the plugin having a delayed return, which would avoid a lot of headaches😅. I think any attempt of a solution will be pretty hacky and fragile.

@NSTechBytes
Copy link
Owner

Ok, here's my attempt to solve the sync issue.
[....]

Did you try it yourself? Is it crashing Rainmeter or not?

@Yincognyto
Copy link

You can test all of these behaviors using the following file:

I'd be glad to help in finding a solution to this, but I just can't understand what that file is supposed to do. Display 15 + 25 = 40 over and over again? How should I compare the behaviors in the two cases if all it does is return that sum, in the skin or the wbeview console? Sorry for being obtuse on this, but I don't get what I should look for or what the expected result would have to be.

Other than that, just a different perspective: if the WebView2 environment always works with async calls, why bother making it sync from the WebView2 side, and not put Rainmeter or the skin on hold until the WebView2 result is completed instead? If the mountain won't come to Muhammad, let Muhammad come to the mountain - no offense, it's just a saying that is widely used where I live, and captures the fact that there are always at least two ways to approach a problem, depending on which has the most chances to work or be implemented.

Another possibility would be to trigger the JS somewhat sooner so it has the time to return by the time the result should be displayed. Of course, messing with the updates in the skin or estimating the time needed for an operation to complete isn't exactly ideal, but well, if it can't be done otherwise...

@Yincognyto
Copy link

Yincognyto commented Dec 9, 2025

You can test all of these behaviors using the following file:

Here's a more natural way to do things...

JSInteraction.ini:
JSInteraction.txt
script.js:
script.js
Preview:
JSInteraction - Changed

The only thing I notice in both your 0.0.6 version of the plugin and the one in which RicardoTM added the OnPageLoadAction, is that occasionally computations are displayed twice in both the skin's WebView and the WebView console. The "Initialized!" message on the status string is not typically displayed (maybe it's replaced with the "Update #N" one too fast, or not displayed separately so it can be seen directly?), unless I do Ctrl +F5 to reload the page repeatedly and hope to see it. And that is with RicardoTM's version of the plugin, by the way (in which I think he removed the discussed couple of lines from the plugin code, if I understood this correctly).

P.S. After a while, the "Result from JS:" value is displayed as "0". Not sure why, and didn't bother investigating it.

@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 9, 2025

Ok, here's my attempt to solve the sync issue.
[....]

Did you try it yourself? Is it crashing Rainmeter or not?

Of course I tried it myself. It does crash Rainmeter if you try to interact with it while it’s loading. I’ll build it later and post it here just for the record. I will undo the changes afterwards.


[Deleted]

@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 9, 2025

I'd …

No, on the last version I sent you that’s not fixed yet.

To be clear. The issue is that the measure’s string value is not properly in sync with the returned value from JS, it’s delayed by one update cycle. The other issue is that the fix nstechbytes tried forces the skin to update all meters and redraw on every update, which causes the status meter to “sync” but it also causes it to draw twice on every update.

The fix I talked about earlier works but makes Rainmeter crash if you interact with the skin while WebView is loading.

Making the skin wait sounds like a solution but that’s not possible, because we are talking about the string value of the measure.

To be precise: Rainmeter asks the plugin to update, the update function on the plugin tells WebView to execute the JS’s window.OnUpdate function asynchronously. At this point, Rainmeter already returned the old value because it can’t wait until JS returns a value. So that value is returned until the next update. The same happens to the window.OnInitialize and the CallJS functions.

You can see it by comparing the value on the WebView window with the string value of the measure. While JS displays Update 5 the string value is returning Update 4.

There’s no way to execute the JS functions “sooner”, they are executed as soon as their plugin counterparts are executed, I mean, you can’t command JS to update before commanding the skin to update first.

I’ll build and send you the version without nstechbytes solutions for you to see it more clearly

Edit. Sent.

Removed synchronous script execution function and adjusted script execution logic in Update function.
@Yincognyto
Copy link

Yincognyto commented Dec 9, 2025

No, on the last version I sent you that’s not fixed yet.

To be clear. The issue is that the measure’s string value is not properly in sync with the returned value from JS, it’s delayed by one update cycle. The other issue is that the fix nstechbytes tried forces the skin to update all meters and redraw on every update, which causes the status meter to “sync” but it also causes it to draw twice on every update.

Ah, ok, I now see it. Phrasing things like "the [MeasureWebView] string value is different than the value displayed by Update #N in the skin" would have properly identified / named these things and helped in understanding it better - just saying, from my own perspective. There could be other measures in the skin, and other values returned from JS, and not identifying them specifically makes things unclear. Somewhat similar issue on the forum when folks don't name the skin they have issues with. ;)

That being said, I forgot to close the JSInteraction skin I posted before (along with the Rainmeter log window and the WebView console in DevTools) for a couple of hours, and when I came back the laptop's fans were going crazy - see the usages below:
Usage - Processes
Usage - Overall
Not sure if that's an issue in the plugin, an issue in the WebView2 environment, an issue in Rainmeter, or just me leaving the log and console windows open, but yeah, it's a bit concerning. Maybe you guys can reproduce it and see if it can be mitigated / alleviated / eliminated. I mean, it's a basic skin with no real animation, it isn't supposed to consume that much CPU. NsTechBytes and you should take a look at it, maybe you can identify what's causing it...

P.S. I was using the same plugin version you (RicardoTM) sent me, if that matters. Will test the version you sent me now later this evening.

EDIT: Unfortunately, it didn't occur to me to check it at the time I closed things, but I tend to think that this high usage might have been caused by the huge console list of all the printed values. I might be wrong though, not sure, as I didn't look closely if the usage got back to normal after closing the DevTools window...

@RicardoTM05
Copy link
Contributor Author

EDIT: Unfortunately, it didn't occur to me to check it at the time I closed things, but I tend to think that this high usage might have been caused by the huge console list of all the printed values. I might be wrong though, not sure, as I didn't look closely if the usage got back to normal after closing the DevTools window...

I'm pretty sure that was the cause. I've let it running for a while and I don't have any issue. Logs consume memory and cpu for sure.

@NSTechBytes NSTechBytes marked this pull request as ready for review December 10, 2025 03:39
@NSTechBytes NSTechBytes marked this pull request as draft December 10, 2025 03:40
@NSTechBytes
Copy link
Owner

Ricardo, do you have any plans to implement OnPageReloadAction?

@RicardoTM05
Copy link
Contributor Author

Ricardo, do you have any plans to implement OnPageReloadAction?

I do.

The ones I already have working are:
OnPageLoadAction
OnWebViewCreateAction (replaces OnFinishAction)
OnWebViewFailAction (I haven't tested this one yet as I need to make WebView fail, somehow)

I have planned:
OnPageReloadAction
OnPageFirstLoadAction (tentative)
OnPageLoadFailAction (tentative)

All names are subject to change, ideas are welcomed.
I'm thinking OnWebViewLoadAction sounds better than OnWebViewCreateAction, or maybe OnWebViewInitAction, I like the first one better though.

Ideas on other actions are also welcomed.

I also have a new option working called Raincontext which injects Yincognito's script to be able to drag the WebView window and open RM's context menu while holding Ctrl. If you don't like the option's name let me know as well.

@RicardoTM05
Copy link
Contributor Author

I have finished implementing:

OnPageFirstLoadAction
OnPageReloadAction

Will commit new changes soon.

@NSTechBytes
Copy link
Owner

Ricardo, do you have any plans to implement OnPageReloadAction?

I do.[....]

Thanks! After this, we will test all the changes and merge them into the branch.
For the value-sync issues, I think we should create a new PR and continue the discussion there, since this PR already contains many features.

Renamed `OnFinishAction` to `OnWebViewLoadAction`

Added actions:
`OnWebViewFailAction`
`OnPageFirstLoadAction`
`OnPageReloadAction`

Added `raincontext` and `isFirstLoad` booleans.

Added `UpdateRaincontext()` function.
@RicardoTM05 RicardoTM05 marked this pull request as draft December 10, 2025 23:08
@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 10, 2025

Ok, I'm going to elaborate on the issue I'm having so that way you can give ideas.

The main problem is that WebView has no way to detect what a reload is. So, I tried 2 main ways:

The first was based on user commands, which is not a good fit because there are many special cases were reloads are falsely detected/undetected.

The second one and more promising was comparing the previous URL to the new URL, however, there's an issue.
For example, If you navigate to https://google.com, that will set the current URL as https://google.com, then immediately it will re-direct to e.g. https://www.google.com/?zx=1765409491011&no_sw_cr=1 so that will be now set as the current URL. If you then reload the page, the URL changes again to https://www.google.com/?zx=1765409529620&no_sw_cr=1 (see the different numbers after ?zx=). So in this case, reloading the page would not be treated as a reload.

So what I tried next was to ignore all query parameters and base the decision on the new normalized URL.
However, that triggers the reload action while navigating on websites, for example, on the Rainmeter forums the URLs from one post to another look like this:
https://forum.rainmeter.net/viewtopic.php?t=45677 to https://forum.rainmeter.net/viewtopic.php?t=45657 so navigating from the first to the second post would be treated as a reload...

So I'm kind of stuck. I'm not a web expert, so maybe there is a list of queries that we can safely ignore, like the google ones. However if such a list exist, I believe it would be too extensive to implement.
Or, we leave it a little not so well done, or we remove the action.

Ideas?

Edit. The same applies to OnFirstLoadAction by the way, because both are related, if it's not a first load then it's a reload.
If we can't safely detect a reload we obviously can't safely detect a first load.
But, if we remove reload, for first load we can then change the definition a little, and say that a First Load is the first time you navigate to a website. So, if you go from github.com to youtube.com, then that is a first load, any navigation inside the same website wouldn't trigger it, only when the website changes.

@NSTechBytes
Copy link
Owner

The following WebView2 implementation helps reliably detect when a page is reloaded versus when a new navigation occurs.

This method compares the current navigation URL with the previously completed navigation URL:

  • Reload detected → User pressed F5, Ctrl+R, or clicked the refresh button (same URL loads again).
  • New navigation → User navigates to a different URL.

Code

// Track the last successfully loaded URL
std::wstring currentUrl;

webView->add_NavigationStarting(
    Callback<ICoreWebView2NavigationStartingEventHandler>(
        [&currentUrl](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
        {
            wil::unique_cotaskmem_string uri;
            args->get_Uri(&uri);

            if (uri.get())
            {
                if (std::wstring(uri.get()) == currentUrl)
                {
                    OutputDebugStringW(L"Reload detected!\n");
                }
                else
                {
                    OutputDebugStringW(L"New navigation\n");
                }
            }
            return S_OK;
        }).Get(),
    &token);

webView->add_NavigationCompleted(
    Callback<ICoreWebView2NavigationCompletedEventHandler>(
        [&currentUrl](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
        {
            wil::unique_cotaskmem_string source;
            sender->get_Source(&source);

            if (source.get())
            {
                currentUrl = source.get();
            }
            return S_OK;
        }).Get()

@RicardoTM05
Copy link
Contributor Author

The following WebView2 implementation…

Did you test it? That’s exactly what I’m already doing. Now do the test that I mentioned on my last post.

Go to google.com then reload the page, it won’t detect it as a reload because the URL changes every time you reload.

@NSTechBytes
Copy link
Owner

Did you test it? That’s exactly what I’m already doing. Now do the test that I mentioned on my last post.

Sure, I’ll test it soon.

@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 11, 2025

Well, I'm out for tonight. Here's the latest test build:

[Deleted]

Changes:

Actions

OnPageLoadStartAction
It triggers when the navigation starts.

OnPageLoadingAction
It triggers when the page starts loading.

OnPageLoadFinishAction
It triggers when navigation finishes.

OnPageFirstLoadAction
It triggers when navigation finishes and the URL you navigated to is different to the URL you navigated from

OnPageReloadAction
It triggers when navigation finishes and the URL you navigated to is the same as the URL you navigated from.

On WebView there are 3 navigation steps:

  1. Navigation starts (OnPageLoadStartAction )
  2. Page content starts loading (OnPageLoadingAction)
  3. Page content finishes loading and navigation finishes. (OnPageLoadFinishAction)

Please suggest the final names for at least these 3 actions, as I think all 3 will stay.

When navigation finishes OnPageLoadFinishAction will trigger. It will also trigger OnPageFirstLoadAction OR OnPageReloadAction depending on the URL conditions that are met.

I added a couple debug logs that print when the URL changes, and also print the current URL. I forgot to change them to Notice, and I'm already tired to do that, so make sure you have Debug mode enabled to see them.

Just as an experiment, now the plugin's measure returns the current URL. The measure will also self-update when the URL changes to show the current URL as it changes even when Update=-1.

Now Yincognito’s script won’t be injected if raincontext option is disabled.

Here's the BangCommand.ini skin that includes all actions:

BangCommand.ini
[Rainmeter]
Update=-1
AccurateText=1
DynamicWindowSize=1

[Metadata]
Name=BangCommand
Author=nstechbytes
Information=Demonstrates controlling WebView2 via !CommandMeasure bangs.
Version=0.0.6
License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0

[Variables]
WebURL=https://nstechbytes.pages.dev/
WebW=600
WebH=470
WebX=300
WebY=45
Hidden=0
ClickThrough=0
Raincontext=1

; ========================================
; Measure
; ========================================
[MeasureWebView]
Measure=Plugin
Plugin=WebView2
URL=#WebURL#
W=#WebW#
H=#WebH#
X=#WebX#
Y=#WebY#
Hidden=#Hidden#
ClickThrough=#ClickThrough#
Raincontext=#Raincontext#
OnWebViewLoadAction=[!log "WebView loaded succesfully!"]
OnWebViewFailAction=[!log "WebView failed :("]
OnPageFirstLoadAction=[!log "First time on this page!"]
OnPageLoadStartAction=[!log "Navigation has started!"]
OnPageLoadingAction=[!log "Page is loading!"]
OnPageLoadFinishAction=[!log "Navigation has finished!"]
OnPageReloadAction=[!log "Page has been reloaded!"]
DynamicVariables=1

; ========================================
; Background
; ========================================
[MeterBackground]
Meter=Shape
Shape=Rectangle 0,0,920,540,12 | FillColor 0,0,0,200 | StrokeWidth 0

[Title]
Meter=String
Text=Bang Command Demo
FontSize=16
FontColor=255,255,255,255
AntiAlias=1
X=25
Y=10

[StyleButtonText]
FontColor=255,255,255,255
FontSize=12
AntiAlias=1
W=250
H=25
StringAlign=CenterCenter
SolidColor=0,200,255,150

; ========================================
; Navigate to URL (Dynamic Variables)
; ========================================
[TxtNavigateGoogle]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to Google (via Variable)
X=150
Y=58
LeftMouseUpAction=[!SetVariable WebURL "https://www.google.com"][!UpdateMeasure MeasureWebView]

[TxtNavigateGitHub]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to GitHub (via Variable)
X=150
Y=88
LeftMouseUpAction=[!SetVariable WebURL "https://github.com"][!UpdateMeasure MeasureWebView]

[TxtNavigateForum]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to Forum (via Bang)
X=150
Y=118
LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://forum.rainmeter.net/"]

[TxtReload]
Meter=String
MeterStyle=StyleButtonText
Text=Reload
X=150
Y=148
LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"]

[TxtGoBack]
Meter=String
MeterStyle=StyleButtonText
Text=Go Back
W=122
H=24
X=86
Y=178
LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoBack"]

[TxtGoForward]
Meter=String
MeterStyle=StyleButtonText
Text=Go Forward
W=125
H=24
X=212
Y=178
LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoForward"]

[TxtShow]
Meter=String
MeterStyle=StyleButtonText
Text=Show
W=122
H=24
X=86
Y=208
LeftMouseUpAction=[!SetVariable Hidden 0][!UpdateMeasure MeasureWebView]

[TxtHide]
Meter=String
MeterStyle=StyleButtonText
Text=Hide
W=125
H=24
X=212
Y=208
LeftMouseUpAction=[!SetVariable Hidden 1][!UpdateMeasure MeasureWebView]

[TxtExecuteScript]
Meter=String
MeterStyle=StyleButtonText
Text=Execute Script
X=150
Y=238
LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript console.log('Script has been executed.')"]

[TxtOpenDevTools]
Meter=String
MeterStyle=StyleButtonText
Text=Open DevTools
X=150
Y=268
LeftMouseUpAction=[!CommandMeasure MeasureWebView "OpenDevTools"]

[TxtSetWidth]
Meter=String
MeterStyle=StyleButtonText
Text=Set Width: 500 (via Variable)
X=150
Y=298
LeftMouseUpAction=[!SetVariable WebW (#WebW#=600?500:600)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetHeight]
Meter=String
MeterStyle=StyleButtonText
Text=Set Height: 400 (via Variable)
X=150
Y=328
LeftMouseUpAction=[!SetVariable WebH (#WebH#=410?470:410)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetX]
Meter=String
MeterStyle=StyleButtonText
Text=Set X: 320 (via Variable)
X=150
Y=358
LeftMouseUpAction=[!SetVariable WebX (#WebX#=300?320:300)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetY]
Meter=String
MeterStyle=StyleButtonText
Text=Set Y: 50 (via Variable)
X=150
Y=388
LeftMouseUpAction=[!SetVariable WebY (#WebY#=45?50:45)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetEnableClickThrough]
Meter=String
MeterStyle=StyleButtonText
Text=Set ClickThrough: 1 (via Variable)
X=150
Y=418
LeftMouseUpAction=[!SetVariable ClickThrough 1][!UpdateMeasure MeasureWebView]

[TxtSetDisableClickThrough]
Meter=String
MeterStyle=StyleButtonText
Text=Set ClickThrough: 0 (via Variable)
X=150
Y=448
LeftMouseUpAction=[!SetVariable ClickThrough 0][!UpdateMeasure MeasureWebView]

[TxtSetEnableRaincontext]
Meter=String
MeterStyle=StyleButtonText
Text=Set Raincontext: 1 (via Variable)
X=150
Y=478
LeftMouseUpAction=[!SetVariable Raincontext 1][!UpdateMeasure MeasureWebView]

[TxtSetDisableRaincontext]
Meter=String
MeterStyle=StyleButtonText
Text=Set Raincontext: 0 (via Variable)
X=150
Y=508
LeftMouseUpAction=[!SetVariable Raincontext 0][!UpdateMeasure MeasureWebView]

To reproduce the issue with OnPageReloadAction:

  1. Load the skin
  2. Navigate to GitHub (notice that correctly triggers OnPageFirstLoadAction)
  3. Reload the page multiple times using the command, or F5, Ctrl+R (notice it correctly triggers OnPageReloadAction)
  4. Navigate to Google.com
  5. Reload the page again (notice it won't ever trigger OnPageReloadAction)

What's the issue?
Some websites change their URL even slightly on every visit | reload, which prevents us from detecting a Reload.

PS. Sorry, I haven't changed the name of Raincontext. I'm still waiting for NSTechBytes to agree with the changes.
PS2. Same for the action's names.

@NSTechBytes
Copy link
Owner

NSTechBytes commented Dec 11, 2025

I think all other actions are good. The RainContext must be changed to AllowDualControl otherwise, it seems the latest changes were not pushed.

Let's see what happens and try to solve the reload issue.

@Yincognyto
Copy link

Yincognyto commented Dec 11, 2025

Well, I'm out for tonight. Here's the latest test build:
[...]
PS. Sorry, I haven't changed the name of Raincontext. I'm still waiting for NSTechBytes to agree with the changes. PS2. Same for the action's names.

I think all other actions are good. The RainContext must be changed to AllowDualControl otherwise, it seems the latest changes were not pushed.
[...]
Let's see what happens and try to solve the reload issue.

Agreed as well, after testing the attached package - it all looks good, the names are fine, just the RainContext to AllowDualControl left to do (in the plugin and the skin) and probably the debug to notice stuff, from my point of view. I tried the page reload test suggested by Ricardo and for some reason I do get the reload message, since apparently the URL / measure's string value stays as "google.com" for me, without any query parameters for the session stuff afterwards.

That being said, the Google query parameters for the session issue does happen for me too occasionally, it's been around a month since I first noticed it (obviously didn't like it) and found what it looks like an explanation for it, see here. By the way, a different query in the URL usually leads to a different page / result, see the queries to get the weather.com's JSON - so I wouldn't worry too much about the specific google.com issue - let them sort it out (yeah, right).

Other than the minor Google issue, everything works fine for me - great work, both of you! Thank you both for all the time, effort and patience on this. Grateful for it, Ricardo - for sure you'll put these to good use in your own WebView2 skin / skins too!

EDIT: For the record, I don't think it was the plugin, but on the very first load of the skin / plugin, I got this (notice the blank):
WebView2 Blank
Again, I don't think it was the plugin, I get some strange behavior for some time now, where the mouse ceases to work and I have to either shut it down or remove / reattach the wifi sensor, or Ctrl+Alt+Del followed by cancelling the action via a couple of clicks, to get back to normal behavior. Incidentally, this coincided with the "blank" page issue. I also get erratic mouse scroll behavior, but that is unrelated.

@RicardoTM05
Copy link
Contributor Author

Alright, thanks for the link. It only happens to me on Edge, on Opera is all good. I’ll try the fix later.

I’ll change Raincontext to AllowDualControl and will remove the URL debugs. And change the other to notice.
Will also remove the URL from the plugin’s string value.

I’ll commit afterwards.

Well, not sure why you have those issues, maybe your Laptop is finally giving up :(

@Yincognyto
Copy link

Yincognyto commented Dec 11, 2025

Will also remove the URL from the plugin’s string value.

Any reason why? I kind of liked it... unless nstechbytes has other idea for the numeric and string value of the measure, I think they should return something useful (in the lack of a better choice, the URL seemed like a good choice for the string value, since you might not always know it beforehand, e.g. the google case; the meaning of the number value should be mentioned in the docs too, by the way). My opinion, at least.

@RicardoTM05
Copy link
Contributor Author

Will also remove the URL from the plugin’s string value.

Any reason why? I kind of liked it... unless nstechbytes has other idea for the numeric and string value of the measure, I think they should return something useful (in the lack of a better choice, the URL seemed like a good choice for the string value, since you might not always know it beforehand; the meaning of the number value should be mentioned in the docs too, by the way). My opinion, at least.

Because it was just for testing and it's out of the scope of this PR. Also it would crash with the window.OnInitialize and window.OnUpdate returns. So we need to take a decision about those 2 first.

My take on it is:
I would personally like to create a kind of state machine for the plugin, we can create a series of number values for every state, and also return a string value related to that, then when navigation is finished, return the current URL. Or, only return the state number values without a string value related to them, and only return the current URL on the string value.
This of course would imply that window.OnInitialize and window.OnUpdate don't let the user return anything.

But yeah, I do prefer the state machine option than letting the user return a delayed value. But we will discuss that on another PR to stay organized.

@Yincognyto
Copy link

Because it was just for testing and it's out of the scope of this PR. Also it would crash with the window.OnInitialize and window.OnUpdate returns.

Ah, ok then, you're right - I didn't think of that. I was just wondering if the user couldn't use the string value himself to handle the URL query strings like on google, as desired. Plus other situations where the URL was a computed value and you'd need to know the "real" / "eventual" URL that was loaded. Might be useful for cloudflare protected sites too, or redirections. But I understand what you're saying, and I don't want to abuse you in any way, you've done a lot until now. Up for you guys to decide on that whenever it's possible, you already know my opinion about it (from a skin designer perspective).

Raincontext is now called AllowDualControl. The script is no longer injected when the option is disabled.
Added new actions.
Added new event listeners for triggering new actions.
@RicardoTM05 RicardoTM05 marked this pull request as ready for review December 11, 2025 18:03
@RicardoTM05 RicardoTM05 changed the title Working2 Adding new options and actions. Dec 11, 2025
@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 11, 2025

This PR is now marked as ready.

SUMMARY OF CHANGES

Actions Added
WebView2 initialization process actions:

  • OnWebViewLoadAction
  • OnWebViewFailAction

Navigation state actions:

  • OnPageLoadStartAction
  • OnPageLoadingAction
  • OnPageLoadFinishAction
  • OnPageFirstLoadAction
  • OnPageReloadAction

Options Added

  • AllowDualControl
  • ZoomFactor

Other Changes

  • Removed meter updates and skin redraws.
  • Fixed a race condition when creating WebView2 that allowed it to be created more than once on a single run.
  • Now both Hidden and Clickthrough options can take negative and positive values without changing their logic.
  • AllowDualControl allows the user to drag the WebView window with Ctrl + Drag and to open the skin's context menu with Ctrl + RMB
  • ZoomFactor allows the user to set a zoom factor dynamically.

Known Issues

  • window.OnInitialize, window.OnUpdate JS functions and CallJS section variable will return with a 1 Update delay.
  • OnPageReloadAction won't trigger when the reloaded site's URL changes.

Build for testing
Will delete after merge
[Deleted]

Skin for testing

BangCommand.ini
[Rainmeter]
Update=-1
AccurateText=1
DynamicWindowSize=1

[Metadata]
Name=BangCommand
Author=nstechbytes
Information=Demonstrates controlling WebView2 via !CommandMeasure bangs.
Version=0.0.6
License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0

[Variables]
WebURL=https://nstechbytes.pages.dev/
WebW=600
WebH=500
WebX=300
WebY=45
ZoomFactor=1.0
Hidden=0
ClickThrough=0
AllowDualControl=1

; ========================================
; Measure
; ========================================
[MeasureWebView]
Measure=Plugin
Plugin=WebView2
URL=#WebURL#
W=#WebW#
H=#WebH#
X=#WebX#
Y=#WebY#
ZoomFactor=#ZoomFactor#
Hidden=#Hidden#
ClickThrough=#ClickThrough#
AllowDualControl=#AllowDualControl#
OnWebViewLoadAction=[!log "WebView loaded succesfully!"]
OnWebViewFailAction=[!log "WebView failed :("]
OnPageFirstLoadAction=[!log "First time on this page!"]
OnPageLoadStartAction=[!log "Navigation has started!"]
OnPageLoadingAction=[!log "Page is loading!"]
OnPageLoadFinishAction=[!log "Navigation has finished!"]
OnPageReloadAction=[!log "Page has been reloaded!"]
DynamicVariables=1

; ========================================
; Background
; ========================================
[MeterBackground]
Meter=Shape
Shape=Rectangle 0,0,920,570,12 | FillColor 0,0,0,200 | StrokeWidth 0

[Title]
Meter=String
Text=Bang Command Demo
FontSize=16
FontColor=255,255,255,255
AntiAlias=1
X=25
Y=10

[StyleButtonText]
FontColor=255,255,255,255
FontSize=12
AntiAlias=1
W=250
H=25
StringAlign=CenterCenter
SolidColor=0,200,255,150

; ========================================
; Navigate to URL (Dynamic Variables)
; ========================================
[TxtNavigateGoogle]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to Google (via Variable)
X=150
Y=58
LeftMouseUpAction=[!SetVariable WebURL "https://www.google.com"][!UpdateMeasure MeasureWebView]

[TxtNavigateGitHub]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to GitHub (via Variable)
X=150
Y=88
LeftMouseUpAction=[!SetVariable WebURL "https://github.com"][!UpdateMeasure MeasureWebView]

[TxtNavigateForum]
Meter=String
MeterStyle=StyleButtonText
Text=Navigate to Forum (via Bang)
X=150
Y=118
LeftMouseUpAction=[!CommandMeasure MeasureWebView "Navigate https://forum.rainmeter.net/"]

[TxtReload]
Meter=String
MeterStyle=StyleButtonText
Text=Reload
X=150
Y=148
LeftMouseUpAction=[!CommandMeasure MeasureWebView "Reload"]

[TxtGoBack]
Meter=String
MeterStyle=StyleButtonText
Text=Go Back
W=122
H=24
X=86
Y=178
LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoBack"]

[TxtGoForward]
Meter=String
MeterStyle=StyleButtonText
Text=Go Forward
W=125
H=24
X=212
Y=178
LeftMouseUpAction=[!CommandMeasure MeasureWebView "GoForward"]

[TxtShow]
Meter=String
MeterStyle=StyleButtonText
Text=Show
W=122
H=24
X=86
Y=208
LeftMouseUpAction=[!SetVariable Hidden 0][!UpdateMeasure MeasureWebView]

[TxtHide]
Meter=String
MeterStyle=StyleButtonText
Text=Hide
W=125
H=24
X=212
Y=208
LeftMouseUpAction=[!SetVariable Hidden 1][!UpdateMeasure MeasureWebView]

[TxtExecuteScript]
Meter=String
MeterStyle=StyleButtonText
Text=Execute Script
X=150
Y=238
LeftMouseUpAction=[!CommandMeasure MeasureWebView "ExecuteScript console.log('Script has been executed.')"]

[TxtOpenDevTools]
Meter=String
MeterStyle=StyleButtonText
Text=Open DevTools
X=150
Y=268
LeftMouseUpAction=[!CommandMeasure MeasureWebView "OpenDevTools"]

[TxtSetWidth]
Meter=String
MeterStyle=StyleButtonText
Text=Set Width: 500 (via Variable)
X=150
Y=298
LeftMouseUpAction=[!SetVariable WebW (#WebW#=600?500:600)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetHeight]
Meter=String
MeterStyle=StyleButtonText
Text=Set Height: 400 (via Variable)
X=150
Y=328
LeftMouseUpAction=[!SetVariable WebH (#WebH#=410?470:410)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetX]
Meter=String
MeterStyle=StyleButtonText
Text=Set X: 320 (via Variable)
X=150
Y=358
LeftMouseUpAction=[!SetVariable WebX (#WebX#=300?320:300)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetY]
Meter=String
MeterStyle=StyleButtonText
Text=Set Y: 50 (via Variable)
X=150
Y=388
LeftMouseUpAction=[!SetVariable WebY (#WebY#=45?50:45)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetZoom]
Meter=String
MeterStyle=StyleButtonText
Text=Set ZoomFactor: 2.0 (via Variable)
X=150
Y=418
LeftMouseUpAction=[!SetVariable ZoomFactor (#ZoomFactor#=1?2:1)][!UpdateMeasure MeasureWebView][!UpdateMeter #CURRENTSECTION#][!Redraw]
DynamicVariables=1

[TxtSetEnableClickThrough]
Meter=String
MeterStyle=StyleButtonText
Text=Set ClickThrough: 1 (via Variable)
X=150
Y=448
LeftMouseUpAction=[!SetVariable ClickThrough 1][!UpdateMeasure MeasureWebView]

[TxtSetDisableClickThrough]
Meter=String
MeterStyle=StyleButtonText
Text=Set ClickThrough: 0 (via Variable)
X=150
Y=478
LeftMouseUpAction=[!SetVariable ClickThrough 0][!UpdateMeasure MeasureWebView]

[TxtSetEnableAllowDualControl]
Meter=String
MeterStyle=StyleButtonText
Text=Set AllowDualControl: 1 (Variable)
X=150
Y=508
LeftMouseUpAction=[!SetVariable AllowDualControl 1][!UpdateMeasure MeasureWebView]

[TxtSetDisableAllowDualControl]
Meter=String
MeterStyle=StyleButtonText
Text=Set AllowDualControl: 0 (Variable)
X=150
Y=538
LeftMouseUpAction=[!SetVariable AllowDualControl 0][!UpdateMeasure MeasureWebView]

@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 11, 2025

Sorry, added ZoomFactor to please Rooky hehe. I'll update the previous post with most recent build and skin.

Edit: Done.

@Yincognyto
Copy link

Yincognyto commented Dec 11, 2025

Sorry, added ZoomFactor to please Rooky hehe. I'll update the previous post with most recent build and skin.

Edit: Done.

Good idea, in my view. Now you'd have to add an OnZoomFactorChangeAction action as well, to please him even more. ;)

P.S. Just joking on the last part - take a good rest, Ricardo, you deserve it!

@RicardoTM05
Copy link
Contributor Author

Good idea, in my view. Now you'd have to add an OnZoomFactorChangeAction action as well, to please him even more. ;)

P.S. Just joking on the last part - take a good rest, Ricardo, you deserve it!

shhhh! don't let him know that's possible!

@RicardoTM05
Copy link
Contributor Author

RicardoTM05 commented Dec 12, 2025

Hey @NSTechBytes, just to let you know I created a new branch with only 3 commits, if you prefer to close this one and merge that one let me know. It has the same code as this, only all on a single commit per file.

@NSTechBytes
Copy link
Owner

Hey @NSTechBytes, just to let you know [.....]

Sure, I will. First, I want to test these new changes. I will merge this PR soon.

@NSTechBytes
Copy link
Owner

One Question

Now both Hidden and Clickthrough options can take negative and positive values without changing their logic.

It means -1,1

@RicardoTM05
Copy link
Contributor Author

One Question

Now both Hidden and Clickthrough options can take negative and positive values without changing their logic.

It means -1,1

No, any value less or equal to 0 is false, and any positive value is true.
So, for example:
-3,-2,-1,0 = false
1,2,3 = true

Any negative value, or 0, will be false, and any positive value will be true.
Rainmeter's boolean options usually work that way, so I adjusted it to work the same way.

@NSTechBytes NSTechBytes merged commit 1db6d10 into NSTechBytes:working2 Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants