Clean-up Multiprocess service support
[platform/framework/web/web-provider.git] / src / Plugin / AppBoxPlugin / AppBoxRenderView.cpp
old mode 100644 (file)
new mode 100755 (executable)
index e9a2066..f609963
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved
  *
- *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    Licensed under the Flora License, Version 1.1 (the "License");
  *    you may not use this file except in compliance with the License.
  *    You may obtain a copy of the License at
  *
- *        http://www.apache.org/licenses/LICENSE-2.0
+ *        http://floralicense.org/license/
  *
  *    Unless required by applicable law or agreed to in writing, software
  *    distributed under the License is distributed on an "AS IS" BASIS,
  * @author  Yunchan Cho (yunchan.cho@samsung.com)
  */
 #include <string>
+#include <fstream>
+#include <streambuf>
 #include <Eina.h>
 #include <Evas.h>
 #include <Ecore.h>
+#include <EWebKit2.h>
+#include <ewk_view.h>
+#include <ewk_context.h>
+#include <ewk_settings.h>
 #include <livebox-service.h>
 #include <i_runnable_widget_object.h>
 #include <core_module.h>
 #include <dpl/fast_delegate.h>
+#include <Core/BoxSchemeHandler.h>
 #include <Core/View/IRenderView.h>
 #include <Core/View/IPdHelper.h>
 #include <Core/View/PdHelper.h>
+#include <API/web_provider_livebox_info.h>
 #include <Core/Util/Log.h>
 #include "AppBoxObserver.h"
 #include "AppBoxRenderBuffer.h"
+#include "AppBoxPdHelper.h"
 #include "AppBoxRenderView.h"
 
-#define RENDER_MAX_TIME 10.0
+#define RENDER_MAX_TIME 30.0
+#define SNAPSHOT_REMOVE_TIME 1.0
+
+// injection javascript file regarding creating js object used by box and pd
+static const std::string injectionFile("/usr/share/web-provider/injection.js");
 
 AppBoxRenderView::AppBoxRenderView(
         std::string boxId, std::string instanceId,
-        Evas_Object* boxWin, EwkContextPtr ewkContext)
+        EwkContextPtr ewkContext)
     : m_appId()
     , m_boxId(boxId)
     , m_instanceId(instanceId)
-    , m_boxWin(boxWin)
-    , m_baseWebView()
-    , m_renderInfo()
-    , m_view()
-    , m_startUrl()
+    , m_ewkContext(ewkContext)
+    , m_boxRenderInfo()
+    , m_boxWrt()
+    , m_pdWrt()
+    , m_snapshot()
     , m_fireRenderTimer()
+    , m_removeSnapShotTimer()
     , m_pdHelper()
-    , m_ewkContext(ewkContext)
+    , m_boxRenderBuffer()
+    , m_pdFastOpen(false)
+    , m_boxFinishLoad(false)
+    , m_boxWrt_isSuspended(false)
 {
     LogD("enter");
     m_appId = getAppId(m_boxId);
@@ -56,103 +73,155 @@ AppBoxRenderView::AppBoxRenderView(
         throw; //exception throw!
     }
 
-    evas_object_show(m_boxWin);
-    m_renderBuffer = AppBoxObserver::Instance()->getRenderBuffer(m_instanceId);
+    m_boxRenderBuffer = AppBoxObserver::Instance()->getRenderBuffer(m_instanceId);
+
+    // use fastopen to default
+    // m_pdFastOpen = web_provider_livebox_get_pd_fast_open(m_boxId.c_str()) ? true : false;
+    m_pdFastOpen = true;
     AppBoxObserver::Instance()->registerRenderView(m_instanceId, this);
 }
 
 AppBoxRenderView::~AppBoxRenderView()
 {
     LogD("enter");
-    evas_object_hide(m_boxWin);
     AppBoxObserver::Instance()->unregisterRenderView(m_instanceId);
 }
 
