ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Chrome Extensions Samples 之 Multi-Tab Browser:基于 webview 的 New Window、User Agent 与权限请求综合示例解析

Chrome Extensions Samples 之 Multi-Tab Browser:基于 webview 的 New Window、User Agent 与权限请求综合示例解析 Chrome Extensions Samples 之 Multi-Tab Browser基于 webview 的 New Window、User Agent 与权限请求综合示例解析【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples本篇技术指南围绕 chrome-extensions-samples 仓库_archive/apps/samples/webview-samples/目录下的Multi-Tab Browser示例展开深入讲解它如何在一个 Chrome 应用中组合使用 webview 的New Window 事件newwindow、User Agent 覆盖setUserAgentOverride与权限请求事件permissionrequest实现一个具备多标签、弹窗确认、伪装移动端 UA、按站点授权等能力的嵌入式浏览器。读完本文你将掌握这套多标签 webview 浏览器的完整架构、各控制器的职责划分与消息传递机制并可直接基于仓库源码复现或改造出自己的浏览器型 Chrome 应用。一、示例定位两个历史示例的合体与一个新增特性原 README 对该示例的定位非常明确它演示了一种把 webview 的 New Window 事件与 User Agent 覆盖方法组合起来的做法。从构成上看它合并了两类示例New Window User Agent Samplewebview-samples/new-window系列负责把页面里window.open()、target_blank等触发的新窗口请求截获下来在应用内用新的 webview 承接并可临时替换 User AgentBrowser Samplewebview-samples/browser系列提供完整的浏览器外壳包括标签页、地址栏、前进/后退/刷新、页内查找find、缩放zoom、退出确认等。值得注意的限制是Multi-Tab Browser去掉了 Browser Sample 中的clearDataAPI 用法与模拟崩溃simulate crash功能并在此基础上新增了基于 webviewpermissionrequest事件的权限特性。这一点在仓库源码中可以直接验证browser.html 的 UI 中找不到任何 clearData 相关入口permission_box_controller.js 是独立新增的权限询问控制器manifest.json 的permissions仅声明了contextMenus与webview两项与权限特性通过 webview 事件而非全局权限实现的事实吻合。二、应用清单与启动流程先看 manifest.json{ manifest_version: 2, name: Multi-tabbed Browser, minimum_chrome_version: 35.0.0.0, version: 0.8, icons: { 16: icon_16.png, 128: icon_128.png }, app: { background: { scripts: [config.js, main.js] } }, permissions: [ contextMenus, webview ] }几个关键点这是一个Manifest V2 的 Chrome 应用Appminimum_chrome_version要求 35.0.0.0 及以上因为示例依赖的newwindow事件、setUserAgentOverride、permissionrequest等 API 在该版本附近才趋于稳定后台脚本只有两个config.js全局配置与main.js应用生命周期入口权限声明刻意精简contextMenus用于在 webview 内创建右键菜单webview是使用webview标签的前提。页面请求权限如摄像头、地理位置、通知并不需要预先在 manifest 里声明而是运行时通过permissionrequest事件逐个询问这正是本示例要演示的权限模型。启动流程位于 main.jschrome.app.runtime.onLaunched.addListener(function() { runApp(); }); chrome.app.runtime.onRestarted.addListener(function() { runApp(); }); function runApp() { chrome.app.window.create( browser.html, {id: initialBrowserWindowID}, function(newWindow) { // Do not inject meaningful window.newWindowEvent; browser will instead // load the homepage newWindow.contentWindow.newWindowEvent null; }); }要点应用被启动onLaunched或重启onRestarted时都会调用runApp()创建主窗口窗口创建成功回调里把newWindowEvent置为null表示首窗口不是由某个新窗口事件衍生而来随后浏览器会直接加载首页。这里使用全局变量window.newWindowEvent作为新窗口事件跨窗口传递的通道是理解整个弹窗流程的关键。浏览器主模块的入口在 browser_main.js页面load后创建webview元素并探测webview.contextMenusAPI 是否可用若不可用旧版 Chrome则显示Chrome Feature Not Supported提示框这与 browser.html 中预置的 lightbox 遮罩一一对应。三、集中式配置config.jsconfig.js 是全局配置单例被popup、contextMenu、browser等模块共同引用主要包含三块var config { homepage: https://www.google.com, popupConfirmBox: { innerHTML: This webpage would like to open a window at span classpopup-url/span Would you like to allow this popup? a href#popup-allow classpopup-allowAllow/a a href#popup-deny classpopup-denyDeny/a, urlSpanClass: popup-url, acceptLinkClass: popup-allow, denyLinkClass: popup-deny }, browsers: { android: Android, ios: iPhone, nokia: Nokia Mobile, bb-playbook: BlackBerry Playbook }, browserUserAgents: { android: Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30, ios: Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3, nokia: Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920), bb-playbook: Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2 (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2 } };三块配置的用途分别对应本示例的三大主题homepage新标签页/首窗口默认加载的地址也作为主页按钮的目标popupConfirmBox弹窗确认框的 HTML 模板与类名约定popup.js据此生成询问 UIbrowsers/browserUserAgents右键菜单中展示的伪装设备列表以及每种设备对应的 UA 字符串。菜单项按browsers动态生成context_menu.js在真正打开链接前把browserUserAgents中对应的 UA 一次性注入新 webview。从源码结构看把 UA 列表放配置而非硬编码在菜单逻辑里是为了让新增一种伪装设备变成纯配置改动只需在browsers与browserUserAgents中成对添加 key 即可右键菜单与注入逻辑无需任何修改。四、浏览器主控制器browser.js 与界面布局browser.js 定义了Browser类是整个应用的中枢。构造函数接收来自 browser_main.js 的一批 DOM 引用控制按钮、地址栏、标签容器、内容容器、各弹层并实例化四个控制器与一个标签列表this.findBoxController new findTool.FindController(findbox, this); this.zoomBoxController new zoomTool.ZoomController(zoombox, this); this.exitBoxController new exitTool.ExitController(exitbox, this); this.permissionBoxController new permissionTool.PermissionController(permissionbox, this); this.tabs new tabsModule.TabList(tabs, this, tabContainer, contentContainer, newTabElement);界面骨架在 browser.html 中定义#tab-controls标签栏、#browser-controls后退/前进/主页/刷新 地址表单 缩放/查找按钮、#zoom-box、#find-box、#exit-box退出确认、#permission-box权限询问以及动态挂载 webview 的#content-container。Browser.init()完成事件接线值得注意的实现细节加载动画与加载中/已加载状态切换reload按钮监听webkitAnimationIteration事件在动画迭代间隙检查当前标签是否还在加载若完成则移除loadingclass——这是用 CSS 动画事件驱动 UI 状态的小技巧全局消息监听窗口级message事件接收来自嵌入页guest的 JSON 消息目前只处理titleResponse页面标题回传未知类型会console.warn告警启动逻辑创建首个 webview 并追加为第一个标签然后根据外部注入的全局变量分流——window.userAgent存在则调用setUserAgentOverridewindow.newWindowEvent存在则event.window.attach(webview)把弹出窗口内容附着到新 webview否则直接导航到首页快捷键doKeyDownCtrlT新建标签、CtrlW关闭当前标签、CtrlF11keyCode 122切换全屏、Ctrl1~9快速切换标签地址栏命令navigateTo会先经handleInternalCommand处理特殊命令目前内置了browser://exit用于关闭窗口实现在 tabs.js 中动态布局doLayout把#content-container、webview 容器与 webview 本身统一设置为窗口高度 - 控件高度保证嵌入内容铺满剩余空间。五、标签管理核心tabs.js 的 TabList 与 Tabtabs.js 实现了两层数据结构TabList标签集合负责用数组list保存所有标签、用字典table按nametabs-0、tabs-1……索引标签append(webview)创建新标签生成自增名字、构造Tab、把标签 DOM 插入#tab-container插到#new-tab的按钮之前把 webview 容器追加进#content-containerremoveTab处理关闭逻辑最后一个标签被关闭时不销毁浏览器而是触发browser.closeBrowser()弹出退出确认框关闭当前标签时会自动选中相邻标签并修正this.selected索引selectTab负责旧标签deselect()、新标签select()并回调browser.doTabSwitch与browser.doLayoutsetLabelByName供外部titleResponse消息按标签名更新标题。Tab单个标签的核心职责initWebview()为每个 webview 注入内容脚本并绑定四类关键事件——loadcommit、loadstop、contentload、newwindow与permissionrequestdoLoadCommit只关心顶层导航e.isTopLevel更新url与加载状态并回调浏览器刷新地址栏doContentLoad触发一次titleRequest消息向嵌入页请求标题导航能力直接代理到 webview 方法goBack→webview.back()、goForward→forward()、doReload→reload()、stopNavigation→stop()、navigateTo→设置webview.src。嵌入内容脚本与跨窗口消息initWebview()里通过addContentScripts向每个 webview 注入 guest_messaging.jsmatches: [all_urls]run_at: document_start。这个嵌入页脚本实现了一个完整的标题回传 行为模拟协议收到titleRequest后绑定title元素的change事件此后每次页面标题变化都会自动postMessage回titleResponse并保证首次加载也主动上报一次[no title]兜底收到simulateCtrlClick时用initMouseEvent构造一个ctrlKey: true的鼠标点击事件派发到临时a元素上从而在嵌入页内模拟Ctrl点击在新标签打开收到simulatePopup时直接调用window.open(url, ..., width100,height100,left100,top100)模拟弹窗。这种宿主通过contentWindow.postMessage发指令、嵌入脚本在页面上下文执行的架构解决了宿主无法直接调用页面内 JS 的问题是 webview 应用中的通用通信范式。六、New Window 事件与弹窗确认popup.jswebview 的newwindow事件在Tab.doNewTab(e)中处理见 tabs.js其分支策略体现了浏览器级的行为设计if (dis new_background_tab || dis new_foreground_tab) { var newWebview dce(webview); e.window.attach(newWebview); // 把弹出的内容直接挂到新 webview ... var newTab this.tabList.append(newWebview); if (e.windowOpenDisposition new_foreground_tab) { this.tabList.selectTab(newTab); } } else { if (this.contextMenu.isOpening(url)) { this.contextMenu.doOpen(url); popupModule.createPopup(e, userAgent); } else { this.popupConfirmBoxList.append(e); // 走确认框流程 } }新后台标签/新前台标签e.windowOpenDisposition为new_background_tab/new_foreground_tab时对应页面中的 Ctrl点击、window.open带目标等直接创建 webview 并e.window.attach(newWebview)随后纳入标签管理前台标签还会自动选中其他弹窗形态若该 URL 是由右键菜单伪装 UA 打开触发的则直接调用createPopup弹新窗口否则弹出确认框PopupConfirmBox由用户决定 Allow 还是 Deny。popup.js 中的createPopup(event, userAgent)展示了跨窗口传递新窗口事件的正确姿势chrome.app.window.create( browser.html, function(newWindow) { // Pass new window event through global: window.newWindowEvent. newWindow.contentWindow.newWindowEvent event; if (userAgent) { newWindow.contentWindow.userAgent userAgent; } });注释点明了关键约束webview 的创建以及event.window.attach(webview)不能在这里旧窗口回调上下文执行必须在新窗口自身的上下文中进行。因此通过newWindow.contentWindow.newWindowEvent把事件对象搬运过去新窗口的Browser.init()browser.js会检测到它并执行window.newWindowEvent.window.attach(webview)。PopupConfirmBox.doDeny()则调用this.event.window.discard()丢弃弹窗请求。七、User Agent 覆盖context_menu.js 的一次性注入机制context_menu.js 利用webview.contextMenus在每个 webview 内创建了两组右键菜单对链接contexts: [link]提供Open link in new window as...与Open link in new tab as...两个父菜单每个父菜单下先是 Default browser 子项然后是分隔线接着按config.browsers动态生成 Android / iPhone / Nokia Mobile / BlackBerry Playbook 四个设备子项选择设备后doNewWindow(e, browser)/doNewTab(e, browser)会记录该 URL 与 UA 的对应关系并把操作指令通过postMessage发给嵌入页脚本模拟simulatePopup/simulateCtrlClick。UA 的注入采用**一次性表 延迟应用**机制ContextMenu.prototype.loadOnce function(url, browser) { var userAgent configModule.browserUserAgents[browser]; this.oneTimeUserAgentTable[url] userAgent; // 无条件记录存在即正在打开 }; ContextMenu.prototype.doOpen function(url, webview) { var userAgent this.oneTimeUserAgentTable[url]; if (webview userAgent) { webview.setUserAgentOverride(this.oneTimeUserAgentTable[url]); } delete this.oneTimeUserAgentTable[url]; // 用完即删保证只生效一次 };由于菜单点击后URL 的加载路径要经过模拟点击 →newwindow事件 → 确认/直接打开 → 新 webview 创建多个异步环节UA 不能立即绑定到目标 webview它还不存在。oneTimeUserAgentTable以 URL 为键暂存 UA后续在Tab.doNewTab新标签/新窗口场景与createPopup弹窗场景中通过getUserAgentOverride(url)读取并setUserAgentOverride到新 webview用完立即删除从而保证这次打开用这个 UA、下次打开回到默认的语义。八、权限请求处理permission_box_controller.js这是本示例新增的权限特性。当嵌入页请求敏感能力如摄像头、麦克风、地理位置、通知等时webview 会派发permissionrequest事件。tabs.js 中的处理是tab.webview.addEventListener(permissionrequest, function (e) { e.preventDefault(); // 阻止默认行为把决定权交给宿主 checkForPermissions(tab, e); }); function checkForPermissions(tab, e) { tab.tabList.browser.permissionBoxController.ifPermits( tab.webview.src, e.permission, function(result) { if (result ALLOW) { e.request.allow(); } else { e.request.deny(); } }); }permission_box_controller.js 实现了按主机名hostname记忆授权结果的权限控制器ifPermits(url, permission, callback)先查hostRecords命中则直接回调已有结论未命中则弹出#permission-box询问条展示The page at URL is asking for permission to use bpermission/b用户点Permit/Stop后addEntry把(host, permission) → ALLOW/DENY存入hostRecords再回调callback(result)最终由tabs.js调用e.request.allow()/deny()由于按getHostForURL解析a.href取hostname和permission类型双重索引同一站点对同一权限的选择会被记住后续请求不再打扰用户不同站点、不同权限相互独立。这套实现把webview 权限请求变成了一个可记忆、可交互的授权体系是理解 Chrome 应用内 webview 权限模型默认拦截 → 宿主决定 →request.allow/deny的绝佳参考。九、功能清单与限制综合 README 与源码本示例的能力汇总如下继承自 New Window User Agent Sample 的能力拦截页面newwindow事件支持新标签/新窗口两种承接方式通过右键菜单按链接选择用哪种设备的 UA 打开并对新 webview 一次性应用setUserAgentOverride。继承自 Browser Sample 的能力除clearData与模拟崩溃外多标签管理新建、关闭、Ctrl1~9切换、标签标题实时同步浏览器外壳地址栏导航、后退/前进按钮状态随canGoBack/canGoForward联动、刷新/停止、主页页内查找find与页面缩放zoom工具条退出确认框、窗口 resize 动态布局。本示例新增的能力permissionrequest事件驱动的权限询问默认拦截、弹条询问、按 主机×权限 记忆授权结果。限制原 README 注明参见 New Window Sample弹窗确认框尚不支持完整的window.open()name/attributes 语义例如同一窗口名多次window.open()、弹窗尺寸/位置等属性未完全管理popup.js 中有对应的 TODO 注释需要支持webview.contextMenus与相关 API 的 Chrome 35 环境旧版本会显示功能不支持提示该示例属于_archive归档目录采用 Manifest V2仅供学习参考不适用于当前 Manifest V3 扩展体系。十、如何运行与进一步探索打开chrome://extensions或旧版 Chrome 的chrome://apps对应入口开启开发者模式选择加载已解压的扩展程序指向本目录 multi-tab-browser需包含 manifest.json应用列表中出现 Multi-tabbed Browser 后点击启动即可体验多标签浏览、链接右键以设备 UA 打开与页面权限询问。若想深入对比可在同目录下继续阅读New Window 系列的 new-window 示例以及 webview-samples 目录下的 browser 等兄弟示例webview 相关 API 的官方参考可查阅 chrome 扩展应用文档 中的 webview 标签页。通过对照本示例中 browser.js、tabs.js、popup.js、context_menu.js、permission_box_controller.js 五份核心文件你可以完整复刻出标签化嵌入式浏览器 新窗口托管 移动端 UA 模拟 运行时权限决策这一整套实践方案。【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表