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