github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/frontend.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package windows
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"log"
    11  	"net"
    12  	"net/url"
    13  	"os"
    14  	"runtime"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  	"time"
    19  
    20  	"github.com/bep/debounce"
    21  	"github.com/secoba/wails/v2/internal/binding"
    22  	"github.com/secoba/wails/v2/internal/frontend"
    23  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/win32"
    24  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc"
    25  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    26  	wailsruntime "github.com/secoba/wails/v2/internal/frontend/runtime"
    27  	"github.com/secoba/wails/v2/internal/logger"
    28  	"github.com/secoba/wails/v2/internal/system/operatingsystem"
    29  	"github.com/secoba/wails/v2/pkg/assetserver"
    30  	"github.com/secoba/wails/v2/pkg/assetserver/webview"
    31  	"github.com/secoba/wails/v2/pkg/options"
    32  	"github.com/secoba/wails/v2/pkg/options/windows"
    33  	"github.com/wailsapp/go-webview2/pkg/edge"
    34  )
    35  
    36  const startURL = "http://wails.localhost/"
    37  
    38  var secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
    39  
    40  type Screen = frontend.Screen
    41  
    42  type Frontend struct {
    43  
    44  	// Context
    45  	ctx context.Context
    46  
    47  	frontendOptions *options.App
    48  	logger          *logger.Logger
    49  	chromium        *edge.Chromium
    50  	debug           bool
    51  	devtoolsEnabled bool
    52  
    53  	// Assets
    54  	assets   *assetserver.AssetServer
    55  	startURL *url.URL
    56  
    57  	// main window handle
    58  	mainWindow *Window
    59  	bindings   *binding.Bindings
    60  	dispatcher frontend.Dispatcher
    61  
    62  	hasStarted bool
    63  
    64  	// Windows build number
    65  	versionInfo     *operatingsystem.WindowsVersionInfo
    66  	resizeDebouncer func(f func())
    67  }
    68  
    69  func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
    70  
    71  	// Get Windows build number
    72  	versionInfo, _ := operatingsystem.GetWindowsVersionInfo()
    73  
    74  	result := &Frontend{
    75  		frontendOptions: appoptions,
    76  		logger:          myLogger,
    77  		bindings:        appBindings,
    78  		dispatcher:      dispatcher,
    79  		ctx:             ctx,
    80  		versionInfo:     versionInfo,
    81  	}
    82  
    83  	if appoptions.Windows != nil {
    84  		if appoptions.Windows.ResizeDebounceMS > 0 {
    85  			result.resizeDebouncer = debounce.New(time.Duration(appoptions.Windows.ResizeDebounceMS) * time.Millisecond)
    86  		}
    87  	}
    88  
    89  	// We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url.
    90  	result.startURL, _ = url.Parse(startURL)
    91  
    92  	if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
    93  		result.startURL = _starturl
    94  		return result
    95  	}
    96  
    97  	if port, _ := ctx.Value("assetserverport").(string); port != "" {
    98  		result.startURL.Host = net.JoinHostPort(result.startURL.Host, port)
    99  	}
   100  
   101  	var bindings string
   102  	var err error
   103  	if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
   104  		bindings, err = appBindings.ToJSON()
   105  		if err != nil {
   106  			log.Fatal(err)
   107  		}
   108  	} else {
   109  		appBindings.DB().UpdateObfuscatedCallMap()
   110  	}
   111  
   112  	assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
   113  	if err != nil {
   114  		log.Fatal(err)
   115  	}
   116  	result.assets = assets
   117  
   118  	go result.startSecondInstanceProcessor()
   119  
   120  	return result
   121  }
   122  
   123  func (f *Frontend) WindowReload() {
   124  	f.ExecJS("runtime.WindowReload();")
   125  }
   126  
   127  func (f *Frontend) WindowSetSystemDefaultTheme() {
   128  	f.mainWindow.SetTheme(windows.SystemDefault)
   129  }
   130  
   131  func (f *Frontend) WindowSetLightTheme() {
   132  	f.mainWindow.SetTheme(windows.Light)
   133  }
   134  
   135  func (f *Frontend) WindowSetDarkTheme() {
   136  	f.mainWindow.SetTheme(windows.Dark)
   137  }
   138  
   139  func (f *Frontend) Run(ctx context.Context) error {
   140  	f.ctx = ctx
   141  
   142  	f.chromium = edge.NewChromium()
   143  
   144  	if f.frontendOptions.SingleInstanceLock != nil {
   145  		SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
   146  	}
   147  
   148  	mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium)
   149  	f.mainWindow = mainWindow
   150  
   151  	var _debug = ctx.Value("debug")
   152  	var _devtoolsEnabled = ctx.Value("devtoolsEnabled")
   153  
   154  	if _debug != nil {
   155  		f.debug = _debug.(bool)
   156  	}
   157  	if _devtoolsEnabled != nil {
   158  		f.devtoolsEnabled = _devtoolsEnabled.(bool)
   159  	}
   160  
   161  	f.WindowCenter()
   162  	f.setupChromium()
   163  
   164  	mainWindow.OnSize().Bind(func(arg *winc.Event) {
   165  		if f.frontendOptions.Frameless {
   166  			// If the window is frameless and we are minimizing, then we need to suppress the Resize on the
   167  			// WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong
   168  			// size during the restore animation and only fully renders when the animation is done. This highly
   169  			// depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319
   170  			event, _ := arg.Data.(*winc.SizeEventData)
   171  			if event != nil && event.Type == w32.SIZE_MINIMIZED {
   172  				return
   173  			}
   174  		}
   175  
   176  		if f.resizeDebouncer != nil {
   177  			f.resizeDebouncer(func() {
   178  				f.mainWindow.Invoke(func() {
   179  					f.chromium.Resize()
   180  				})
   181  			})
   182  		} else {
   183  			f.chromium.Resize()
   184  		}
   185  	})
   186  
   187  	mainWindow.OnClose().Bind(func(arg *winc.Event) {
   188  		if f.frontendOptions.HideWindowOnClose {
   189  			f.WindowHide()
   190  		} else {
   191  			f.Quit()
   192  		}
   193  	})
   194  
   195  	go func() {
   196  		if f.frontendOptions.OnStartup != nil {
   197  			f.frontendOptions.OnStartup(f.ctx)
   198  		}
   199  	}()
   200  	mainWindow.UpdateTheme()
   201  	return nil
   202  }
   203  
   204  func (f *Frontend) WindowClose() {
   205  	if f.mainWindow != nil {
   206  		f.mainWindow.Close()
   207  	}
   208  }
   209  
   210  func (f *Frontend) RunMainLoop() {
   211  	_ = winc.RunMainLoop()
   212  }
   213  
   214  func (f *Frontend) WindowCenter() {
   215  	runtime.LockOSThread()
   216  	defer runtime.UnlockOSThread()
   217  	f.mainWindow.Center()
   218  }
   219  
   220  func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
   221  	runtime.LockOSThread()
   222  	defer runtime.UnlockOSThread()
   223  	f.mainWindow.SetAlwaysOnTop(b)
   224  }
   225  
   226  func (f *Frontend) WindowSetPosition(x, y int) {
   227  	runtime.LockOSThread()
   228  	defer runtime.UnlockOSThread()
   229  	f.mainWindow.SetPos(x, y)
   230  }
   231  func (f *Frontend) WindowGetPosition() (int, int) {
   232  	runtime.LockOSThread()
   233  	defer runtime.UnlockOSThread()
   234  	return f.mainWindow.Pos()
   235  }
   236  
   237  func (f *Frontend) WindowSetSize(width, height int) {
   238  	runtime.LockOSThread()
   239  	defer runtime.UnlockOSThread()
   240  	f.mainWindow.SetSize(width, height)
   241  }
   242  
   243  func (f *Frontend) WindowGetSize() (int, int) {
   244  	runtime.LockOSThread()
   245  	defer runtime.UnlockOSThread()
   246  	return f.mainWindow.Size()
   247  }
   248  
   249  func (f *Frontend) WindowSetTitle(title string) {
   250  	runtime.LockOSThread()
   251  	defer runtime.UnlockOSThread()
   252  	f.mainWindow.SetText(title)
   253  }
   254  
   255  func (f *Frontend) WindowFullscreen() {
   256  	runtime.LockOSThread()
   257  	defer runtime.UnlockOSThread()
   258  	if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
   259  		f.ExecJS("window.wails.flags.enableResize = false;")
   260  	}
   261  	f.mainWindow.Fullscreen()
   262  }
   263  
   264  func (f *Frontend) WindowReloadApp() {
   265  	f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
   266  }
   267  
   268  func (f *Frontend) WindowUnfullscreen() {
   269  	runtime.LockOSThread()
   270  	defer runtime.UnlockOSThread()
   271  	if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
   272  		f.ExecJS("window.wails.flags.enableResize = true;")
   273  	}
   274  	f.mainWindow.UnFullscreen()
   275  }
   276  
   277  func (f *Frontend) WindowShow() {
   278  	runtime.LockOSThread()
   279  	defer runtime.UnlockOSThread()
   280  	f.ShowWindow()
   281  }
   282  
   283  func (f *Frontend) WindowHide() {
   284  	runtime.LockOSThread()
   285  	defer runtime.UnlockOSThread()
   286  	f.mainWindow.Hide()
   287  }
   288  
   289  func (f *Frontend) WindowMaximise() {
   290  	runtime.LockOSThread()
   291  	defer runtime.UnlockOSThread()
   292  	if f.hasStarted {
   293  		if !f.frontendOptions.DisableResize {
   294  			f.mainWindow.Maximise()
   295  		}
   296  	} else {
   297  		f.frontendOptions.WindowStartState = options.Maximised
   298  	}
   299  }
   300  
   301  func (f *Frontend) WindowToggleMaximise() {
   302  	runtime.LockOSThread()
   303  	defer runtime.UnlockOSThread()
   304  	if !f.hasStarted {
   305  		return
   306  	}
   307  	if f.mainWindow.IsMaximised() {
   308  		f.WindowUnmaximise()
   309  	} else {
   310  		f.WindowMaximise()
   311  	}
   312  }
   313  
   314  func (f *Frontend) WindowUnmaximise() {
   315  	runtime.LockOSThread()
   316  	defer runtime.UnlockOSThread()
   317  	if f.mainWindow.Form.IsFullScreen() {
   318  		return
   319  	}
   320  	f.mainWindow.Restore()
   321  }
   322  
   323  func (f *Frontend) WindowMinimise() {
   324  	runtime.LockOSThread()
   325  	defer runtime.UnlockOSThread()
   326  	if f.hasStarted {
   327  		f.mainWindow.Minimise()
   328  	} else {
   329  		f.frontendOptions.WindowStartState = options.Minimised
   330  	}
   331  }
   332  
   333  func (f *Frontend) WindowUnminimise() {
   334  	runtime.LockOSThread()
   335  	defer runtime.UnlockOSThread()
   336  	if f.mainWindow.Form.IsFullScreen() {
   337  		return
   338  	}
   339  	f.mainWindow.Restore()
   340  }
   341  
   342  func (f *Frontend) WindowSetMinSize(width int, height int) {
   343  	runtime.LockOSThread()
   344  	defer runtime.UnlockOSThread()
   345  	f.mainWindow.SetMinSize(width, height)
   346  }
   347  func (f *Frontend) WindowSetMaxSize(width int, height int) {
   348  	runtime.LockOSThread()
   349  	defer runtime.UnlockOSThread()
   350  	f.mainWindow.SetMaxSize(width, height)
   351  }
   352  
   353  func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
   354  	if col == nil {
   355  		return
   356  	}
   357  
   358  	f.mainWindow.Invoke(func() {
   359  		win32.SetBackgroundColour(f.mainWindow.Handle(), col.R, col.G, col.B)
   360  
   361  		controller := f.chromium.GetController()
   362  		controller2 := controller.GetICoreWebView2Controller2()
   363  
   364  		backgroundCol := edge.COREWEBVIEW2_COLOR{
   365  			A: col.A,
   366  			R: col.R,
   367  			G: col.G,
   368  			B: col.B,
   369  		}
   370  
   371  		// WebView2 only has 0 and 255 as valid values.
   372  		if backgroundCol.A > 0 && backgroundCol.A < 255 {
   373  			backgroundCol.A = 255
   374  		}
   375  
   376  		if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent {
   377  			backgroundCol.A = 0
   378  		}
   379  
   380  		err := controller2.PutDefaultBackgroundColor(backgroundCol)
   381  		if err != nil {
   382  			log.Fatal(err)
   383  		}
   384  	})
   385  
   386  }
   387  
   388  func (f *Frontend) ScreenGetAll() ([]Screen, error) {
   389  	var wg sync.WaitGroup
   390  	wg.Add(1)
   391  	screens := []Screen{}
   392  	err := error(nil)
   393  	f.mainWindow.Invoke(func() {
   394  		screens, err = GetAllScreens(f.mainWindow.Handle())
   395  		wg.Done()
   396  
   397  	})
   398  	wg.Wait()
   399  	return screens, err
   400  }
   401  
   402  func (f *Frontend) Show() {
   403  	f.mainWindow.Show()
   404  }
   405  
   406  func (f *Frontend) Hide() {
   407  	f.mainWindow.Hide()
   408  }
   409  
   410  func (f *Frontend) WindowIsMaximised() bool {
   411  	return f.mainWindow.IsMaximised()
   412  }
   413  
   414  func (f *Frontend) WindowIsMinimised() bool {
   415  	return f.mainWindow.IsMinimised()
   416  }
   417  
   418  func (f *Frontend) WindowIsNormal() bool {
   419  	return f.mainWindow.IsNormal()
   420  }
   421  
   422  func (f *Frontend) WindowIsFullscreen() bool {
   423  	return f.mainWindow.IsFullScreen()
   424  }
   425  
   426  func (f *Frontend) Quit() {
   427  	if f.frontendOptions.OnBeforeClose != nil && f.frontendOptions.OnBeforeClose(f.ctx) {
   428  		return
   429  	}
   430  	// Exit must be called on the Main-Thread. It calls PostQuitMessage which sends the WM_QUIT message to the thread's
   431  	// message queue and our message queue runs on the Main-Thread.
   432  	f.mainWindow.Invoke(winc.Exit)
   433  }
   434  
   435  func (f *Frontend) WindowPrint() {
   436  	f.ExecJS("window.print();")
   437  }
   438  
   439  func (f *Frontend) setupChromium() {
   440  	chromium := f.chromium
   441  
   442  	disableFeatues := []string{}
   443  	if !f.frontendOptions.EnableFraudulentWebsiteDetection {
   444  		disableFeatues = append(disableFeatues, "msSmartScreenProtection")
   445  	}
   446  
   447  	if opts := f.frontendOptions.Windows; opts != nil {
   448  		chromium.DataPath = opts.WebviewUserDataPath
   449  		chromium.BrowserPath = opts.WebviewBrowserPath
   450  
   451  		if opts.WebviewGpuIsDisabled {
   452  			chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu")
   453  		}
   454  		if opts.WebviewDisableRendererCodeIntegrity {
   455  			disableFeatues = append(disableFeatues, "RendererCodeIntegrity")
   456  		}
   457  	}
   458  
   459  	if len(disableFeatues) > 0 {
   460  		arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ","))
   461  		chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg)
   462  	}
   463  
   464  	chromium.MessageCallback = f.processMessage
   465  	chromium.WebResourceRequestedCallback = f.processRequest
   466  	chromium.NavigationCompletedCallback = f.navigationCompleted
   467  	chromium.AcceleratorKeyCallback = func(vkey uint) bool {
   468  		if vkey == w32.VK_F12 && f.devtoolsEnabled {
   469  			var keyState [256]byte
   470  			if w32.GetKeyboardState(keyState[:]) {
   471  				// Check if CTRL is pressed
   472  				if keyState[w32.VK_CONTROL]&0x80 != 0 && keyState[w32.VK_SHIFT]&0x80 != 0 {
   473  					chromium.OpenDevToolsWindow()
   474  					return true
   475  				}
   476  			} else {
   477  				f.logger.Error("Call to GetKeyboardState failed")
   478  			}
   479  		}
   480  		w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0)
   481  		return false
   482  	}
   483  	chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) {
   484  		kind, err := args.GetProcessFailedKind()
   485  		if err != nil {
   486  			f.logger.Error("GetProcessFailedKind: %s", err)
   487  			return
   488  		}
   489  
   490  		f.logger.Error("WebVie2wProcess failed with kind %d", kind)
   491  		switch kind {
   492  		case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED:
   493  			// => The app has to recreate a new WebView to recover from this failure.
   494  			messages := windows.DefaultMessages()
   495  			if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil {
   496  				messages = f.frontendOptions.Windows.Messages
   497  			}
   498  			winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash)
   499  			os.Exit(-1)
   500  		case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED,
   501  			edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED:
   502  			// => A new render process is created automatically and navigated to an error page.
   503  			// => Make sure that the error page is shown.
   504  			if !f.hasStarted {
   505  				// NavgiationCompleted didn't come in, make sure the chromium is shown
   506  				chromium.Show()
   507  			}
   508  			if !f.mainWindow.hasBeenShown {
   509  				// The window has never been shown, make sure to show it
   510  				f.ShowWindow()
   511  			}
   512  		}
   513  	}
   514  
   515  	chromium.Embed(f.mainWindow.Handle())
   516  
   517  	if chromium.HasCapability(edge.SwipeNavigation) {
   518  		swipeGesturesEnabled := f.frontendOptions.Windows != nil && f.frontendOptions.Windows.EnableSwipeGestures
   519  		err := chromium.PutIsSwipeNavigationEnabled(swipeGesturesEnabled)
   520  		if err != nil {
   521  			log.Fatal(err)
   522  		}
   523  	}
   524  	chromium.Resize()
   525  	settings, err := chromium.GetSettings()
   526  	if err != nil {
   527  		log.Fatal(err)
   528  	}
   529  	err = settings.PutAreDefaultContextMenusEnabled(f.debug || f.frontendOptions.EnableDefaultContextMenu)
   530  	if err != nil {
   531  		log.Fatal(err)
   532  	}
   533  	err = settings.PutAreDevToolsEnabled(f.devtoolsEnabled)
   534  	if err != nil {
   535  		log.Fatal(err)
   536  	}
   537  
   538  	if opts := f.frontendOptions.Windows; opts != nil {
   539  		if opts.ZoomFactor > 0.0 {
   540  			chromium.PutZoomFactor(opts.ZoomFactor)
   541  		}
   542  		err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled)
   543  		if err != nil {
   544  			log.Fatal(err)
   545  		}
   546  	}
   547  
   548  	err = settings.PutIsStatusBarEnabled(false)
   549  	if err != nil {
   550  		log.Fatal(err)
   551  	}
   552  	err = settings.PutAreBrowserAcceleratorKeysEnabled(false)
   553  	if err != nil {
   554  		log.Fatal(err)
   555  	}
   556  
   557  	if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup {
   558  		chromium.OpenDevToolsWindow()
   559  	}
   560  
   561  	// Setup focus event handler
   562  	onFocus := f.mainWindow.OnSetFocus()
   563  	onFocus.Bind(f.onFocus)
   564  
   565  	// Set background colour
   566  	f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour)
   567  
   568  	chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow)
   569  	chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
   570  	chromium.Navigate(f.startURL.String())
   571  }
   572  
   573  type EventNotify struct {
   574  	Name string        `json:"name"`
   575  	Data []interface{} `json:"data"`
   576  }
   577  
   578  func (f *Frontend) Notify(name string, data ...interface{}) {
   579  	notification := EventNotify{
   580  		Name: name,
   581  		Data: data,
   582  	}
   583  	payload, err := json.Marshal(notification)
   584  	if err != nil {
   585  		f.logger.Error(err.Error())
   586  		return
   587  	}
   588  	f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
   589  }
   590  
   591  func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
   592  	// Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but
   593  	// we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request.
   594  	if reqHeaders, err := req.GetHeaders(); err == nil {
   595  		useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent)
   596  		useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ")
   597  		reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent)
   598  		reqHeaders.Release()
   599  	}
   600  
   601  	if f.assets == nil {
   602  		// We are using the devServer let the WebView2 handle the request with its default handler
   603  		return
   604  	}
   605  
   606  	//Get the request
   607  	uri, _ := req.GetUri()
   608  	reqUri, err := url.ParseRequestURI(uri)
   609  	if err != nil {
   610  		f.logger.Error("Unable to parse equest uri %s: %s", uri, err)
   611  		return
   612  	}
   613  
   614  	if reqUri.Scheme != f.startURL.Scheme {
   615  		// Let the WebView2 handle the request with its default handler
   616  		return
   617  	} else if reqUri.Host != f.startURL.Host {
   618  		// Let the WebView2 handle the request with its default handler
   619  		return
   620  	}
   621  
   622  	webviewRequest, err := webview.NewRequest(
   623  		f.chromium.Environment(),
   624  		args,
   625  		func(fn func()) {
   626  			runtime.LockOSThread()
   627  			defer runtime.UnlockOSThread()
   628  			if f.mainWindow.InvokeRequired() {
   629  				var wg sync.WaitGroup
   630  				wg.Add(1)
   631  				f.mainWindow.Invoke(func() {
   632  					fn()
   633  					wg.Done()
   634  				})
   635  				wg.Wait()
   636  			} else {
   637  				fn()
   638  			}
   639  		})
   640  
   641  	if err != nil {
   642  		f.logger.Error("%s: NewRequest failed: %s", uri, err)
   643  		return
   644  	}
   645  
   646  	f.assets.ServeWebViewRequest(webviewRequest)
   647  }
   648  
   649  var edgeMap = map[string]uintptr{
   650  	"n-resize":  w32.HTTOP,
   651  	"ne-resize": w32.HTTOPRIGHT,
   652  	"e-resize":  w32.HTRIGHT,
   653  	"se-resize": w32.HTBOTTOMRIGHT,
   654  	"s-resize":  w32.HTBOTTOM,
   655  	"sw-resize": w32.HTBOTTOMLEFT,
   656  	"w-resize":  w32.HTLEFT,
   657  	"nw-resize": w32.HTTOPLEFT,
   658  }
   659  
   660  func (f *Frontend) processMessage(message string) {
   661  	if message == "drag" {
   662  		if !f.mainWindow.IsFullScreen() {
   663  			err := f.startDrag()
   664  			if err != nil {
   665  				f.logger.Error(err.Error())
   666  			}
   667  		}
   668  		return
   669  	}
   670  
   671  	if message == "runtime:ready" {
   672  		cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
   673  		f.ExecJS(cmd)
   674  		return
   675  	}
   676  
   677  	if strings.HasPrefix(message, "resize:") {
   678  		if !f.mainWindow.IsFullScreen() {
   679  			sl := strings.Split(message, ":")
   680  			if len(sl) != 2 {
   681  				f.logger.Info("Unknown message returned from dispatcher: %+v", message)
   682  				return
   683  			}
   684  			edge := edgeMap[sl[1]]
   685  			err := f.startResize(edge)
   686  			if err != nil {
   687  				f.logger.Error(err.Error())
   688  			}
   689  		}
   690  		return
   691  	}
   692  
   693  	go func() {
   694  		result, err := f.dispatcher.ProcessMessage(message, f)
   695  		if err != nil {
   696  			f.logger.Error(err.Error())
   697  			f.Callback(result)
   698  			return
   699  		}
   700  		if result == "" {
   701  			return
   702  		}
   703  
   704  		switch result[0] {
   705  		case 'c':
   706  			// Callback from a method call
   707  			f.Callback(result[1:])
   708  		default:
   709  			f.logger.Info("Unknown message returned from dispatcher: %+v", result)
   710  		}
   711  	}()
   712  }
   713  
   714  func (f *Frontend) Callback(message string) {
   715  	escaped, err := json.Marshal(message)
   716  	if err != nil {
   717  		panic(err)
   718  	}
   719  	f.mainWindow.Invoke(func() {
   720  		f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`)
   721  	})
   722  }
   723  
   724  func (f *Frontend) startDrag() error {
   725  	if !w32.ReleaseCapture() {
   726  		return fmt.Errorf("unable to release mouse capture")
   727  	}
   728  	// Use PostMessage because we don't want to block the caller until dragging has been finished.
   729  	w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
   730  	return nil
   731  }
   732  
   733  func (f *Frontend) startResize(border uintptr) error {
   734  	if !w32.ReleaseCapture() {
   735  		return fmt.Errorf("unable to release mouse capture")
   736  	}
   737  	// Use PostMessage because we don't want to block the caller until resizing has been finished.
   738  	w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0)
   739  	return nil
   740  }
   741  
   742  func (f *Frontend) ExecJS(js string) {
   743  	f.mainWindow.Invoke(func() {
   744  		f.chromium.Eval(js)
   745  	})
   746  }
   747  
   748  func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
   749  	if f.frontendOptions.OnDomReady != nil {
   750  		go f.frontendOptions.OnDomReady(f.ctx)
   751  	}
   752  
   753  	if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
   754  		f.ExecJS("window.wails.flags.enableResize = true;")
   755  	}
   756  
   757  	if f.hasStarted {
   758  		return
   759  	}
   760  	f.hasStarted = true
   761  
   762  	// Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
   763  	err := f.chromium.Hide()
   764  	if err != nil {
   765  		log.Fatal(err)
   766  	}
   767  	err = f.chromium.Show()
   768  	if err != nil {
   769  		log.Fatal(err)
   770  	}
   771  
   772  	if f.frontendOptions.StartHidden {
   773  		return
   774  	}
   775  
   776  	switch f.frontendOptions.WindowStartState {
   777  	case options.Maximised:
   778  		if !f.frontendOptions.DisableResize {
   779  			win32.ShowWindowMaximised(f.mainWindow.Handle())
   780  		} else {
   781  			win32.ShowWindow(f.mainWindow.Handle())
   782  		}
   783  	case options.Minimised:
   784  		win32.ShowWindowMinimised(f.mainWindow.Handle())
   785  	case options.Fullscreen:
   786  		f.mainWindow.Fullscreen()
   787  		win32.ShowWindow(f.mainWindow.Handle())
   788  	default:
   789  		if f.frontendOptions.Fullscreen {
   790  			f.mainWindow.Fullscreen()
   791  		}
   792  		win32.ShowWindow(f.mainWindow.Handle())
   793  	}
   794  
   795  	f.mainWindow.hasBeenShown = true
   796  
   797  }
   798  
   799  func (f *Frontend) ShowWindow() {
   800  	f.mainWindow.Invoke(func() {
   801  		if !f.mainWindow.hasBeenShown {
   802  			f.mainWindow.hasBeenShown = true
   803  			switch f.frontendOptions.WindowStartState {
   804  			case options.Maximised:
   805  				if !f.frontendOptions.DisableResize {
   806  					win32.ShowWindowMaximised(f.mainWindow.Handle())
   807  				} else {
   808  					win32.ShowWindow(f.mainWindow.Handle())
   809  				}
   810  			case options.Minimised:
   811  				win32.RestoreWindow(f.mainWindow.Handle())
   812  			case options.Fullscreen:
   813  				f.mainWindow.Fullscreen()
   814  				win32.ShowWindow(f.mainWindow.Handle())
   815  			default:
   816  				if f.frontendOptions.Fullscreen {
   817  					f.mainWindow.Fullscreen()
   818  				}
   819  				win32.ShowWindow(f.mainWindow.Handle())
   820  			}
   821  		} else {
   822  			if win32.IsWindowMinimised(f.mainWindow.Handle()) {
   823  				win32.RestoreWindow(f.mainWindow.Handle())
   824  			} else {
   825  				win32.ShowWindow(f.mainWindow.Handle())
   826  			}
   827  		}
   828  		w32.SetForegroundWindow(f.mainWindow.Handle())
   829  		w32.SetFocus(f.mainWindow.Handle())
   830  	})
   831  
   832  }
   833  
   834  func (f *Frontend) onFocus(arg *winc.Event) {
   835  	f.chromium.Focus()
   836  }
   837  
   838  func (f *Frontend) startSecondInstanceProcessor() {
   839  	for secondInstanceData := range secondInstanceBuffer {
   840  		if f.frontendOptions.SingleInstanceLock != nil &&
   841  			f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
   842  			f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
   843  		}
   844  	}
   845  }