-void AppBoxRenderView::showBox(RenderInfoPtr renderInfo)
+void AppBoxRenderView::showBox(RenderInfoPtr boxRenderInfo)
 {
     LogD("enter");
 
+    // stop updating render buffer
+    m_boxRenderBuffer->stopCanvasUpdate();
+
+    // clear snapshot if this is not the case of pd open
+    if (!m_pdHelper) {
+        clearSnapShot();
+    }
+
     // delete already running timer
-    deleteRenderTimer();
+    deleteTimer(&m_fireRenderTimer);
 
-    // stop touch timer
-    if (livebox_service_mouse_event(m_boxId.c_str())) {
-        m_renderBuffer->deleteTouchTimer();
+    // delete touch timer
+    if (web_provider_livebox_get_mouse_event(m_boxId.c_str())) {
+        m_boxRenderBuffer->deleteTouchTimer();
     }
 
-    // copy to url
-    m_startUrl = getStartUrl(URL_TYPE_BOX, renderInfo->defaultUrlParams);
+    // set boxFinishLoad to false
+    m_boxFinishLoad = false;
 
-    if (!createView()) {
-        LogD("can't create view instance");
-        return;
+    // copy to url
+    std::string boxStartUrl = getStartUrl(URL_TYPE_BOX, boxRenderInfo->defaultUrlParams);
+    if (m_boxWrt) {
+        LogD("existing wrt core is removed");
+        destroyBoxWrtCore();
     }
 
+    m_boxWrt = createWrtCore(
+                URL_TYPE_BOX, boxStartUrl, 
+                boxRenderInfo->window, m_ewkContext);
+    m_boxWrt_isSuspended = false;
+
     // in case of showing box by request of pd open
     if (m_pdHelper) {
-        m_pdHelper->setBaseWebView(m_baseWebView);
+        m_pdHelper->setBoxWebView(m_boxWrt->GetCurrentWebview());
     }
 
     // resize webview fitted to width, height of Box
     evas_object_resize(
-            m_baseWebView, 
-            renderInfo->width,
-            renderInfo->height);
+            m_boxWrt->GetCurrentWebview(), 
+            boxRenderInfo->width,
+            boxRenderInfo->height);
+
 
-    m_view->Show();
-    m_renderInfo = renderInfo;
+    evas_object_show(boxRenderInfo->window);
+    m_boxWrt->Show();
+    m_boxRenderInfo = boxRenderInfo;
 }
 
-bool AppBoxRenderView::createView()
+AppBoxRenderView::WrtCorePtr AppBoxRenderView::createWrtCore(
+        UrlType type, std::string& startUrl, 
+        Evas_Object* win, EwkContextPtr ewkContext)
 {
     LogD("enter");
     
-    if (m_view) {
-        m_view->Hide();
-        m_view.reset();
-        m_baseWebView = NULL;
-    }
-
-    m_view = WRT::CoreModuleSingleton::
+    WrtCorePtr wrt;
+    wrt = WRT::CoreModuleSingleton::
                 Instance().getRunnableWidgetObject(m_appId);
-
     // prepare webview
-    if (m_startUrl.empty()) {
+    if (startUrl.empty()) {
         LogD("no start url");
-        return false;
+        return WrtCorePtr();
     }
-    m_view->PrepareView(m_startUrl, m_boxWin, m_ewkContext.get());
-    m_view->CheckBeforeLaunch();
+    wrt->PrepareView(startUrl, win, ewkContext.get());
+    wrt->CheckBeforeLaunch();
 
     // set callback functions of RunnableWidgetObject
     WRT::UserDelegatesPtr cbs(new WRT::UserDelegates);
     cbs->loadStart = DPL::MakeDelegate(this, &AppBoxRenderView::startLoadCallback);
-    cbs->loadFinish = DPL::MakeDelegate(this, &AppBoxRenderView::finishLoadCallback);
+    if (type == URL_TYPE_BOX) {
+        cbs->loadFinish = DPL::MakeDelegate(this, &AppBoxRenderView::finishBoxLoadCallback);
+    } else {
+        cbs->loadFinish = DPL::MakeDelegate(this, &AppBoxRenderView::finishPdLoadCallback);
+    }
+
     cbs->bufferSet = DPL::MakeDelegate(this, &AppBoxRenderView::setBufferCallback);
     cbs->bufferUnset = DPL::MakeDelegate(this, &AppBoxRenderView::unsetBufferCallback);
-    cbs->windowCreateBefore = DPL::MakeDelegate(this, &AppBoxRenderView::createWindowBeforeCallback);
-    cbs->windowCreateAfter = DPL::MakeDelegate(this, &AppBoxRenderView::createWindowAfterCallback);
-    m_view->SetUserDelegates(cbs);
+    if (!m_pdFastOpen) {
+        cbs->windowCreateBefore =
+            DPL::MakeDelegate(this, &AppBoxRenderView::createWindowBeforeCallback);
+        cbs->windowCreateAfter =
+            DPL::MakeDelegate(this, &AppBoxRenderView::createWindowAfterCallback);
+    }
 
-    // set base webview
-    m_baseWebView = m_view->GetCurrentWebview();
-    m_renderBuffer->startCanvasUpdate();
-    return true;
+    cbs->navigationDecide =
+        DPL::MakeDelegate(this, &AppBoxRenderView::decideNavigationCallback);
+    cbs->webCrash = DPL::MakeDelegate(this, &AppBoxRenderView::crashWebProcessCallback);
+    wrt->SetUserDelegates(cbs);
+
+    // set basic webview setting
+    setWebViewBasicSetting(wrt->GetCurrentWebview());
+    return wrt;
+}
+
+void AppBoxRenderView::destroyBoxWrtCore()
+{
+    LogD("enter");
+
+    m_boxRenderBuffer->stopCanvasUpdate();
+    deleteTimer(&m_fireRenderTimer);
+    deleteTimer(&m_removeSnapShotTimer);
+    destroyWrtCore(m_boxWrt);
+    m_boxWrt.reset();
+
+    // temp
+    m_boxWrt_isSuspended = false;
 }
 
-bool AppBoxRenderView::destroyView()
+void AppBoxRenderView::destroyPdWrtCore()
 {
     LogD("enter");
 
-    m_renderBuffer->stopCanvasUpdate();
-    ecore_idler_add(destroyViewIdlerCallback, this);
-    return true;
+    destroyWrtCore(m_pdWrt);
+    m_pdWrt.reset();
+}
+
+void AppBoxRenderView::destroyWrtCore(WrtCorePtr wrt)
+{
+    LogD("enter");
+
+    if (wrt) {
+        wrt->Hide();
+    }
 }
 
 void AppBoxRenderView::hideBox()
 {
     LogD("enter");
-    destroyView();
+    destroyBoxWrtCore();
+    if (m_boxRenderInfo->window) {
+        evas_object_hide(m_boxRenderInfo->window);
+    }
 }
 
 void AppBoxRenderView::pauseBox()
@@ -165,41 +234,90 @@ void AppBoxRenderView::resumeBox()
     LogD("enter");
 }
 
-void AppBoxRenderView::showPd(Evas_Object* pdWin, RenderInfoPtr renderInfo)
+void AppBoxRenderView::showPd(RenderInfoPtr pdRenderInfo, RenderInfoPtr boxRenderInfo)
 {
     LogD("enter");
 
-    // create pd helper
-    std::string pdStartUrl = getStartUrl(URL_TYPE_PD, renderInfo->defaultUrlParams);
-    m_pdHelper = PdHelper::create(pdWin, pdStartUrl, renderInfo);
+    std::string pdStartUrl = getStartUrl(URL_TYPE_PD, pdRenderInfo->defaultUrlParams);
+    if (m_pdFastOpen) {
+        destroyPdWrtCore();
+        // if you want to launch new Web Process for PD, use the following line.
+        // EwkContextPtr pdContext = AppBoxObserver::Instance()->getPdEwkContext();
+        m_pdWrt = createWrtCore(URL_TYPE_PD, pdStartUrl, pdRenderInfo->window, m_ewkContext);
+        if (!m_pdWrt) {
+            LogD("no wrt core instance");
+            return;
+        }
+        m_pdHelper = AppBoxPdHelper::create(pdRenderInfo->window);
+
+        // resize webview fitted to width, height of pd
+        evas_object_resize(
+                m_pdWrt->GetCurrentWebview(), 
+                pdRenderInfo->width,
+                pdRenderInfo->height);
+        // show pd
+        m_pdWrt->Show();
+        m_pdHelper->finishOpen(m_pdWrt->GetCurrentWebview());
+    } else {
+        m_pdHelper = PdHelper::create(pdRenderInfo, pdStartUrl);
+    }
 
     // show pd window
-    evas_object_show(pdWin);
-    showBox(m_renderInfo);
+    evas_object_show(pdRenderInfo->window);
+
+    // show box
+    showBox(boxRenderInfo);
+
+    // start timer for clearing existing snapshot in case of only pd open
+    addTimer(&m_removeSnapShotTimer, SNAPSHOT_REMOVE_TIME, removeSnapShotTimerCallback);
+
 }
 
 void AppBoxRenderView::hidePd()
 {
     LogD("enter");
 
+    if (m_pdFastOpen) {
+        destroyPdWrtCore();
+    }
     m_pdHelper->close();
     m_pdHelper.reset();
 
-    // destory box webview to stop rendering
-    destroyView();
+    // stop box webview
+    stopRenderBox();
+}
+
+Evas_Object* AppBoxRenderView::getBoxWebView()
+{
+    if (!m_pdHelper) {
+        return m_boxWrt->GetCurrentWebview();
+    } else {
+        // Here, we can't use GetCurrentWebView() of wrt-core to get Box' webview,
+        // because in the non fast-open, GetCurrentWebview() returns PD's webview.
+        return m_pdHelper->getBoxWebView();
+    }
+}
+
+Evas_Object* AppBoxRenderView::getPdWebView()
+{
+    if (!m_pdHelper) {
+        return NULL;
+    }
+
+    return m_pdHelper->getPdWebView();
 }
 
 std::string AppBoxRenderView::getAppId(std::string& boxId)
 {
-    // TODO more exact and safe parsing is needed
-    std::string temp = std::string(boxId);
-    int found = temp.find_last_of(".");
-    if (found == std::string::npos) {
+    LogD("enter");
+
+    const char* appId = web_provider_livebox_get_app_id(boxId.c_str());
+    if (!appId) {
+        LogD("no appid of %s", boxId.c_str());
         return std::string();
     }
 
-    temp.assign(temp, 0, found);
-    return temp;
+    return std::string(appId);
 }
 
 std::string AppBoxRenderView::getStartUrl(UrlType type, std::string& defaultParams)
@@ -222,82 +340,205 @@ std::string AppBoxRenderView::getStartUrl(UrlType type, std::string& defaultPara
     return url;
 }
 
-void AppBoxRenderView::addRenderTimer()
+Evas_Object* AppBoxRenderView::getCurrentSnapShot() 
+{
+    LogD("enter");
+    clearSnapShot();
+    m_snapshot = m_boxRenderBuffer->getSnapshot();
+
+    return m_snapshot;
+}
+
+void AppBoxRenderView::clearSnapShot()
 {
     LogD("enter");
-    if (m_fireRenderTimer) {
-        deleteRenderTimer();
+    if (m_snapshot) {
+        evas_object_del(m_snapshot);
+        m_snapshot = NULL;
     }
+}
 
-    m_fireRenderTimer = ecore_timer_add(
-                            RENDER_MAX_TIME, 
-                            fireRenderTimerCallback,
-                            this);
+void AppBoxRenderView::showSnapShot()
+{
+    LogD("enter");
+    if (m_snapshot) {
+        evas_object_raise(m_snapshot);
+        evas_object_show(m_snapshot);
+    }
 }
 
-void AppBoxRenderView::deleteRenderTimer()
+void AppBoxRenderView::hideSnapShot()
 {
     LogD("enter");
-    if (m_fireRenderTimer) {
-        ecore_timer_del(m_fireRenderTimer);
-        m_fireRenderTimer = NULL;
+    if (m_snapshot) {
+        evas_object_hide(m_snapshot);
+        evas_object_lower(m_snapshot);
     }
 }
 
-Eina_Bool AppBoxRenderView::fireRenderTimerCallback(void* data)
+void AppBoxRenderView::addTimer(Ecore_Timer** timer, double interval, Ecore_Task_Cb callback)
 {
     LogD("enter");
+    if (*timer) {
+        deleteTimer(timer);
+    }
 
-    AppBoxRenderView* This = static_cast<AppBoxRenderView*>(data);
-    This->m_fireRenderTimer = NULL;
-    
-    This->m_renderBuffer->stopCanvasUpdate();
-    if (livebox_service_mouse_event(This->m_boxId.c_str())) {
+    *timer = ecore_timer_add(interval, callback, this);
+}
+
+void AppBoxRenderView::deleteTimer(Ecore_Timer** timer)
+{
+    LogD("enter");
+    if (*timer) {
+        ecore_timer_del(*timer);
+        *timer = NULL;
+    }
+}
+
+void AppBoxRenderView::stopRenderBox()
+{
+    deleteTimer(&m_fireRenderTimer);
+    m_boxRenderBuffer->stopCanvasUpdate();
+    if (web_provider_livebox_get_mouse_event(m_boxId.c_str())) {
         // stop touch timer
-        This->m_renderBuffer->deleteTouchTimer();
-        This->m_view->Suspend();
+        m_boxRenderBuffer->deleteTouchTimer();
+
+        // temp condition
+        if (m_boxWrt_isSuspended == false)
+        {
+            m_boxWrt_isSuspended = true;
+            m_boxWrt->Suspend();
+        }
     } else {
-        // webview should be removed
-        ecore_idler_add(destroyViewIdlerCallback, This);
+        // Before webview should be removed,
+        // new evas object with last render data should be created
+        // otherwise, after webview is removed, box is white screen.
+        evas_object_show(getCurrentSnapShot());
+        destroyBoxWrtCore();
     }
+}
+
+void AppBoxRenderView::setWebViewBasicSetting(Evas_Object* webview)
+{
+    LogD("enter");
+
+    if (!webview) {
+        return;
+    }
+    Ewk_Settings* setting = ewk_view_settings_get(webview);
+    // To support transparent background
+    evas_object_color_set(webview, 0, 0, 0, 1);
+    ewk_view_visibility_set(webview, EINA_TRUE);
+
+    // To know starting point for updating buffer
+    evas_object_smart_callback_add(
+            webview,
+            "load,nonemptylayout,finished",
+            loadNonEmptyLayoutFinishedCallback,
+            this);
+    evas_object_smart_callback_add(
+            webview,
+            "frame,rendered",
+            frameRenderedCallback,
+            this);
+}
+
+Eina_Bool AppBoxRenderView::fireRenderTimerCallback(void* data)
+{
+    LogD("enter");
+
+    AppBoxRenderView* This = static_cast<AppBoxRenderView*>(data);
+    This->m_fireRenderTimer = NULL;
+    This->stopRenderBox();
 
     return ECORE_CALLBACK_CANCEL;
 }
 
-Eina_Bool AppBoxRenderView::destroyViewIdlerCallback(void* data)
+Eina_Bool AppBoxRenderView::removeSnapShotTimerCallback(void* data)
 {
     LogD("enter");
 
     AppBoxRenderView* This = static_cast<AppBoxRenderView*>(data);
-    if (This->m_view) {
-        This->m_view->Hide();
-        This->m_view.reset();
-        This->m_baseWebView = NULL;
+    if (!(This->m_boxFinishLoad)) {
+        return ECORE_CALLBACK_RENEW;
     }
 
+    // hide snapshot because valid frame has been prepared generally.
+    This->clearSnapShot();
+
+    This->m_removeSnapShotTimer = NULL;
+    return ECORE_CALLBACK_CANCEL;
+}
+
+Eina_Bool AppBoxRenderView::openPdIdlerCallback(void* data)
+{
+    LogD("enter");
+    AppBoxRenderView* This = static_cast<AppBoxRenderView*>(data);
+    if (This && This->m_pdHelper) {
+        This->m_pdHelper->startOpen();
+    }
     return ECORE_CALLBACK_CANCEL;
 }
 
+void AppBoxRenderView::executeScriptCallback(
+        Evas_Object* webview, const char* result, void* data)
+{
+    LogD("enter");
+
+    std::string resultStr(result ? result : "null");
+    LogD("result: %s", resultStr.c_str());
+}
+
 void AppBoxRenderView::startLoadCallback(Evas_Object* webview)
 {
-    // start load
+    LogD("enter");
+    if(!webview) {
+        return;
+    }
+    // execute injection for creating js objects
+    std::ifstream jsFile(injectionFile);
+    std::string script((std::istreambuf_iterator<char>(jsFile)),
+                        std::istreambuf_iterator<char>());
+
+    LogD("injected js code: %s", script.c_str());
+    ewk_view_script_execute(webview, script.c_str(), executeScriptCallback, this);
 }
 
-void AppBoxRenderView::finishLoadCallback(Evas_Object* webview)
+void AppBoxRenderView::finishBoxLoadCallback(Evas_Object* webview)
 {
     LogD("enter");
+    if (!webview) {
+        return;
+    }
+
     ewk_view_visibility_set(webview, EINA_TRUE);
 
     if (!m_pdHelper) {
         // start render timer
-        addRenderTimer();
+        addTimer(&m_fireRenderTimer, RENDER_MAX_TIME, fireRenderTimerCallback);
     } else {
-        if (!(m_pdHelper->isPdOpened()) && 
-                webview == m_pdHelper->getBaseWebView())
-        {
-            m_pdHelper->startOpen();
+        if (!m_pdFastOpen) {
+            if (!(m_pdHelper->isPdOpened()) && 
+                    webview == m_pdHelper->getBoxWebView())
+            {
+                // open pd
+                ecore_idler_add(openPdIdlerCallback, this);
+            }
         }
     }
+
+    // set flag
+    m_boxFinishLoad = true;
+}
+
+void AppBoxRenderView::finishPdLoadCallback(Evas_Object* webview)
+{
+    LogD("enter");
+    if (!webview) {
+        return;
+    }
+
+    ewk_view_visibility_set(webview, EINA_TRUE);
 }
 
 void AppBoxRenderView::createWindowBeforeCallback(Evas** canvas, Evas_Object* parent)
@@ -306,13 +547,13 @@ void AppBoxRenderView::createWindowBeforeCallback(Evas** canvas, Evas_Object* pa
 
     if (m_pdHelper) {
         if (!(m_pdHelper->isPdOpened()) && 
-                parent == m_pdHelper->getBaseWebView())
+                parent == m_pdHelper->getBoxWebView())
         {
             LogD("pd canvas is used");
             *canvas = m_pdHelper->getPdCanvas();
             return;
         }
-    } 
+    }
     
     LogD("canvas of this webview is used");
     *canvas = evas_object_evas_get(parent);
@@ -321,17 +562,25 @@ void AppBoxRenderView::createWindowBeforeCallback(Evas** canvas, Evas_Object* pa
 void AppBoxRenderView::createWindowAfterCallback(Evas_Object* parent, Evas_Object* child)
 {
     LogD("enter");
+    if (!parent) {
+        return;
+    }
 
     if (m_pdHelper) {
         Evas* parentCanvas = evas_object_evas_get(parent);
         Evas* childCanvas = evas_object_evas_get(child);
 
         if (parentCanvas != childCanvas) {
+           // wrt-core change visibility value to false internally
+           // So plugin should reset this value to true for painting parent webview
+           ewk_view_visibility_set(parent, EINA_TRUE);
            evas_object_show(parent);
-           evas_object_show(child);
            m_pdHelper->finishOpen(child); 
         }
     }
+
+    setWebViewBasicSetting(child);
+    evas_object_show(child);
 }
 
 void AppBoxRenderView::setBufferCallback(Evas_Object* webview)
@@ -346,3 +595,37 @@ void AppBoxRenderView::unsetBufferCallback(Evas_Object* webview)
     LogD("enter");
     evas_object_hide(webview);
 }
+
+void AppBoxRenderView::decideNavigationCallback(Evas_Object* webview, std::string& uri)
+{
+    LogD("enter");
+
+    // navigation of box scheme should be ignored
+    if(BoxSchemeHandler::Instance()->isBoxScheme(uri)) {
+        LogD("box scheme");
+        BoxSchemeHandler::Instance()->process(m_instanceId, uri);
+    }
+}
+
+void AppBoxRenderView::crashWebProcessCallback()
+{
+    LogD("enter");
+    ewk_shutdown();
+    elm_exit();
+}
+
+void AppBoxRenderView::loadNonEmptyLayoutFinishedCallback(
+        void* data, Evas_Object* webview, void* eventInfo)
+{
+    LogD("enter");
+}
+
+void AppBoxRenderView::frameRenderedCallback(
+        void* data, Evas_Object* webview, void* eventInfo)
+{
+    LogD("enter");
+
+    // start to update render buffer!
+    AppBoxRenderView* This = static_cast<AppBoxRenderView*>(data);
+    This->m_boxRenderBuffer->startCanvasUpdate();
+}