Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VisualC++

Using the WebView Control's navigateToLocalStreamUri from WinJS/JavaScript

4.33/5 (2 votes)
26 Feb 2016CPOL3 min read 18.2K  
WebView navigateToLocalStreamUri can be used from WinJS/JavaScript with some more advanced coding techniques to wrap a necessary interface

Introduction

Microsoft did not give a Promise or callback type of wrapper for the pattern of navigateToLocalStreamUri which requires an interface implementation which is not possible in WinJS/JavaScript. Hopefully, in the future, Microsoft will implement this interface which has been around since Windows 8.1. In the mean time, this is an attempt to show how it can be done with a helper wrapper in native C++ or C++/CX.

Background

Knowledge of native C++ or C++/CX, WinRT, COM, threading and threading model, WinJS and JavaScript as well as if one would choose to do some translation of the C++/CX code, then VB.NET/C# also could implement this.  This is a bit more advanced than typical web programming.

Using the Code

The code requires IDL for native C++ and if one were to translate to VB.NET/C# or choose to only use C++/CX since the native C++ is advanced and largely unsupported by Microsoft (requiring a custom parallel task library from another article I wrote), then only look in the __cplusplus_winrt areas as the programming pattern is slightly different. The callback to WinJS must not occur on the same thread which got the UriToStreamAsync event or a STA (single threaded apartment) to STA error will be thrown and crash the application. The event callback is not currently wrapping the Uri in a special event args object so it needs to be accessed through the details property that WinJS will automatically provide for unknown event parameters.

The IDL for native C++ Wrappers.idl is as follows:

C++
#include <sdkddkver.h>
#ifndef __cplusplus_winrt
namespace Wrappers
{
	[object]
	[uuid(94BEE8B5-6B7D-4670-A968-E2E3076FCFD2)]
	[pointer_default(unique)]
	[version(NTDDI_VERSION)]
	interface IUriToStreamResolverWrapper : IInspectable
	{
		[eventadd] HRESULT UriToStreamAsyncCalled([in] 
		Windows.Foundation.EventHandler<iinspectable*>* handler, 
		[out][retval] EventRegistrationToken* cookie);
		[eventremove] HRESULT UriToStreamAsyncCalled([in] EventRegistrationToken cookie);
		HRESULT UriToStreamAsyncResolved([in] Windows.Foundation.Uri* uri, 
		[in] Windows.Storage.Streams.IInputStream* inputStream);
	}

	[activatable(NTDDI_VERSION)]
	[threading(sta)]
	[marshaling_behavior(agile)]
	[version(NTDDI_VERSION)]
	runtimeclass UriToStreamResolverWrapper
	{
		[default] interface IUriToStreamResolverWrapper;
	}
}
#endif

Wrappers.cpp contains both native C++ and C++/CX using conditional compilation. Wrappers_h.h is the generated IDL header file with the interfaces. One might question the design pattern as the promises returned later from the WinJS functions are not used and instead a callback on the interface is used. In fact, more in spirit with the WinRT model would be to store the IAsyncOperation's wrapping the Streams and waiting for their completion method. This would not require the extra method call, and would eliminate the 2 maps in exchange for an array. Yet the current method should work in all cases even if there is a very slight inefficiency.

C++
#ifdef _WINRT_DLL
#include <map>
#ifndef __cplusplus_winrt
#include "Wrappers_h.h"
#include <windows.web.h>
#endif

namespace Wrappers {
#ifdef __cplusplus_winrt
		[uuid("94BEE8B5-6B7D-4670-A968-E2E3076FCFD2")]
		public interface class IUriToStreamResolverWrapper
		{
			virtual event Windows::Foundation::EventHandler
			<Windows::Foundation::Uri^>^ UriToStreamAsyncCalled;
			void UriToStreamResolved(Windows::Foundation::Uri^ uri, 
			Windows::Storage::Streams::IInputStream^ inputStream);
		};
#endif
}
#ifdef __cplusplus_winrt
#define IUriToStreamResolverWrapper 
TimeStamp::WebCamSecUtils::IUriToStreamResolverWrapper
DEFINE_GUID(IID_IUriToStreamResolverWrapper, 0x94bee8b5, 
0x6b7d, 0x4670, 0xa9, 0x68, 0xe2, 0xe3, 0x7, 0x6f, 0xcf, 0xd2);
#define IUriToStreamResolverWrapperObj IUriToStreamResolverWrapper^
#else
#define IUriToStreamResolverWrapper ABI::TimeStamp::WebCamSecUtils::IUriToStreamResolverWrapper
#define IID_IUriToStreamResolverWrapper 
ABI::TimeStamp::WebCamSecUtils::IID_IUriToStreamResolverWrapper
#define IUriToStreamResolverWrapperObj _ComPtr<IUriToStreamResolverWrapper>
#endif

