1 #include "browser-panel-client.hpp"
2 #include <util/dstr.h>
3
4 #include <QUrl>
5 #include <QDesktopServices>
6 #include <QApplication>
7 #include <QMenu>
8 #include <QThread>
9
10 #include <obs-module.h>
11 #ifdef _WIN32
12 #include <windows.h>
13 #endif
14
15 /* CefClient */
GetLoadHandler()16 CefRefPtr<CefLoadHandler> QCefBrowserClient::GetLoadHandler()
17 {
18 return this;
19 }
20
GetDisplayHandler()21 CefRefPtr<CefDisplayHandler> QCefBrowserClient::GetDisplayHandler()
22 {
23 return this;
24 }
25
GetRequestHandler()26 CefRefPtr<CefRequestHandler> QCefBrowserClient::GetRequestHandler()
27 {
28 return this;
29 }
30
GetLifeSpanHandler()31 CefRefPtr<CefLifeSpanHandler> QCefBrowserClient::GetLifeSpanHandler()
32 {
33 return this;
34 }
35
GetContextMenuHandler()36 CefRefPtr<CefContextMenuHandler> QCefBrowserClient::GetContextMenuHandler()
37 {
38 return this;
39 }
40
GetKeyboardHandler()41 CefRefPtr<CefKeyboardHandler> QCefBrowserClient::GetKeyboardHandler()
42 {
43 return this;
44 }
45
46 /* CefDisplayHandler */
OnTitleChange(CefRefPtr<CefBrowser> browser,const CefString & title)47 void QCefBrowserClient::OnTitleChange(CefRefPtr<CefBrowser> browser,
48 const CefString &title)
49 {
50 if (widget && widget->cefBrowser->IsSame(browser)) {
51 std::string str_title = title;
52 QString qt_title = QString::fromUtf8(str_title.c_str());
53 QMetaObject::invokeMethod(widget, "titleChanged",
54 Q_ARG(QString, qt_title));
55 } else { /* handle popup title */
56 #ifdef _WIN32
57 std::wstring str_title = title;
58 HWND hwnd = browser->GetHost()->GetWindowHandle();
59 SetWindowTextW(hwnd, str_title.c_str());
60 #endif
61 }
62 }
63
64 /* CefRequestHandler */
OnBeforeBrowse(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame>,CefRefPtr<CefRequest> request,bool,bool)65 bool QCefBrowserClient::OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
66 CefRefPtr<CefFrame>,
67 CefRefPtr<CefRequest> request, bool,
68 bool)
69 {
70 std::string str_url = request->GetURL();
71
72 std::lock_guard<std::mutex> lock(popup_whitelist_mutex);
73 for (size_t i = forced_popups.size(); i > 0; i--) {
74 PopupWhitelistInfo &info = forced_popups[i - 1];
75
76 if (!info.obj) {
77 forced_popups.erase(forced_popups.begin() + (i - 1));
78 continue;
79 }
80
81 if (astrcmpi(info.url.c_str(), str_url.c_str()) == 0) {
82 /* Open tab popup URLs in user's actual browser */
83 QUrl url = QUrl(str_url.c_str(), QUrl::TolerantMode);
84 QDesktopServices::openUrl(url);
85 browser->GoBack();
86 return true;
87 }
88 }
89
90 if (widget) {
91 QString qt_url = QString::fromUtf8(str_url.c_str());
92 QMetaObject::invokeMethod(widget, "urlChanged",
93 Q_ARG(QString, qt_url));
94 }
95 return false;
96 }
97
OnOpenURLFromTab(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,const CefString & target_url,CefRequestHandler::WindowOpenDisposition,bool)98 bool QCefBrowserClient::OnOpenURLFromTab(
99 CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, const CefString &target_url,
100 CefRequestHandler::WindowOpenDisposition, bool)
101 {
102 std::string str_url = target_url;
103
104 /* Open tab popup URLs in user's actual browser */
105 QUrl url = QUrl(str_url.c_str(), QUrl::TolerantMode);
106 QDesktopServices::openUrl(url);
107 return true;
108 }
109
OnLoadError(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefLoadHandler::ErrorCode errorCode,const CefString & errorText,const CefString & failedUrl)110 void QCefBrowserClient::OnLoadError(CefRefPtr<CefBrowser> browser,
111 CefRefPtr<CefFrame> frame,
112 CefLoadHandler::ErrorCode errorCode,
113 const CefString &errorText,
114 const CefString &failedUrl)
115 {
116 UNUSED_PARAMETER(browser);
117 if (errorCode == ERR_ABORTED)
118 return;
119
120 struct dstr html;
121 char *path = obs_module_file("error.html");
122 char *errorPage = os_quick_read_utf8_file(path);
123
124 dstr_init_copy(&html, errorPage);
125
126 dstr_replace(&html, "%%ERROR_URL%%", failedUrl.ToString().c_str());
127
128 dstr_replace(&html, "Error.Title", obs_module_text("Error.Title"));
129 dstr_replace(&html, "Error.Description",
130 obs_module_text("Error.Description"));
131 dstr_replace(&html, "Error.Retry", obs_module_text("Error.Retry"));
132 const char *translError;
133 std::string errorKey = "ErrorCode." + errorText.ToString();
134 if (obs_module_get_string(errorKey.c_str(),
135 (const char **)&translError)) {
136 dstr_replace(&html, "%%ERROR_CODE%%", translError);
137 } else {
138 dstr_replace(&html, "%%ERROR_CODE%%",
139 errorText.ToString().c_str());
140 }
141
142 frame->LoadURL(
143 "data:text/html;base64," +
144 CefURIEncode(CefBase64Encode(html.array, html.len), false)
145 .ToString());
146
147 dstr_free(&html);
148 bfree(path);
149 bfree(errorPage);
150 }
151
152 /* CefLifeSpanHandler */
OnBeforePopup(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,const CefString & target_url,const CefString &,CefLifeSpanHandler::WindowOpenDisposition,bool,const CefPopupFeatures &,CefWindowInfo & windowInfo,CefRefPtr<CefClient> &,CefBrowserSettings &,CefRefPtr<CefDictionaryValue> &,bool *)153 bool QCefBrowserClient::OnBeforePopup(
154 CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, const CefString &target_url,
155 const CefString &, CefLifeSpanHandler::WindowOpenDisposition, bool,
156 const CefPopupFeatures &, CefWindowInfo &windowInfo,
157 CefRefPtr<CefClient> &, CefBrowserSettings &,
158 #if CHROME_VERSION_BUILD >= 3770
159 CefRefPtr<CefDictionaryValue> &,
160 #endif
161 bool *)
162 {
163 if (allowAllPopups) {
164 #ifdef _WIN32
165 HWND hwnd = (HWND)widget->effectiveWinId();
166 windowInfo.parent_window = hwnd;
167 #else
168 UNUSED_PARAMETER(windowInfo);
169 #endif
170 return false;
171 }
172
173 std::string str_url = target_url;
174
175 std::lock_guard<std::mutex> lock(popup_whitelist_mutex);
176 for (size_t i = popup_whitelist.size(); i > 0; i--) {
177 PopupWhitelistInfo &info = popup_whitelist[i - 1];
178
179 if (!info.obj) {
180 popup_whitelist.erase(popup_whitelist.begin() +
181 (i - 1));
182 continue;
183 }
184
185 if (astrcmpi(info.url.c_str(), str_url.c_str()) == 0) {
186 #ifdef _WIN32
187 HWND hwnd = (HWND)widget->effectiveWinId();
188 windowInfo.parent_window = hwnd;
189 #endif
190 return false;
191 }
192 }
193
194 /* Open popup URLs in user's actual browser */
195 QUrl url = QUrl(str_url.c_str(), QUrl::TolerantMode);
196 QDesktopServices::openUrl(url);
197 return true;
198 }
199
OnBeforeContextMenu(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,CefRefPtr<CefContextMenuParams>,CefRefPtr<CefMenuModel> model)200 void QCefBrowserClient::OnBeforeContextMenu(CefRefPtr<CefBrowser>,
201 CefRefPtr<CefFrame>,
202 CefRefPtr<CefContextMenuParams>,
203 CefRefPtr<CefMenuModel> model)
204 {
205 if (model->IsVisible(MENU_ID_BACK) &&
206 (!model->IsVisible(MENU_ID_RELOAD) &&
207 !model->IsVisible(MENU_ID_RELOAD_NOCACHE))) {
208 model->InsertItemAt(
209 2, MENU_ID_RELOAD_NOCACHE,
210 QObject::tr("RefreshBrowser").toUtf8().constData());
211 }
212 if (model->IsVisible(MENU_ID_PRINT)) {
213 model->Remove(MENU_ID_PRINT);
214 }
215 }
216
217 #if defined(_WIN32)
RunContextMenu(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,CefRefPtr<CefContextMenuParams>,CefRefPtr<CefMenuModel> model,CefRefPtr<CefRunContextMenuCallback> callback)218 bool QCefBrowserClient::RunContextMenu(
219 CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>,
220 CefRefPtr<CefContextMenuParams>, CefRefPtr<CefMenuModel> model,
221 CefRefPtr<CefRunContextMenuCallback> callback)
222 {
223 std::vector<std::tuple<std::string, int, bool, int>> menu_items;
224 menu_items.reserve(model->GetCount());
225 for (int i = 0; i < model->GetCount(); i++) {
226 menu_items.push_back(
227 {model->GetLabelAt(i), model->GetCommandIdAt(i),
228 model->IsEnabledAt(i), model->GetTypeAt(i)});
229 }
230
231 QMetaObject::invokeMethod(
232 QCoreApplication::instance()->thread(),
233 [menu_items, callback]() {
234 QMenu contextMenu;
235 std::string name;
236 int command_id;
237 bool enabled;
238 int type_id;
239
240 for (int i = 0; i < menu_items.size(); i++) {
241 std::tie(name, command_id, enabled, type_id) =
242 menu_items[i];
243 switch (type_id) {
244 case MENUITEMTYPE_COMMAND: {
245 QAction *item =
246 new QAction(name.c_str());
247 item->setEnabled(enabled);
248 item->setProperty("cmd_id", command_id);
249 contextMenu.addAction(item);
250 } break;
251 case MENUITEMTYPE_SEPARATOR:
252 contextMenu.addSeparator();
253 break;
254 }
255 }
256
257 QAction *action = contextMenu.exec(QCursor::pos());
258 if (action) {
259 QVariant cmdId = action->property("cmd_id");
260 callback.get()->Continue(cmdId.toInt(),
261 EVENTFLAG_NONE);
262 } else {
263 callback.get()->Cancel();
264 }
265 });
266 return true;
267 }
268 #endif
269
OnLoadEnd(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame> frame,int)270 void QCefBrowserClient::OnLoadEnd(CefRefPtr<CefBrowser>,
271 CefRefPtr<CefFrame> frame, int)
272 {
273 if (frame->IsMain() && !script.empty())
274 frame->ExecuteJavaScript(script, CefString(), 0);
275 }
276
OnPreKeyEvent(CefRefPtr<CefBrowser> browser,const CefKeyEvent & event,CefEventHandle,bool *)277 bool QCefBrowserClient::OnPreKeyEvent(CefRefPtr<CefBrowser> browser,
278 const CefKeyEvent &event, CefEventHandle,
279 bool *)
280 {
281 #ifdef _WIN32
282 if (event.type != KEYEVENT_RAWKEYDOWN)
283 return false;
284
285 if (event.windows_key_code == 'R' &&
286 (event.modifiers & EVENTFLAG_CONTROL_DOWN) != 0) {
287 browser->ReloadIgnoreCache();
288 return true;
289 }
290 #else
291 UNUSED_PARAMETER(browser);
292 UNUSED_PARAMETER(event);
293 #endif
294 return false;
295 }
296