window.pywebview = { token: '%(token)s', platform: '%(platform)s', api: {}, _eventHandlers: {}, _returnValuesCallbacks: {}, _createApi: function (funcList) { function sanitize_params(params) { var reservedWords = [ 'case', 'catch', 'const', 'debugger', 'default', 'delete', 'do', 'export', 'extends', 'false', 'function', 'instanceof', 'let', 'new', 'null', 'super', 'switch', 'this', 'throw', 'true', 'typeof', 'var', 'void', ]; for (var i = 0; i < params.length; i++) { var param = params[i]; if (reservedWords.indexOf(param) !== -1) { params[i] = param + '_'; } } return params; } for (var i = 0; i < funcList.length; i++) { var element = funcList[i]; var funcName = element.func; var params = element.params; // Create nested structure and assign function var funcHierarchy = funcName.split('.'); var functionName = funcHierarchy.pop(); var nestedObject = funcHierarchy.reduce(function (obj, prop) { if (!obj[prop]) { obj[prop] = {}; } return obj[prop]; }, window.pywebview.api); // Define the function body var funcBody = 'var __id = (Math.random() + "").substring(2);' + 'var promise = new Promise(function(resolve, reject) {' + ' window.pywebview._checkValue("' + funcName + '", resolve, reject, __id);' + '});' + 'window.pywebview._jsApiCallback("' + funcName + '", Array.prototype.slice.call(arguments), __id);' + 'return promise;'; // Assign the new function nestedObject[functionName] = new Function( sanitize_params(params), funcBody ); window.pywebview._returnValuesCallbacks[funcName] = {}; } }, _jsApiCallback: function (funcName, params, id) { switch (window.pywebview.platform) { case 'mshtml': case 'cef': case 'qtwebkit': case 'android-webkit': return window.external.call(funcName, pywebview.stringify(params), id); case 'edgechromium': // Full file path support for WebView2 if ( params.event instanceof Event && params.event.type === 'drop' && params.event.dataTransfer.files ) { chrome.webview.postMessageWithAdditionalObjects( 'FilesDropped', params.event.dataTransfer.files ); } return window.chrome.webview.postMessage([ funcName, pywebview.stringify(params), id, ]); case 'cocoa': case 'gtkwebkit2': return window.webkit.messageHandlers.jsBridge.postMessage( pywebview.stringify( { funcName: funcName, params: params, id: id } ) ); case 'qtwebengine': if (!window.pywebview._QWebChannel) { setTimeout(function () { window.pywebview._QWebChannel.objects.external.call( funcName, pywebview.stringify(params), id ); }, 100); } else { window.pywebview._QWebChannel.objects.external.call( funcName, pywebview.stringify(params), id ); } break; default: break; } }, _checkValue: function (funcName, resolve, reject, id) { window.pywebview._returnValuesCallbacks[funcName][id] = function(returnObj) { var value = returnObj.value; var isError = returnObj.isError; delete window.pywebview._returnValuesCallbacks[funcName][id]; if (isError) { var pyError = JSON.parse(value); var error = new Error(pyError.message); error.name = pyError.name; error.stack = pyError.stack; reject(error); } else { resolve(JSON.parse(value)); } }; }, _asyncCallback: function (result, id) { window.pywebview._jsApiCallback('pywebviewAsyncCallback', result, id); }, _isPromise: function (obj) { return ( !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' ); }, stringify: function stringify(obj, timing) { function tryConvertToArray(obj) { try { return Array.prototype.slice.call(obj); } catch (e) { return obj; } } function isArrayLike(a) { return ( a && typeof a.length === 'number' && typeof a !== 'string' && (Array.isArray(a) || (typeof a === 'object' && a.length >= 0 && (a.length === 0 || (a.length - 1) in a))) ); } function serialize(obj, ancestors) { try { if (obj instanceof Window) return 'Window'; if (obj instanceof Node) { obj = pywebview.domJSON.toJSON(obj, { metadata: false, serialProperties: true, }); } var boundSerialize = serialize.bind(obj); if (typeof obj !== 'object' || obj === null) { return obj; } while ( ancestors.length > 0 && ancestors[ancestors.length - 1] !== this ) { ancestors.pop(); } if (ancestors.indexOf(obj) > -1) { return '[Circular Reference]'; } ancestors.push(obj); if (isArrayLike(obj)) { obj = tryConvertToArray(obj); } if (Array.isArray(obj)) { var arr = obj.map(function (value) { return boundSerialize(value, ancestors); }); return arr; } var newObj = {}; for (var key in obj) { if (typeof obj === 'function') { continue; } newObj[key] = boundSerialize(obj[key], ancestors); } return newObj; } catch (e) { console.error(e); return e.toString(); } } var startTime = +new Date(); var _serialize = serialize.bind(null); var finalObj = _serialize(obj, []); var result = JSON.stringify(finalObj); var endTime = +new Date(); if (timing) { console.log('Serialization time: ' + (endTime - startTime) / 1000 + 's'); } return result; }, _getNodeId: function (element) { if (!element) { return null; } var pywebviewId = element.getAttribute('data-pywebview-id') || Math.random().toString(36).substr(2, 11); if (!element.hasAttribute('data-pywebview-id')) { element.setAttribute('data-pywebview-id', pywebviewId); } return pywebviewId; }, _insertNode: function (node, parent, mode) { if (mode === 'LAST_CHILD') { parent.appendChild(node); } else if (mode === 'FIRST_CHILD') { parent.insertBefore(node, parent.firstChild); } else if (mode === 'BEFORE') { parent.parentNode.insertBefore(node, parent); } else if (mode === 'AFTER') { parent.parentNode.insertBefore(node, parent.nextSibling); } else if (mode === 'REPLACE') { parent.parentNode.replaceChild(node, parent); } }, _loadCss: function (css) { var interval = setInterval(function () { if (document.readyState === 'complete') { clearInterval(interval); var cssElement = document.createElement('style'); cssElement.type = 'text/css'; cssElement.innerHTML = css; document.head.appendChild(cssElement); } }, 10); }, _processElements: function (elements) { var serializedElements = []; for (var i = 0; i < elements.length; i++) { var pywebviewId; if (elements[i] === window) { pywebviewId = 'window'; } else if (elements[i] === document) { pywebviewId = 'document'; } else { pywebviewId = window.pywebview._getNodeId(elements[i]); } var node = pywebview.domJSON.toJSON(elements[i], { metadata: false, serialProperties: true, deep: false, }); node._pywebviewId = pywebviewId; serializedElements.push(node); } return serializedElements; }, _debounce: function (func, delay) { var timeout; return function () { var context = this; var args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { func.apply(context, args); }, delay); }; }, };