namespace Wrappers {
#ifdef __cplusplus_winrt
		public ref class UriToStreamResolverWrapper sealed : 
		public IUriToStreamResolverWrapper, Windows::Web::IUriToStreamResolver
#else
		class UriToStreamResolverWrapper :
		public Microsoft::WRL::RuntimeClass<
			Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::RuntimeClassType::WinRt >,
			IUriToStreamResolverWrapper, ABI::Windows::Web::IUriToStreamResolver>
#endif
		{
#ifndef __cplusplus_winrt
			InspectableClass
			(RuntimeClass_TimeStamp_WebCamSecUtils_UriToStreamResolverWrapper, BaseTrust)
#endif
		public:
#ifdef __cplusplus_winrt
			virtual event Windows::Foundation::EventHandler
			<Windows::Foundation::Uri^>^ UriToStreamAsyncCalled;
			virtual void UriToStreamResolved(Windows::Foundation::Uri^ uri, 
			Windows::Storage::Streams::IInputStream^ inputStream)
			{
				std::wstring uristr;
				UINT32 len;
				PCWSTR rawbuf = WindowsGetStringRawBuffer((HSTRING)uri->RawUri, &len);
				uristr.assign(rawbuf, rawbuf + len);
				{
					AutoLock lock(m_CritSec);
					m_map.insert(std::pair<std::wstring, 
					Windows::Storage::Streams::IInputStream^>(uristr, inputStream));
					m_SignalMap.at(uristr)->set();
				}
			}
#else
			STDMETHOD(RuntimeClassInitialize)() { return S_OK; }

			STDMETHODIMP add_UriToStreamAsyncCalled
			(__FIEventHandler_1_IInspectable *handler, EventRegistrationToken *cookie)
			{
				return m_events.Add(Microsoft::WRL::Make<ContextEventWrapper
				<__FIEventHandler_1_IInspectable>>(handler).Detach(), cookie);
			}
			STDMETHODIMP remove_UriToStreamAsyncCalled(EventRegistrationToken cookie)
			{
				return m_events.Remove(cookie);
			}
			HRESULT STDMETHODCALLTYPE UriToStreamAsyncResolved(
				/* [in] */ ABI::Windows::Foundation::IUriRuntimeClass *uri,
				/* [in] */ ABI::Windows::Storage::Streams::IInputStream *inputStream)
			{
				std::wstring uristr;
				HSTRING hstr;
				UINT32 len;
				PCWSTR rawbuf;
				if (SUCCEEDED(uri->get_RawUri(&hstr))) {
					rawbuf = WindowsGetStringRawBuffer(hstr, &len);
					uristr.assign(rawbuf, rawbuf + len);
					WindowsDeleteString(hstr);
				}
				{
					AutoLock lock(m_CritSec);
					m_map.insert(std::pair<std::wstring, Microsoft::WRL::ComPtr
					<ABI::Windows::Storage::Streams::IInputStream>>
					(uristr, inputStream));
					m_SignalMap.at(uristr)->set();
				}
				return S_OK;
			}
#endif
#ifndef __cplusplus_winrt
			virtual HRESULT STDMETHODCALLTYPE UriToStreamAsync(
				/* [in] */ __RPC__in_opt 
				ABI::Windows::Foundation::IUriRuntimeClass *uri,
				/* [out][retval] */ __RPC__deref_out_opt 
				__FIAsyncOperation_1_Windows__CStorage__CStreams__CIInputStream 
				**operation)
			{
				if (!operation) return E_INVALIDARG;
				Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IUriRuntimeClass> 
					puri = uri;
				AddRef();
				*operation = Concurrency_winrt::create_async
				<ABI::Windows::Storage::Streams::IInputStream*>
				([=](ABI::Windows::Storage::Streams::IInputStream** 
				retVal) -> HRESULT {
					std::wstring uristr;
					HSTRING hstr;
					UINT32 len;
					PCWSTR rawbuf;
					if (SUCCEEDED(uri->get_RawUri(&hstr))) {
						rawbuf = WindowsGetStringRawBuffer(hstr, &len);
						uristr.assign(rawbuf, rawbuf + len);
						WindowsDeleteString(hstr);
					}
					Concurrency::event signal;
					{
						AutoLock lock(m_CritSec);
						m_SignalMap.insert(std::pair<std::wstring, 
						concurrency::event*>(uristr, &signal));
					}
					m_events.InvokeAll<IInspectable*, 
					IInspectable*>((IUriToStreamResolverWrapper*)this, 
					puri.Get()); 	//must not be done on callback 
							//thread as its STA -> STA
					signal.wait();
					{
						AutoLock lock(m_CritSec);
						*retVal = m_map.at(uristr).Detach();
						m_SignalMap.erase(uristr);
						m_map.erase(uristr);
					}
					Release();
					END_CREATE_ASYNC(S_OK);
				return S_OK;
			}
#else
			virtual Windows::Foundation::IAsyncOperation
			<Windows::Storage::Streams::IInputStream^>^ 
			UriToStreamAsync(Windows::Foundation::Uri^ uri)
			{
				UriToStreamResolverWrapper^ _this = this;
				Platform::WeakReference wr(this);
				return Concurrency::create_async([wr, uri]() -> 
				Windows::Storage::Streams::IInputStream^ {
					UriToStreamResolverWrapper^ _this = 
					wr.Resolve<UriToStreamResolverWrapper>();
					Windows::Storage::Streams::IInputStream^ inputStream;
					std::wstring uristr;
					UINT32 len;
					PCWSTR rawbuf = WindowsGetStringRawBuffer
							((HSTRING)uri->RawUri, &len);
					uristr.assign(rawbuf, rawbuf + len);
					Concurrency::event signal;
					{
						AutoLock lock(_this->m_CritSec);
						_this->m_SignalMap.insert(std::pair<std::wstring, 
						concurrency::event*>(uristr, &signal));
					}
					_this->UriToStreamAsyncCalled(_this, uri);
					signal.wait();
					{
						AutoLock lock(_this->m_CritSec);
						inputStream = _this->m_map.at(uristr);
						_this->m_SignalMap.erase(uristr);
						_this->m_map.erase(uristr);
					}
					return inputStream;
				});
			}
#endif
		private:
#ifdef __cplusplus_winrt
			static Windows::Foundation::EventRegistrationToken UriToStreamAsyncCalledAdd
			(IUriToStreamResolverWrapperObj iCallback, Windows::Foundation::EventHandler
			<Windows::Foundation::Uri^>^ handler) {
				return iCallback->UriToStreamAsyncCalled += handler;
			}
			static void UriToStreamAsyncCalledRemove
			(IUriToStreamResolverWrapperObj iCallback, 
			Windows::Foundation::EventRegistrationToken token) {
				iCallback->UriToStreamAsyncCalled -= token;
			}
			EventBinder<IUriToStreamResolverWrapper, IUriToStreamResolverWrapperObj, 
			Windows::Foundation::EventHandler<Windows::Foundation::Uri^>^, 
			&UriToStreamAsyncCalledAdd, &UriToStreamAsyncCalledRemove> m_EventBinder;
#else
			EventBinder<IUriToStreamResolverWrapper, IUriToStreamResolverWrapperObj, 
			ABI::Windows::Foundation::__FIEventHandler_1_IInspectable_t*, 
			&IUriToStreamResolverWrapper::add_UriToStreamAsyncCalled, 
			&IUriToStreamResolverWrapper::remove_UriToStreamAsyncCalled> m_EventBinder;
#endif
#ifndef __cplusplus_winrt
			Microsoft::WRL::EventSource<ContextEventWrapper
			<__FIEventHandler_1_IInspectable>> m_events;
#endif
			CritSec m_CritSec;
			std::map<std::wstring, Concurrency::event*> m_SignalMap;
#ifndef __cplusplus_winrt
			std::map<std::wstring, Microsoft::WRL::ComPtr
			<ABI::Windows::Storage::Streams::IInputStream>> m_map;
#else
			std::map<std::wstring, Windows::Storage::Streams::IInputStream^> m_map;
#endif
		};
}
#endif

Now to use this from WinJS is relatively simple, yet note the special URL replacement function and how it is built using the GUID of the app and the key that was used. It shows how one can actually swap default.html with the live document, and remove all JavaScript to set up for example to make a screenshot before disposing the webview. The navigation is monitored as well for completion and errors.

C++
var getUriToStreamAsync = function (streamResolver) {
    return function (uri) {
        var toHex = function(str) {
            var tempstr = '';
            for (var i = 0; i < str.length; i++) {
                tempstr = tempstr + str.charCodeAt(i).toString(16);
            }
            return tempstr;
        }
        if (uri.detail[0].rawUri === "ms-local-stream://" +
        Windows.ApplicationModel.Package.current.id.name.toLowerCase() +
        "_" + toHex("default") + "/default.html" ||
            uri.detail[0].rawUri.indexOf(".js", uri.detail[0].rawUri.length - 3) !== -1) {
            var randomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
            var dataWriter = new Windows.Storage.Streams.DataWriter(randomAccessStream);
            dataWriter.unicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.utf8;
            dataWriter.byteOrder = Windows.Storage.Streams.ByteOrder.littleEndian;
            dataWriter.writeString(uri.detail[0].rawUri.indexOf
            (".js", uri.detail[0].rawUri.length - 3) !== -1 ?
            '' : document.documentElement.outerHTML);
            return dataWriter.storeAsync().then(function () {
                return dataWriter.flushAsync();
            }).then(function () {
                var stream = dataWriter.detachStream();
                stream.seek(0);
                dataWriter.close();
                streamResolver.uriToStreamAsyncResolved(uri, stream);
            });
        } else {
            return Windows.Storage.StorageFile.getFileFromApplicationUriAsync
            (new Windows.Foundation.Uri(uri.detail[0].rawUri.replace
            ("ms-local-stream://" + Windows.ApplicationModel.Package.current.id.name.toLowerCase() +
            "_" + toHex("default"),
            "ms-appx://"))).then(function (file) {
                return file.openReadAsync();
            }).then(function (stream) {
                streamResolver.uriToStreamAsyncResolved(uri, stream);
            });
        }
    };
};
var _this = this, webViewControl = window.document.createElement
("x-ms-webview"), streamResolver =
new TimeStamp.WebCamSecUtils.UriToStreamResolverWrapper(),
uriGetter = this.getUriToStreamAsync(streamResolver);
webViewControl.width = width;
webViewControl.height = height;

webViewControl.settings.isJavaScriptEnabled = false;
return new WinJS.Promise(function (complete) {
    var navComplete = function () {
        webViewControl.removeEventListener
        ("MSWebViewNavigationCompleted", navComplete);
        complete();
    };
    streamResolver.addEventListener("uritostreamasynccalled", uriGetter);
    webViewControl.addEventListener("MSWebViewNavigationCompleted", navComplete);
    webViewControl.addEventListener("MSWebViewScriptNotify", navComplete);
    webViewControl.addEventListener("MSWebViewUnviewableContentIdentified", navComplete);
    webViewControl.navigateToLocalStreamUri
    (webViewControl.buildLocalStreamUri
    ("default", "/default.html"), streamResolver);
    window.document.body.appendChild(webViewControl);
});

Points of Interest

The control shown is not implementing events on a singleton pattern so it is up to the JavaScript not to respond to the event callback more than one time. Perhaps there is a better delegate model to enforce this yet delegates are just glorified singleton events for the most part.

The object implemented for the callback interface is called in parallel, hence a map is needed to identify and sequence the JavaScript callbacks which must also use the uri as a key. There could be additional logic needed and the reason Microsoft perhaps did not offer it in WinJS was to keep things highly parallel without being synchronized on the UI thread. However WinJS will still quickly return promises and do most processing in the background so the performance should be negligibly different. It might be better to implement the interface purely in the library though if loading large amounts of files very frequently. It is the author's opinion that Microsoft should implement such a wrapper and provide it for the WinJS library to make this easier and possible given it has been around since Windows 8.1 and is supposedly supported.

History

  • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)