From 43d5f961d4d2cb1a84c0b073a219fbaf5dc1e38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E7=BA=A2=E5=B2=A9?= Date: Fri, 6 Jan 2023 18:26:40 +0800 Subject: [PATCH] v2.2.1 Fixed Chinese input and gtk3 issues for linux packaging --- cef/cef-browser-config.go | 24 +++--- cef/cef-browser-window.go | 8 +- cef/cef-events-callback.go | 3 +- cef/cef-lcl-browser-window.go | 4 + cef/cef-views-framework-browser-window.go | 78 +++++++++++-------- cef/window.go | 1 + .../src/main-browser-window.go | 6 +- example/browser-linux/linux.go | 9 ++- example/mini-browser/src/min-browser.go | 39 +++++----- 9 files changed, 97 insertions(+), 75 deletions(-) diff --git a/cef/cef-browser-config.go b/cef/cef-browser-config.go index 73e020b..3cc937d 100644 --- a/cef/cef-browser-config.go +++ b/cef/cef-browser-config.go @@ -12,17 +12,17 @@ import ( "github.com/energye/energy/common" ) -type viewsFrameBrowserWindowOnEventCallback func(event *BrowserEvent, window IViewsFrameworkBrowserWindow) -type browserWindowOnEventCallback func(event *BrowserEvent, window ILCLBrowserWindow) -type browserWindowAfterOnEventCallback func(window ILCLBrowserWindow) +//type viewsFrameBrowserWindowOnEventCallback func(event *BrowserEvent, window IViewsFrameworkBrowserWindow) +type browserWindowOnEventCallback func(event *BrowserEvent, window IBrowserWindow) +type browserWindowAfterOnEventCallback func(window IBrowserWindow) //创建主窗口指定的一些快捷配置属性 type browserConfig struct { WindowProperty - chromiumConfig *tCefChromiumConfig //主窗体浏览器配置 - viewsFrameBrowserWindowOnEventCallback viewsFrameBrowserWindowOnEventCallback //主窗口初始化回调 - 基于CEF views framework窗口 - browserWindowOnEventCallback browserWindowOnEventCallback //主窗口初始化回调 - 基于LCL窗口 - browserWindowAfterOnEventCallback browserWindowAfterOnEventCallback //主窗口初始化之后回调 + chromiumConfig *tCefChromiumConfig //主窗体浏览器配置 + //viewsFrameBrowserWindowOnEventCallback viewsFrameBrowserWindowOnEventCallback //主窗口初始化回调 - 基于CEF views framework窗口 + browserWindowOnEventCallback browserWindowOnEventCallback //主窗口初始化回调 - 基于LCL窗口 + browserWindowAfterOnEventCallback browserWindowAfterOnEventCallback //主窗口初始化之后回调 } //设置chromium配置 @@ -42,11 +42,11 @@ func (m *browserConfig) ChromiumConfig() *tCefChromiumConfig { //主窗口初始化回调 - 基于CEF views framework窗口 // //该回调函数和基于LCL窗口回调是互斥的,默认情况只有一个会被回调 -func (m *browserConfig) setViewsFrameBrowserWindowOnEventCallback(fn viewsFrameBrowserWindowOnEventCallback) { - if fn != nil && common.Args.IsMain() { - m.viewsFrameBrowserWindowOnEventCallback = fn - } -} +//func (m *browserConfig) setViewsFrameBrowserWindowOnEventCallback(fn viewsFrameBrowserWindowOnEventCallback) { +// if fn != nil && common.Args.IsMain() { +// //m.viewsFrameBrowserWindowOnEventCallback = fn +// } +//} //主窗口初始化回调 - 基于LCL窗口 // diff --git a/cef/cef-browser-window.go b/cef/cef-browser-window.go index a3d77b8..671b55b 100644 --- a/cef/cef-browser-window.go +++ b/cef/cef-browser-window.go @@ -190,9 +190,9 @@ func (m *browser) MainWindow() *LCLBrowserWindow { // event 浏览器事件 // // views framework window 窗口信息对象 -func (m *browser) SetViewFrameBrowserInit(fn viewsFrameBrowserWindowOnEventCallback) { - m.Config.setViewsFrameBrowserWindowOnEventCallback(fn) -} +//func (m *browser) SetViewFrameBrowserInit(fn viewsFrameBrowserWindowOnEventCallback) { +// m.Config.setViewsFrameBrowserWindowOnEventCallback(fn) +//} // 基于LCL窗口 - 主窗口和chromium初始化时回调 // @@ -242,7 +242,7 @@ func (m *browser) GetNextWindowNum() int32 { return m.windowSerial } -func (m *browser) createNextPopupWindow() { +func (m *browser) createNextLCLPopupWindow() { m.popupWindow = NewLCLWindow(&BrowserWindow.Config.WindowProperty, m.MainWindow()) m.popupWindow.AsLCLBrowserWindow().BrowserWindow().defaultWindowCloseEvent() } diff --git a/cef/cef-events-callback.go b/cef/cef-events-callback.go index d714bf0..8314675 100644 --- a/cef/cef-events-callback.go +++ b/cef/cef-events-callback.go @@ -38,10 +38,9 @@ func chromiumOnBeforeBrowser(browser *ICefBrowser, frame *ICefFrame) { } BrowserWindow.setOrIncNextWindowNum(browser.Identifier() + 1) if IsMessageLoop { - } else { QueueAsyncCall(func(id int) { - BrowserWindow.createNextPopupWindow() + BrowserWindow.createNextLCLPopupWindow() }) } } diff --git a/cef/cef-lcl-browser-window.go b/cef/cef-lcl-browser-window.go index bf4452e..d56ccf0 100644 --- a/cef/cef-lcl-browser-window.go +++ b/cef/cef-lcl-browser-window.go @@ -295,6 +295,10 @@ func (m *LCLBrowserWindow) ChromiumCreate(config *tCefChromiumConfig, defaultUrl }) } +func (m *LCLBrowserWindow) WindowProperty() *WindowProperty { + return m.windowProperty +} + func (m *LCLBrowserWindow) putChromiumWindowInfo() { BrowserWindow.putWindowInfo(m.windowId, m) } diff --git a/cef/cef-views-framework-browser-window.go b/cef/cef-views-framework-browser-window.go index 56bb57a..97a100b 100644 --- a/cef/cef-views-framework-browser-window.go +++ b/cef/cef-views-framework-browser-window.go @@ -38,7 +38,7 @@ type ViewsFrameworkBrowserWindow struct { } //创建 ViewsFrameworkBrowserWindow 窗口 -func NewViewsFrameworkBrowserWindow(chromiumConfig *tCefChromiumConfig, windowProperty *WindowProperty) *ViewsFrameworkBrowserWindow { +func NewViewsFrameworkBrowserWindow(chromiumConfig *tCefChromiumConfig, windowProperty *WindowProperty, browserWindowOnEventCallback browserWindowOnEventCallback) *ViewsFrameworkBrowserWindow { if chromiumConfig == nil { chromiumConfig = BrowserWindow.Config.ChromiumConfig() } @@ -60,11 +60,14 @@ func NewViewsFrameworkBrowserWindow(chromiumConfig *tCefChromiumConfig, windowPr window.CenterWindow(NewCefSize(windowProperty.Width, windowProperty.Height)) } if windowProperty.IconFS != "" { - m.windowComponent.SetWindowAppIconFS(1, windowProperty.IconFS) + _ = m.windowComponent.SetWindowAppIconFS(1, windowProperty.IconFS) } else if windowProperty.Icon != "" { - m.windowComponent.SetWindowAppIcon(1, windowProperty.Icon) + _ = m.windowComponent.SetWindowAppIcon(1, windowProperty.Icon) } m.browserViewComponent.RequestFocus() + if browserWindowOnEventCallback != nil { + browserWindowOnEventCallback(BrowserWindow.browserEvent, m) + } m.windowComponent.Show() } }) @@ -98,18 +101,16 @@ func (m *browser) appContextInitialized(app *TCEFApplication) { return } app.SetOnContextInitialized(func() { - vFrameBrowserWindow := NewViewsFrameworkBrowserWindow(m.Config.ChromiumConfig(), &m.Config.WindowProperty) - //BrowserWindow.mainBrowserWindow.windowId = BrowserWindow.GetNextWindowNum() - //BrowserWindow.mainBrowserWindow.putChromiumWindowInfo() + vFrameBrowserWindow := NewViewsFrameworkBrowserWindow(m.Config.ChromiumConfig(), &m.Config.WindowProperty, m.Config.browserWindowOnEventCallback) + vFrameBrowserWindow.SetWindowType(consts.WT_POPUP_SUB_BROWSER) + vFrameBrowserWindow.windowId = BrowserWindow.GetNextWindowNum() + vFrameBrowserWindow.putChromiumWindowInfo() vFrameBrowserWindow.registerPopupEvent() vFrameBrowserWindow.registerDefaultEvent() vFrameBrowserWindow.windowComponent.SetOnCanClose(func(sender lcl.IObject, window *ICefWindow, aResult *bool) { *aResult = true app.QuitMessageLoop() }) - if m.Config.viewsFrameBrowserWindowOnEventCallback != nil { - m.Config.viewsFrameBrowserWindowOnEventCallback(m.browserEvent, vFrameBrowserWindow) - } vFrameBrowserWindow.windowComponent.CreateTopLevelWindow() }) } @@ -129,34 +130,35 @@ func (m *ViewsFrameworkBrowserWindow) registerPopupEvent() { return true } fmt.Println("BrowserWindow-TargetUrl:", beforePopupInfo.TargetUrl, "IsMessageLoop:", consts.IsMessageLoop) + wp := &WindowProperty{ + Title: BrowserWindow.Config.WindowProperty.Title, + Url: beforePopupInfo.TargetUrl, + CanMinimize: BrowserWindow.Config.WindowProperty.CanMinimize, + CanMaximize: BrowserWindow.Config.WindowProperty.CanMaximize, + CanResize: BrowserWindow.Config.WindowProperty.CanResize, + CanClose: BrowserWindow.Config.WindowProperty.CanClose, + CenterWindow: BrowserWindow.Config.WindowProperty.CenterWindow, + IsShowModel: BrowserWindow.Config.WindowProperty.IsShowModel, + WindowState: BrowserWindow.Config.WindowProperty.WindowState, + Icon: BrowserWindow.Config.WindowProperty.Icon, + IconFS: BrowserWindow.Config.WindowProperty.IconFS, + X: BrowserWindow.Config.WindowProperty.X, + Y: BrowserWindow.Config.WindowProperty.Y, + Width: BrowserWindow.Config.WindowProperty.Width, + Height: BrowserWindow.Config.WindowProperty.Height, + } + var vfbw = NewViewsFrameworkBrowserWindow(BrowserWindow.Config.ChromiumConfig(), wp, nil) var result = false if bwEvent.onBeforePopup != nil { - result = bwEvent.onBeforePopup(sender, browser, frame, beforePopupInfo, BrowserWindow.popupWindow, noJavascriptAccess) + result = bwEvent.onBeforePopup(sender, browser, frame, beforePopupInfo, vfbw, noJavascriptAccess) } if !result { - wp := &WindowProperty{ - Title: BrowserWindow.Config.WindowProperty.Title, - Url: beforePopupInfo.TargetUrl, - CanMinimize: BrowserWindow.Config.WindowProperty.CanMinimize, - CanMaximize: BrowserWindow.Config.WindowProperty.CanMaximize, - CanResize: BrowserWindow.Config.WindowProperty.CanResize, - CanClose: BrowserWindow.Config.WindowProperty.CanClose, - CenterWindow: BrowserWindow.Config.WindowProperty.CenterWindow, - IsShowModel: BrowserWindow.Config.WindowProperty.IsShowModel, - WindowState: BrowserWindow.Config.WindowProperty.WindowState, - Icon: BrowserWindow.Config.WindowProperty.Icon, - IconFS: BrowserWindow.Config.WindowProperty.IconFS, - X: BrowserWindow.Config.WindowProperty.X, - Y: BrowserWindow.Config.WindowProperty.Y, - Width: BrowserWindow.Config.WindowProperty.Width, - Height: BrowserWindow.Config.WindowProperty.Height, - } - vFrameBrowserWindow := NewViewsFrameworkBrowserWindow(BrowserWindow.Config.ChromiumConfig(), wp) - //BrowserWindow.mainBrowserWindow.windowId = BrowserWindow.GetNextWindowNum() - //BrowserWindow.mainBrowserWindow.putChromiumWindowInfo() - vFrameBrowserWindow.registerPopupEvent() - vFrameBrowserWindow.registerDefaultEvent() - vFrameBrowserWindow.windowComponent.CreateTopLevelWindow() + vfbw.SetWindowType(consts.WT_POPUP_SUB_BROWSER) + vfbw.windowId = BrowserWindow.GetNextWindowNum() + vfbw.putChromiumWindowInfo() + vfbw.registerPopupEvent() + vfbw.registerDefaultEvent() + vfbw.windowComponent.CreateTopLevelWindow() result = true } return result @@ -262,6 +264,14 @@ func (m *ViewsFrameworkBrowserWindow) registerDefaultEvent() { }) } +func (m *ViewsFrameworkBrowserWindow) WindowProperty() *WindowProperty { + return m.windowProperty +} + +func (m *ViewsFrameworkBrowserWindow) putChromiumWindowInfo() { + BrowserWindow.putWindowInfo(m.windowId, m) +} + func (m *ViewsFrameworkBrowserWindow) BrowserWindow() *ViewsFrameworkBrowserWindow { return m } @@ -275,7 +285,7 @@ func (m *ViewsFrameworkBrowserWindow) AsLCLBrowserWindow() ILCLBrowserWindow { } func (m *ViewsFrameworkBrowserWindow) SetTitle(title string) { - m.WindowComponent().SetTitle(title) + m.WindowProperty().Title = title } func (m *ViewsFrameworkBrowserWindow) getAuxTools() *auxTools { diff --git a/cef/window.go b/cef/window.go index 533b267..7d62a80 100644 --- a/cef/window.go +++ b/cef/window.go @@ -75,6 +75,7 @@ type IBrowserWindow interface { SetTitle(title string) IsViewsFramework() bool IsLCL() bool + WindowProperty() *WindowProperty } type ILCLBrowserWindow interface { diff --git a/example/browser-control/src/main-browser-window.go b/example/browser-control/src/main-browser-window.go index 2c0d1ff..d62d0f3 100644 --- a/example/browser-control/src/main-browser-window.go +++ b/example/browser-control/src/main-browser-window.go @@ -28,11 +28,11 @@ func MainBrowserWindow() { config.SetEnableDevTools(true) cef.BrowserWindow.Config.SetChromiumConfig(config) //创建窗口时的回调函数 对浏览器事件设置,和窗口属性组件等创建和修改 - cef.BrowserWindow.SetBrowserInit(func(event *cef.BrowserEvent, browserWindow cef.ILCLBrowserWindow) { + cef.BrowserWindow.SetBrowserInit(func(event *cef.BrowserEvent, browserWindow cef.IBrowserWindow) { //设置应用图标 这里加载的图标是内置到执行程序里的资源文件 lcl.Application.Icon().LoadFromFSFile("resources/icon.ico") //在窗体初始化时创建窗口内的组件 - back, forward, stop, refresh, progressLabel, addr := controlUI(browserWindow.BrowserWindow()) + back, forward, stop, refresh, progressLabel, addr := controlUI(browserWindow.AsLCLBrowserWindow().BrowserWindow()) //页面加载处理进度 event.SetOnLoadingProgressChange(func(sender lcl.IObject, browser *cef.ICefBrowser, progress float64) { //linux 更新UI组件必须使用 QueueAsyncCall 主线程异步同步 @@ -59,7 +59,7 @@ func MainBrowserWindow() { }) }) //创建窗口之后对对主窗口的属性、组件或子窗口的创建 - cef.BrowserWindow.SetBrowserInitAfter(func(browserWindow cef.ILCLBrowserWindow) { + cef.BrowserWindow.SetBrowserInitAfter(func(browserWindow cef.IBrowserWindow) { fmt.Println("SetBrowserInitAfter") }) } diff --git a/example/browser-linux/linux.go b/example/browser-linux/linux.go index 4339c4c..d4ad51a 100644 --- a/example/browser-linux/linux.go +++ b/example/browser-linux/linux.go @@ -23,7 +23,8 @@ func main() { //指定一个URL地址,或本地html文件目录 cef.BrowserWindow.Config.Url = "http://localhost:22022/index.html" cef.BrowserWindow.Config.IconFS = "resources/icon.png" - cef.BrowserWindow.SetViewFrameBrowserInit(func(event *cef.BrowserEvent, window cef.IViewsFrameworkBrowserWindow) { + + cef.BrowserWindow.SetBrowserInit(func(event *cef.BrowserEvent, window cef.IBrowserWindow) { fmt.Println("cef.BrowserWindow.SetViewFrameBrowserInit", window) fmt.Printf("%+v\n", window) event.SetOnBeforeContextMenu(func(sender lcl.IObject, browser *cef.ICefBrowser, frame *cef.ICefFrame, params *cef.ICefContextMenuParams, model *cef.ICefMenuModel) { @@ -32,6 +33,12 @@ func main() { event.SetOnContextMenuCommand(func(sender lcl.IObject, browser *cef.ICefBrowser, frame *cef.ICefFrame, params *cef.ICefContextMenuParams, commandId consts.MenuId, eventFlags uint32, result *bool) { fmt.Println("SetOnContextMenuCommand", commandId) }) + event.SetOnBeforePopup(func(sender lcl.IObject, browser *cef.ICefBrowser, frame *cef.ICefFrame, beforePopupInfo *cef.BeforePopupInfo, popupWindow cef.IBrowserWindow, noJavascriptAccess *bool) bool { + fmt.Println("IsViewsFramework:", popupWindow.IsViewsFramework()) + popupWindow.WindowProperty().Url = "https://www.csdn.net/" + popupWindow.SetTitle("修改了标题") + return false + }) }) //在主进程启动成功之后执行 //在这里启动内置http服务 diff --git a/example/mini-browser/src/min-browser.go b/example/mini-browser/src/min-browser.go index dfd49f7..43d9e11 100644 --- a/example/mini-browser/src/min-browser.go +++ b/example/mini-browser/src/min-browser.go @@ -180,7 +180,7 @@ func AppBrowserInit() { }) //主窗口初始化回调函数 - cef.BrowserWindow.SetBrowserInit(func(event *cef.BrowserEvent, browserWindow cef.ILCLBrowserWindow) { + cef.BrowserWindow.SetBrowserInit(func(event *cef.BrowserEvent, browserWindow cef.IBrowserWindow) { lcl.Application.SetOnMinimize(func(sender lcl.IObject) { fmt.Println("minimize") }) @@ -200,16 +200,17 @@ func AppBrowserInit() { //browserWindow.Window.Constraints().SetMinWidth(300) //browserWindow.Window.Constraints().SetMaxWidth(1600) //browserWindow.Window.Constraints().SetMaxHeight(900) - browserWindow.BrowserWindow().Constraints().SetOnChange(func(sender lcl.IObject) { + window := browserWindow.AsLCLBrowserWindow() + window.BrowserWindow().Constraints().SetOnChange(func(sender lcl.IObject) { fmt.Println("browserWindow SetOnChange") }) //添加事件,add不会覆盖默认的事件 set会覆盖默认的事件 - browserWindow.BrowserWindow().AddOnClose(func(sender lcl.IObject, action *types.TCloseAction) bool { + window.BrowserWindow().AddOnClose(func(sender lcl.IObject, action *types.TCloseAction) bool { fmt.Println("添加onclose事件") return false }) //窗口大小改变后触发 - browserWindow.BrowserWindow().AddOnResize(func(sender lcl.IObject) bool { + window.BrowserWindow().AddOnResize(func(sender lcl.IObject) bool { //Browser是在chromium加载完之后创建, 窗口创建时该对象还不存在 if browserWindow.Browser() != nil { var target = &cef.EmitTarget{ @@ -217,10 +218,10 @@ func AppBrowserInit() { FrameId: browserWindow.Browser().MainFrame().Id, } var argumentList = ipc.NewArgumentList() - argumentList.SetInt32(0, browserWindow.BrowserWindow().Left()) - argumentList.SetInt32(1, browserWindow.BrowserWindow().Top()) - argumentList.SetInt32(2, browserWindow.BrowserWindow().Width()) - argumentList.SetInt32(3, browserWindow.BrowserWindow().Height()) + argumentList.SetInt32(0, window.BrowserWindow().Left()) + argumentList.SetInt32(1, window.BrowserWindow().Top()) + argumentList.SetInt32(2, window.BrowserWindow().Width()) + argumentList.SetInt32(3, window.BrowserWindow().Height()) browserWindow.Chromium().Emit("window-resize", argumentList, target) browserWindow.Chromium().EmitAndCallback("window-resize", argumentList, target, func(context ipc.IIPCContext) { fmt.Println("EmitAndCallback AddOnResize") @@ -232,7 +233,7 @@ func AppBrowserInit() { return false }) //windows下可以使用这个函数,实时触发 - browserWindow.BrowserWindow().SetOnConstrainedResize(func(sender lcl.IObject, minWidth, minHeight, maxWidth, maxHeight *int32) { + window.BrowserWindow().SetOnConstrainedResize(func(sender lcl.IObject, minWidth, minHeight, maxWidth, maxHeight *int32) { //Browser是在chromium加载完之后创建, 窗口创建时该对象还不存在 if browserWindow.Browser() != nil { var target = &cef.EmitTarget{ @@ -240,10 +241,10 @@ func AppBrowserInit() { FrameId: browserWindow.Browser().MainFrame().Id, } var argumentList = ipc.NewArgumentList() - argumentList.SetInt32(0, browserWindow.BrowserWindow().Left()) - argumentList.SetInt32(1, browserWindow.BrowserWindow().Top()) - argumentList.SetInt32(2, browserWindow.BrowserWindow().Width()) - argumentList.SetInt32(3, browserWindow.BrowserWindow().Height()) + argumentList.SetInt32(0, window.BrowserWindow().Left()) + argumentList.SetInt32(1, window.BrowserWindow().Top()) + argumentList.SetInt32(2, window.BrowserWindow().Width()) + argumentList.SetInt32(3, window.BrowserWindow().Height()) browserWindow.Chromium().Emit("window-resize", argumentList, target) //使用EmitAndReturn函数会锁死 // browserWindow.Chromium.EmitAndCallback("window-resize", argumentList, target, func(context cef.IIPCContext) { @@ -252,11 +253,11 @@ func AppBrowserInit() { } }) //自定义browser窗体 - addressBar(browserWindow.BrowserWindow()) + addressBar(window.BrowserWindow()) //下载文件 //linux系统弹出保存对话框不启作用 //自己调用系统的保存对话框获得保存路径 - dlSave := lcl.NewSaveDialog(browserWindow.BrowserWindow()) + dlSave := lcl.NewSaveDialog(window.BrowserWindow()) dlSave.SetTitle("保存对话框标题") event.SetOnBeforeDownload(func(sender lcl.IObject, browser *cef.ICefBrowser, beforeDownloadItem *cef.DownloadItem, suggestedName string, callback *cef.ICefBeforeDownloadCallback) { fmt.Println("OnBeforeDownload:", beforeDownloadItem, suggestedName) @@ -281,7 +282,7 @@ func AppBrowserInit() { fmt.Println("OnFindResult:", browser.Identifier(), identifier, count, selectionRect, activeMatchOrdinal, finalUpdate) }) event.SetOnBeforePopup(func(sender lcl.IObject, browser *cef.ICefBrowser, frame *cef.ICefFrame, beforePopupInfo *cef.BeforePopupInfo, popupWindow cef.IBrowserWindow, noJavascriptAccess *bool) bool { - fmt.Println("OnBeforePopup: " + beforePopupInfo.TargetUrl) + fmt.Println("OnBeforePopup: "+beforePopupInfo.TargetUrl, "isLCL:", popupWindow.IsLCL()) window := popupWindow.AsLCLBrowserWindow().BrowserWindow() window.SetCaption("改变了标题 - " + beforePopupInfo.TargetUrl) //popupWindow.Form.SetBorderStyle(types.BsNone) @@ -381,13 +382,13 @@ func AppBrowserInit() { }) }) //添加子窗口初始化 - cef.BrowserWindow.SetBrowserInitAfter(func(browserWindow cef.ILCLBrowserWindow) { + cef.BrowserWindow.SetBrowserInitAfter(func(browserWindow cef.IBrowserWindow) { //在这里创建 一些子窗口 子组件 等 //托盘 if common.IsWindows() { - cefTray(browserWindow.BrowserWindow()) + cefTray(browserWindow.AsLCLBrowserWindow().BrowserWindow()) } else { - tray(browserWindow.BrowserWindow()) + tray(browserWindow.AsLCLBrowserWindow().BrowserWindow()) } }) } -- GitLab