1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13 End User License Agreement: www.juce.com/juce-6-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24 */
25
26 namespace juce
27 {
28
29 struct InternalWebViewType
30 {
InternalWebViewTypejuce::InternalWebViewType31 InternalWebViewType() {}
~InternalWebViewTypejuce::InternalWebViewType32 virtual ~InternalWebViewType() {}
33
34 virtual void createBrowser() = 0;
35 virtual bool hasBrowserBeenCreated() = 0;
36
37 virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
38
39 virtual void stop() = 0;
40 virtual void goBack() = 0;
41 virtual void goForward() = 0;
42 virtual void refresh() = 0;
43
focusGainedjuce::InternalWebViewType44 virtual void focusGained() {}
45 virtual void setWebViewSize (int, int) = 0;
46
47 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalWebViewType)
48 };
49
50 #if JUCE_MINGW
51 JUCE_DECLARE_UUID_GETTER (IOleClientSite, "00000118-0000-0000-c000-000000000046")
52 JUCE_DECLARE_UUID_GETTER (IDispatch, "00020400-0000-0000-c000-000000000046")
53
54 #ifndef WebBrowser
55 class WebBrowser;
56 #endif
57 #endif
58
59 JUCE_DECLARE_UUID_GETTER (DWebBrowserEvents2, "34A715A0-6587-11D0-924A-0020AFC7AC4D")
60 JUCE_DECLARE_UUID_GETTER (IConnectionPointContainer, "B196B284-BAB4-101A-B69C-00AA00341D07")
61 JUCE_DECLARE_UUID_GETTER (IWebBrowser2, "D30C1661-CDAF-11D0-8A3E-00C04FC9E26E")
62 JUCE_DECLARE_UUID_GETTER (WebBrowser, "8856F961-340A-11D0-A96B-00C04FD705A2")
63
64 //==============================================================================
65 class Win32WebView : public InternalWebViewType,
66 public ActiveXControlComponent
67 {
68 public:
Win32WebView(WebBrowserComponent & owner)69 Win32WebView (WebBrowserComponent& owner)
70 {
71 owner.addAndMakeVisible (this);
72 }
73
~Win32WebView()74 ~Win32WebView() override
75 {
76 if (connectionPoint != nullptr)
77 connectionPoint->Unadvise (adviseCookie);
78
79 if (browser != nullptr)
80 browser->Release();
81 }
82
createBrowser()83 void createBrowser() override
84 {
85 auto webCLSID = __uuidof (WebBrowser);
86 createControl (&webCLSID);
87
88 auto iidWebBrowser2 = __uuidof (IWebBrowser2);
89 auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer);
90
91 browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2);
92
93 if (auto connectionPointContainer = (IConnectionPointContainer*) queryInterface (&iidConnectionPointContainer))
94 {
95 connectionPointContainer->FindConnectionPoint (__uuidof (DWebBrowserEvents2), &connectionPoint);
96
97 if (connectionPoint != nullptr)
98 {
99 auto* owner = dynamic_cast<WebBrowserComponent*> (Component::getParentComponent());
100 jassert (owner != nullptr);
101
102 auto handler = new EventHandler (*owner);
103 connectionPoint->Advise (handler, &adviseCookie);
104 handler->Release();
105 }
106 }
107 }
108
hasBrowserBeenCreated()109 bool hasBrowserBeenCreated() override
110 {
111 return browser != nullptr;
112 }
113
goToURL(const String & url,const StringArray * headers,const MemoryBlock * postData)114 void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
115 {
116 if (browser != nullptr)
117 {
118 VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers)
119 VariantInit (&headerFlags);
120 VariantInit (&frame);
121 VariantInit (&postDataVar);
122 VariantInit (&headersVar);
123
124 if (headers != nullptr)
125 {
126 V_VT (&headersVar) = VT_BSTR;
127 V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer());
128 }
129
130 if (postData != nullptr && postData->getSize() > 0)
131 {
132 auto sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize());
133
134 if (sa != nullptr)
135 {
136 void* data = nullptr;
137 SafeArrayAccessData (sa, &data);
138 jassert (data != nullptr);
139
140 if (data != nullptr)
141 {
142 postData->copyTo (data, 0, postData->getSize());
143 SafeArrayUnaccessData (sa);
144
145 VARIANT postDataVar2;
146 VariantInit (&postDataVar2);
147 V_VT (&postDataVar2) = VT_ARRAY | VT_UI1;
148 V_ARRAY (&postDataVar2) = sa;
149
150 sa = nullptr;
151 postDataVar = postDataVar2;
152 }
153 else
154 {
155 SafeArrayDestroy (sa);
156 }
157 }
158 }
159
160 auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
161 browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
162 SysFreeString (urlBSTR);
163
164 VariantClear (&headerFlags);
165 VariantClear (&frame);
166 VariantClear (&postDataVar);
167 VariantClear (&headersVar);
168 }
169 }
170
stop()171 void stop() override
172 {
173 if (browser != nullptr)
174 browser->Stop();
175 }
176
goBack()177 void goBack() override
178 {
179 if (browser != nullptr)
180 browser->GoBack();
181 }
182
goForward()183 void goForward() override
184 {
185 if (browser != nullptr)
186 browser->GoForward();
187 }
188
refresh()189 void refresh() override
190 {
191 if (browser != nullptr)
192 browser->Refresh();
193 }
194
focusGained()195 void focusGained() override
196 {
197 auto iidOleObject = __uuidof (IOleObject);
198 auto iidOleWindow = __uuidof (IOleWindow);
199
200 if (auto oleObject = (IOleObject*) queryInterface (&iidOleObject))
201 {
202 if (auto oleWindow = (IOleWindow*) queryInterface (&iidOleWindow))
203 {
204 IOleClientSite* oleClientSite = nullptr;
205
206 if (SUCCEEDED (oleObject->GetClientSite (&oleClientSite)))
207 {
208 HWND hwnd;
209 oleWindow->GetWindow (&hwnd);
210 oleObject->DoVerb (OLEIVERB_UIACTIVATE, nullptr, oleClientSite, 0, hwnd, nullptr);
211 oleClientSite->Release();
212 }
213
214 oleWindow->Release();
215 }
216
217 oleObject->Release();
218 }
219 }
220
221 using ActiveXControlComponent::focusGained;
222
setWebViewSize(int width,int height)223 void setWebViewSize (int width, int height) override
224 {
225 setSize (width, height);
226 }
227
228 private:
229 IWebBrowser2* browser = nullptr;
230 IConnectionPoint* connectionPoint = nullptr;
231 DWORD adviseCookie = 0;
232
233 //==============================================================================
234 struct EventHandler : public ComBaseClassHelper<IDispatch>,
235 public ComponentMovementWatcher
236 {
EventHandlerjuce::Win32WebView::EventHandler237 EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {}
238
GetTypeInfoCountjuce::Win32WebView::EventHandler239 JUCE_COMRESULT GetTypeInfoCount (UINT*) override { return E_NOTIMPL; }
GetTypeInfojuce::Win32WebView::EventHandler240 JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; }
GetIDsOfNamesjuce::Win32WebView::EventHandler241 JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) override { return E_NOTIMPL; }
242
Invokejuce::Win32WebView::EventHandler243 JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams,
244 VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) override
245 {
246 if (dispIdMember == DISPID_BEFORENAVIGATE2)
247 {
248 *pDispParams->rgvarg->pboolVal
249 = owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE
250 : VARIANT_TRUE;
251 return S_OK;
252 }
253
254 if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/)
255 {
256 owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal);
257 *pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE;
258 return S_OK;
259 }
260
261 if (dispIdMember == DISPID_DOCUMENTCOMPLETE)
262 {
263 owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal));
264 return S_OK;
265 }
266
267 if (dispIdMember == 271 /*DISPID_NAVIGATEERROR*/)
268 {
269 int statusCode = pDispParams->rgvarg[1].pvarVal->intVal;
270 *pDispParams->rgvarg[0].pboolVal = VARIANT_FALSE;
271
272 // IWebBrowser2 also reports http status codes here, we need
273 // report only network errors
274 if (statusCode < 0)
275 {
276 LPTSTR messageBuffer = nullptr;
277 auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
278 nullptr, statusCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
279 (LPTSTR) &messageBuffer, 0, nullptr);
280
281 String message (messageBuffer, size);
282 LocalFree (messageBuffer);
283
284 if (! owner.pageLoadHadNetworkError (message))
285 *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
286 }
287
288 return S_OK;
289 }
290
291 if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/)
292 {
293 owner.windowCloseRequest();
294
295 // setting this bool tells the browser to ignore the event - we'll handle it.
296 if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL))
297 *pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
298
299 return S_OK;
300 }
301
302 return E_NOTIMPL;
303 }
304
componentMovedOrResizedjuce::Win32WebView::EventHandler305 void componentMovedOrResized (bool, bool) override {}
componentPeerChangedjuce::Win32WebView::EventHandler306 void componentPeerChanged() override {}
componentVisibilityChangedjuce::Win32WebView::EventHandler307 void componentVisibilityChanged() override { owner.visibilityChanged(); }
308
309 private:
310 WebBrowserComponent& owner;
311
getStringFromVariantjuce::Win32WebView::EventHandler312 static String getStringFromVariant (VARIANT* v)
313 {
314 return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal
315 : v->bstrVal;
316 }
317
318 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
319 };
320
321 //==============================================================================
322 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32WebView)
323 };
324
325 #if JUCE_USE_WIN_WEBVIEW2
326
327 using namespace Microsoft::WRL;
328
329 class WebView2 : public InternalWebViewType,
330 public Component,
331 public ComponentMovementWatcher
332 {
333 public:
WebView2(WebBrowserComponent & o,const File & dllLocation,const File & userDataFolder)334 WebView2 (WebBrowserComponent& o, const File& dllLocation, const File& userDataFolder)
335 : ComponentMovementWatcher (&o),
336 owner (o)
337 {
338 if (! createWebViewEnvironment (dllLocation, userDataFolder))
339 throw std::runtime_error ("Failed to create the CoreWebView2Environemnt");
340
341 owner.addAndMakeVisible (this);
342 }
343
~WebView2()344 ~WebView2() override
345 {
346 removeEventHandlers();
347 closeWebView();
348
349 if (webView2LoaderHandle != nullptr)
350 ::FreeLibrary (webView2LoaderHandle);
351 }
352
createBrowser()353 void createBrowser() override
354 {
355 if (webView == nullptr)
356 {
357 jassert (webViewEnvironment != nullptr);
358 createWebView();
359 }
360 }
361
hasBrowserBeenCreated()362 bool hasBrowserBeenCreated() override
363 {
364 return webView != nullptr || isCreating;
365 }
366
goToURL(const String & url,const StringArray * headers,const MemoryBlock * postData)367 void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
368 {
369 urlRequest = { url,
370 headers != nullptr ? *headers : StringArray(),
371 postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() };
372
373 if (webView != nullptr)
374 webView->Navigate (urlRequest.url.toWideCharPointer());
375 }
376
stop()377 void stop() override
378 {
379 if (webView != nullptr)
380 webView->Stop();
381 }
382
goBack()383 void goBack() override
384 {
385 if (webView != nullptr)
386 {
387 BOOL canGoBack = false;
388 webView->get_CanGoBack (&canGoBack);
389
390 if (canGoBack)
391 webView->GoBack();
392 }
393 }
394
goForward()395 void goForward() override
396 {
397 if (webView != nullptr)
398 {
399 BOOL canGoForward = false;
400 webView->get_CanGoForward (&canGoForward);
401
402 if (canGoForward)
403 webView->GoForward();
404 }
405 }
406
refresh()407 void refresh() override
408 {
409 if (webView != nullptr)
410 webView->Reload();
411 }
412
setWebViewSize(int width,int height)413 void setWebViewSize (int width, int height) override
414 {
415 setSize (width, height);
416 }
417
componentMovedOrResized(bool,bool)418 void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
419 {
420 if (auto* peer = owner.getTopLevelComponent()->getPeer())
421 setControlBounds (peer->getAreaCoveredBy (owner));
422 }
423
componentPeerChanged()424 void componentPeerChanged() override
425 {
426 componentMovedOrResized (true, true);
427 }
428
componentVisibilityChanged()429 void componentVisibilityChanged() override
430 {
431 setControlVisible (owner.isShowing());
432
433 componentPeerChanged();
434 owner.visibilityChanged();
435 }
436
437 private:
438 //==============================================================================
439 template <class ArgType>
getUriStringFromArgs(ArgType * args)440 static String getUriStringFromArgs (ArgType* args)
441 {
442 if (args != nullptr)
443 {
444 LPWSTR uri;
445 args->get_Uri (&uri);
446
447 return uri;
448 }
449
450 return {};
451 }
452
453 //==============================================================================
addEventHandlers()454 void addEventHandlers()
455 {
456 if (webView != nullptr)
457 {
458 webView->add_NavigationStarting (Callback<ICoreWebView2NavigationStartingEventHandler> (
459 [this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
460 {
461 auto uriString = getUriStringFromArgs (args);
462
463 if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString))
464 args->put_Cancel (true);
465
466 return S_OK;
467 }).Get(), &navigationStartingToken);
468
469 webView->add_NewWindowRequested (Callback<ICoreWebView2NewWindowRequestedEventHandler> (
470 [this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
471 {
472 auto uriString = getUriStringFromArgs (args);
473
474 if (uriString.isNotEmpty())
475 {
476 owner.newWindowAttemptingToLoad (uriString);
477 args->put_Handled (true);
478 }
479
480 return S_OK;
481 }).Get(), &newWindowRequestedToken);
482
483 webView->add_WindowCloseRequested (Callback<ICoreWebView2WindowCloseRequestedEventHandler> (
484 [this] (ICoreWebView2*, IUnknown*) -> HRESULT
485 {
486 owner.windowCloseRequest();
487 return S_OK;
488 }).Get(), &windowCloseRequestedToken);
489
490 webView->add_NavigationCompleted (Callback<ICoreWebView2NavigationCompletedEventHandler> (
491 [this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
492 {
493 LPWSTR uri;
494 sender->get_Source (&uri);
495
496 String uriString (uri);
497
498 if (uriString.isNotEmpty())
499 {
500 BOOL success = false;
501 args->get_IsSuccess (&success);
502
503 COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
504 args->get_WebErrorStatus (&errorStatus);
505
506 if (success
507 || errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore
508 {
509 owner.pageFinishedLoading (uriString);
510 }
511 else
512 {
513 auto errorString = "Error code: " + String (errorStatus);
514
515 if (owner.pageLoadHadNetworkError (errorString))
516 owner.goToURL ("data:text/plain;charset=UTF-8," + errorString);
517 }
518 }
519
520 return S_OK;
521 }).Get(), &navigationCompletedToken);
522
523 webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
524
525 webView->add_WebResourceRequested (Callback<ICoreWebView2WebResourceRequestedEventHandler> (
526 [this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT
527 {
528 if (urlRequest.url.isEmpty())
529 return S_OK;
530
531 ComSmartPtr<ICoreWebView2WebResourceRequest> request;
532 args->get_Request (request.resetAndGetPointerAddress());
533
534 auto uriString = getUriStringFromArgs<ICoreWebView2WebResourceRequest> (request);
535
536 if (uriString == urlRequest.url
537 || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url))
538 {
539 String method ("GET");
540
541 if (urlRequest.postData.getSize() > 0)
542 {
543 method = "POST";
544
545 ComSmartPtr<IStream> content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(),
546 (UINT) urlRequest.postData.getSize()));
547 request->put_Content (content);
548 }
549
550 if (! urlRequest.headers.isEmpty())
551 {
552 ComSmartPtr<ICoreWebView2HttpRequestHeaders> headers;
553 request->get_Headers (headers.resetAndGetPointerAddress());
554
555 for (auto& header : urlRequest.headers)
556 {
557 headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(),
558 header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer());
559 }
560 }
561
562 request->put_Method (method.toWideCharPointer());
563
564 urlRequest = {};
565 }
566
567 return S_OK;
568 }).Get(), &webResourceRequestedToken);
569 }
570 }
571
removeEventHandlers()572 void removeEventHandlers()
573 {
574 if (webView != nullptr)
575 {
576 if (navigationStartingToken.value != 0)
577 webView->remove_NavigationStarting (navigationStartingToken);
578
579 if (newWindowRequestedToken.value != 0)
580 webView->remove_NewWindowRequested (newWindowRequestedToken);
581
582 if (windowCloseRequestedToken.value != 0)
583 webView->remove_WindowCloseRequested (windowCloseRequestedToken);
584
585 if (navigationCompletedToken.value != 0)
586 webView->remove_NavigationCompleted (navigationCompletedToken);
587
588 if (webResourceRequestedToken.value != 0)
589 {
590 webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
591 webView->remove_WebResourceRequested (webResourceRequestedToken);
592 }
593 }
594 }
595
createWebViewEnvironment(const File & dllLocation,const File & userDataFolder)596 bool createWebViewEnvironment (const File& dllLocation, const File& userDataFolder)
597 {
598 using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
599 ICoreWebView2EnvironmentOptions*,
600 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
601
602 auto dllPath = dllLocation.getFullPathName();
603
604 if (dllPath.isEmpty())
605 dllPath = "WebView2Loader.dll";
606
607 webView2LoaderHandle = LoadLibraryA (dllPath.toUTF8());
608
609 if (webView2LoaderHandle == nullptr)
610 return false;
611
612 auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (webView2LoaderHandle,
613 "CreateCoreWebView2EnvironmentWithOptions");
614 if (createWebViewEnvironmentWithOptions == nullptr)
615 {
616 // failed to load WebView2Loader.dll
617 jassertfalse;
618 return false;
619 }
620
621 auto options = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
622
623 WeakReference<WebView2> weakThis (this);
624 auto hr = createWebViewEnvironmentWithOptions (nullptr,
625 userDataFolder != File() ? userDataFolder.getFullPathName().toWideCharPointer() : nullptr,
626 options.Get(),
627 Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
628 [this, weakThis] (HRESULT, ICoreWebView2Environment* env) -> HRESULT
629 {
630 if (weakThis != nullptr)
631 webViewEnvironment = env;
632
633 return S_OK;
634 }).Get());
635
636 return SUCCEEDED (hr);
637 }
638
createWebView()639 void createWebView()
640 {
641 if (auto* peer = getPeer())
642 {
643 isCreating = true;
644
645 WeakReference<WebView2> weakThis (this);
646
647 webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
648 Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
649 [this, weakThis] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT
650 {
651 if (weakThis != nullptr)
652 {
653 isCreating = false;
654
655 if (controller != nullptr)
656 {
657 webViewController = controller;
658 controller->get_CoreWebView2 (webView.resetAndGetPointerAddress());
659
660 addEventHandlers();
661 componentMovedOrResized (true, true);
662
663 if (webView != nullptr && urlRequest.url.isNotEmpty())
664 webView->Navigate (urlRequest.url.toWideCharPointer());
665 }
666 }
667
668 return S_OK;
669 }).Get());
670 }
671 }
672
closeWebView()673 void closeWebView()
674 {
675 if (webViewController != nullptr)
676 {
677 webViewController->Close();
678 webViewController = nullptr;
679 webView = nullptr;
680 }
681
682 webViewEnvironment = nullptr;
683 }
684
685 //==============================================================================
setControlBounds(Rectangle<int> newBounds) const686 void setControlBounds (Rectangle<int> newBounds) const
687 {
688 if (webViewController != nullptr)
689 {
690 #if JUCE_WIN_PER_MONITOR_DPI_AWARE
691 if (auto* peer = owner.getTopLevelComponent()->getPeer())
692 newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
693 #endif
694
695 webViewController->put_Bounds({ newBounds.getX(), newBounds.getY(),
696 newBounds.getRight(), newBounds.getBottom() });
697 }
698 }
699
setControlVisible(bool shouldBeVisible) const700 void setControlVisible (bool shouldBeVisible) const
701 {
702 if (webViewController != nullptr)
703 webViewController->put_IsVisible (shouldBeVisible);
704 }
705
706 //==============================================================================
707 WebBrowserComponent& owner;
708
709 HMODULE webView2LoaderHandle = nullptr;
710
711 ComSmartPtr<ICoreWebView2Environment> webViewEnvironment;
712 ComSmartPtr<ICoreWebView2Controller> webViewController;
713 ComSmartPtr<ICoreWebView2> webView;
714
715 EventRegistrationToken navigationStartingToken { 0 },
716 newWindowRequestedToken { 0 },
717 windowCloseRequestedToken { 0 },
718 navigationCompletedToken { 0 },
719 webResourceRequestedToken { 0 };
720
721 struct URLRequest
722 {
723 String url;
724 StringArray headers;
725 MemoryBlock postData;
726 };
727 URLRequest urlRequest;
728
729 bool isCreating = false;
730
731 //==============================================================================
732 JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
733 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2)
734 };
735
736 #endif
737
738 //==============================================================================
739 class WebBrowserComponent::Pimpl
740 {
741 public:
Pimpl(WebBrowserComponent & owner,const File & dllLocation,const File & userDataFolder,bool useWebView2)742 Pimpl (WebBrowserComponent& owner, const File& dllLocation, const File& userDataFolder, bool useWebView2)
743 {
744 if (useWebView2)
745 {
746 #if JUCE_USE_WIN_WEBVIEW2
747 try
748 {
749 internal.reset (new WebView2 (owner, dllLocation, userDataFolder));
750 }
751 catch (std::runtime_error&) {}
752 #endif
753 }
754
755 ignoreUnused (dllLocation, userDataFolder);
756
757 if (internal == nullptr)
758 internal.reset (new Win32WebView (owner));
759 }
760
getInternalWebView()761 InternalWebViewType& getInternalWebView()
762 {
763 return *internal;
764 }
765
766 private:
767 std::unique_ptr<InternalWebViewType> internal;
768 };
769
770 //==============================================================================
WebBrowserComponent(bool unloadWhenHidden)771 WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
772 : browser (new Pimpl (*this, {}, {}, false)),
773 unloadPageWhenBrowserIsHidden (unloadWhenHidden)
774 {
775 setOpaque (true);
776 }
777
WebBrowserComponent(bool unloadWhenHidden,const File & dllLocation,const File & userDataFolder)778 WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden,
779 const File& dllLocation,
780 const File& userDataFolder)
781 : browser (new Pimpl (*this, dllLocation, userDataFolder, true)),
782 unloadPageWhenBrowserIsHidden (unloadWhenHidden)
783 {
784 setOpaque (true);
785 }
786
~WebBrowserComponent()787 WebBrowserComponent::~WebBrowserComponent()
788 {
789 }
790
791 //==============================================================================
goToURL(const String & url,const StringArray * headers,const MemoryBlock * postData)792 void WebBrowserComponent::goToURL (const String& url,
793 const StringArray* headers,
794 const MemoryBlock* postData)
795 {
796 lastURL = url;
797
798 if (headers != nullptr)
799 lastHeaders = *headers;
800 else
801 lastHeaders.clear();
802
803 if (postData != nullptr)
804 lastPostData = *postData;
805 else
806 lastPostData.reset();
807
808 blankPageShown = false;
809
810 if (! browser->getInternalWebView().hasBrowserBeenCreated())
811 checkWindowAssociation();
812
813 browser->getInternalWebView().goToURL (url, headers, postData);
814 }
815
stop()816 void WebBrowserComponent::stop()
817 {
818 browser->getInternalWebView().stop();
819 }
820
goBack()821 void WebBrowserComponent::goBack()
822 {
823 lastURL.clear();
824 blankPageShown = false;
825
826 browser->getInternalWebView().goBack();
827 }
828
goForward()829 void WebBrowserComponent::goForward()
830 {
831 lastURL.clear();
832
833 browser->getInternalWebView().goForward();
834 }
835
refresh()836 void WebBrowserComponent::refresh()
837 {
838 browser->getInternalWebView().refresh();
839 }
840
841 //==============================================================================
paint(Graphics & g)842 void WebBrowserComponent::paint (Graphics& g)
843 {
844 if (! browser->getInternalWebView().hasBrowserBeenCreated())
845 {
846 g.fillAll (Colours::white);
847 checkWindowAssociation();
848 }
849 }
850
checkWindowAssociation()851 void WebBrowserComponent::checkWindowAssociation()
852 {
853 if (isShowing())
854 {
855 if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr)
856 {
857 browser->getInternalWebView().createBrowser();
858 reloadLastURL();
859 }
860 else
861 {
862 if (blankPageShown)
863 goBack();
864 }
865 }
866 else
867 {
868 if (browser != nullptr && unloadPageWhenBrowserIsHidden && ! blankPageShown)
869 {
870 // when the component becomes invisible, some stuff like flash
871 // carries on playing audio, so we need to force it onto a blank
872 // page to avoid this..
873
874 blankPageShown = true;
875 browser->getInternalWebView().goToURL ("about:blank", 0, 0);
876 }
877 }
878 }
879
reloadLastURL()880 void WebBrowserComponent::reloadLastURL()
881 {
882 if (lastURL.isNotEmpty())
883 {
884 goToURL (lastURL, &lastHeaders, &lastPostData);
885 lastURL.clear();
886 }
887 }
888
parentHierarchyChanged()889 void WebBrowserComponent::parentHierarchyChanged()
890 {
891 checkWindowAssociation();
892 }
893
resized()894 void WebBrowserComponent::resized()
895 {
896 browser->getInternalWebView().setWebViewSize (getWidth(), getHeight());
897 }
898
visibilityChanged()899 void WebBrowserComponent::visibilityChanged()
900 {
901 checkWindowAssociation();
902 }
903
focusGained(FocusChangeType)904 void WebBrowserComponent::focusGained (FocusChangeType)
905 {
906 browser->getInternalWebView().focusGained();
907 }
908
clearCookies()909 void WebBrowserComponent::clearCookies()
910 {
911 HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
912 ::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
913 ::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
914
915 if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
916 {
917 entry.realloc (1, entrySize);
918 urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
919 }
920
921 if (urlCacheHandle != nullptr)
922 {
923 for (;;)
924 {
925 ::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
926
927 if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
928 {
929 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
930 {
931 entry.realloc (1, entrySize);
932
933 if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
934 continue;
935 }
936
937 break;
938 }
939 }
940
941 FindCloseUrlCache (urlCacheHandle);
942 }
943 }
944
945 } // namespace juce
946