/* * Copyright (C) 2010 Apple Inc. All rights reserved. * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "QtBuiltinBundlePage.h" #include "APIData.h" #include "QtBuiltinBundle.h" #include "WKArray.h" #include "WKBundleFrame.h" #include "WKData.h" #include "WKRetainPtr.h" #include "WKString.h" #include "WKStringPrivate.h" #include "WKStringQt.h" #include #include #include #include #include namespace WebKit { typedef JSClassRef (*CreateClassRefCallback)(); static void registerNavigatorObject(JSObjectRef *object, JSStringRef name, JSGlobalContextRef context, void* data, CreateClassRefCallback createClassRefCallback, JSStringRef postMessageName, JSObjectCallAsFunctionCallback postMessageCallback) { static JSStringRef navigatorName = JSStringCreateWithUTF8CString("navigator"); if (*object) JSValueUnprotect(context, *object); *object = JSObjectMake(context, createClassRefCallback(), data); JSValueProtect(context, *object); JSObjectRef postMessage = JSObjectMakeFunctionWithCallback(context, postMessageName, postMessageCallback); JSObjectSetProperty(context, *object, postMessageName, postMessage, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly, 0); JSValueRef navigatorValue = JSObjectGetProperty(context, JSContextGetGlobalObject(context), navigatorName, 0); if (!JSValueIsObject(context, navigatorValue)) return; JSObjectRef navigatorObject = JSValueToObject(context, navigatorValue, 0); JSObjectSetProperty(context, navigatorObject, name, *object, kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly, 0); } static JSClassRef createEmptyJSClassRef() { const JSClassDefinition definition = kJSClassDefinitionEmpty; return JSClassCreate(&definition); } static JSClassRef navigatorQtObjectClass() { static JSClassRef classRef = createEmptyJSClassRef(); return classRef; } static JSValueRef qt_postMessageCallback(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) { // FIXME: should it work regardless of the thisObject? if (argumentCount < 1 || !JSValueIsString(context, arguments[0])) return JSValueMakeUndefined(context); QtBuiltinBundlePage* bundlePage = reinterpret_cast(JSObjectGetPrivate(thisObject)); ASSERT(bundlePage); // FIXME: needed? if (!bundlePage->navigatorQtObjectEnabled()) return JSValueMakeUndefined(context); JSRetainPtr jsContents = JSValueToStringCopy(context, arguments[0], 0); WKRetainPtr contents(AdoptWK, WKStringCreateWithJSString(jsContents.get())); bundlePage->postMessageFromNavigatorQtObject(contents.get()); return JSValueMakeUndefined(context); } static JSObjectRef createWrappedMessage(JSGlobalContextRef context, WKStringRef data) { static JSStringRef dataName = JSStringCreateWithUTF8CString("data"); JSRetainPtr jsData = WKStringCopyJSString(data); JSObjectRef wrappedMessage = JSObjectMake(context, 0, 0); JSObjectSetProperty(context, wrappedMessage, dataName, JSValueMakeString(context, jsData.get()), kJSPropertyAttributeDontDelete | kJSPropertyAttributeReadOnly, 0); return wrappedMessage; } static void callOnMessage(JSObjectRef object, WKStringRef contents, WKBundlePageRef page) { static JSStringRef onmessageName = JSStringCreateWithUTF8CString("onmessage"); if (!object) return; WKBundleFrameRef frame = WKBundlePageGetMainFrame(page); JSGlobalContextRef context = WKBundleFrameGetJavaScriptContext(frame); JSValueRef onmessageValue = JSObjectGetProperty(context, object, onmessageName, 0); if (!JSValueIsObject(context, onmessageValue)) return; JSObjectRef onmessageFunction = JSValueToObject(context, onmessageValue, 0); if (!JSObjectIsFunction(context, onmessageFunction)) return; JSObjectRef wrappedMessage = createWrappedMessage(context, contents); JSObjectCallAsFunction(context, onmessageFunction, 0, 1, &wrappedMessage, 0); } #if ENABLE(QT_WEBCHANNEL) static JSClassRef navigatorQtWebChannelTransportObjectClass() { static JSClassRef classRef = createEmptyJSClassRef(); return classRef; } static QByteArray convertJsonTextToBinary(const CString& textJson) { QByteArray jsonData = QByteArray::fromRawData(textJson.data(), textJson.length()); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); if (error.error != QJsonParseError::NoError) { qWarning() << "Failed to parse the client WebKit QWebChannel message as JSON: " << jsonData << "Error message is:" << error.errorString(); return QByteArray(); } if (!doc.isObject()) { qWarning() << "Received WebKit QWebChannel message is not a JSON object: " << jsonData; return QByteArray(); } return doc.toBinaryData(); } static JSValueRef qt_postWebChannelMessageCallback(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) { // FIXME: should it work regardless of the thisObject? if (argumentCount < 1 || !JSValueIsString(context, arguments[0])) return JSValueMakeUndefined(context); QtBuiltinBundlePage* bundlePage = reinterpret_cast(JSObjectGetPrivate(thisObject)); ASSERT(bundlePage); JSC::ExecState* exec = toJS(context); JSC::JSLockHolder locker(exec); JSC::JSValue jsValue = toJS(exec, arguments[0]); CString textJson = jsValue.toString(exec)->value(exec).utf8(); QByteArray message = convertJsonTextToBinary(textJson); if (!message.isEmpty()) bundlePage->postMessageFromNavigatorQtWebChannelTransport(WKDataCreate(reinterpret_cast(message.data()), message.length())); return JSValueMakeUndefined(context); } #endif QtBuiltinBundlePage::QtBuiltinBundlePage(QtBuiltinBundle* bundle, WKBundlePageRef page) : m_bundle(bundle) , m_page(page) , m_navigatorQtObject(0) , m_navigatorQtObjectEnabled(false) #if ENABLE(QT_WEBCHANNEL) , m_navigatorQtWebChannelTransportObject(0) #endif { WKBundlePageLoaderClientV0 loaderClient; memset(&loaderClient, 0, sizeof(WKBundlePageLoaderClientV0)); loaderClient.base.version = 0; loaderClient.base.clientInfo = this; loaderClient.didClearWindowObjectForFrame = didClearWindowForFrame; WKBundlePageSetPageLoaderClient(m_page, &loaderClient.base); } QtBuiltinBundlePage::~QtBuiltinBundlePage() { if (!m_navigatorQtObject #if ENABLE(QT_WEBCHANNEL) && !m_navigatorQtWebChannelTransportObject #endif ) { return; } WKBundleFrameRef frame = WKBundlePageGetMainFrame(m_page); JSGlobalContextRef context = WKBundleFrameGetJavaScriptContext(frame); if (m_navigatorQtObject) JSValueUnprotect(context, m_navigatorQtObject); #if ENABLE(QT_WEBCHANNEL) if (m_navigatorQtWebChannelTransportObject) JSValueUnprotect(context, m_navigatorQtWebChannelTransportObject); #endif } void QtBuiltinBundlePage::didClearWindowForFrame(WKBundlePageRef page, WKBundleFrameRef frame, WKBundleScriptWorldRef world, const void* clientInfo) { static_cast(const_cast(clientInfo))->didClearWindowForFrame(frame, world); } void QtBuiltinBundlePage::didClearWindowForFrame(WKBundleFrameRef frame, WKBundleScriptWorldRef world) { if (!WKBundleFrameIsMainFrame(frame) || WKBundleScriptWorldNormalWorld() != world) return; JSGlobalContextRef context = WKBundleFrameGetJavaScriptContextForWorld(frame, world); registerNavigatorQtObject(context); #if ENABLE(QT_WEBCHANNEL) registerNavigatorQtWebChannelTransportObject(context); #endif } void QtBuiltinBundlePage::postMessageFromNavigatorQtObject(WKStringRef message) { static WKStringRef messageName = WKStringCreateWithUTF8CString("MessageFromNavigatorQtObject"); postNavigatorMessage(messageName, message); } void QtBuiltinBundlePage::didReceiveMessageToNavigatorQtObject(WKStringRef message) { callOnMessage(m_navigatorQtObject, message, m_page); } void QtBuiltinBundlePage::setNavigatorQtObjectEnabled(bool enabled) { if (enabled == m_navigatorQtObjectEnabled) return; // Note that this will take effect only after the next page load. m_navigatorQtObjectEnabled = enabled; } void QtBuiltinBundlePage::registerNavigatorQtObject(JSGlobalContextRef context) { static JSStringRef name = JSStringCreateWithUTF8CString("qt"); static JSStringRef postMessageName = JSStringCreateWithUTF8CString("postMessage"); registerNavigatorObject(&m_navigatorQtObject, name, context, this, &navigatorQtObjectClass, postMessageName, &qt_postMessageCallback); } #if ENABLE(QT_WEBCHANNEL) void QtBuiltinBundlePage::registerNavigatorQtWebChannelTransportObject(JSGlobalContextRef context) { static JSStringRef name = JSStringCreateWithUTF8CString("qtWebChannelTransport"); static JSStringRef postMessageName = JSStringCreateWithUTF8CString("send"); registerNavigatorObject(&m_navigatorQtWebChannelTransportObject, name, context, this, &navigatorQtWebChannelTransportObjectClass, postMessageName, &qt_postWebChannelMessageCallback); } void QtBuiltinBundlePage::didReceiveMessageToNavigatorQtWebChannelTransport(WKDataRef contents) { const char* bytes = reinterpret_cast(WKDataGetBytes(contents)); int size = WKDataGetSize(contents); QJsonDocument doc = QJsonDocument::fromRawData(bytes, size, QJsonDocument::BypassValidation); ASSERT(doc.isObject()); QByteArray textJson = doc.toJson(QJsonDocument::Compact); WKStringRef message = WKStringCreateWithUTF8CString(textJson.constData()); callOnMessage(m_navigatorQtWebChannelTransportObject, message, m_page); } void QtBuiltinBundlePage::postMessageFromNavigatorQtWebChannelTransport(WKDataRef message) { static WKStringRef messageName = WKStringCreateWithUTF8CString("MessageFromNavigatorQtWebChannelTransportObject"); postNavigatorMessage(messageName, message); } #endif void QtBuiltinBundlePage::postNavigatorMessage(WKStringRef messageName, WKTypeRef message) { WKTypeRef body[] = { page(), message }; WKRetainPtr messageBody(AdoptWK, WKArrayCreate(body, sizeof(body) / sizeof(WKTypeRef))); WKBundlePostMessage(m_bundle->toRef(), messageName, messageBody.get()); } } // namespace WebKit