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