1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components/rich_text/resource/rich_text_delegate.h"
17 
18 #include <algorithm>
19 #include <iomanip>
20 #include <sstream>
21 
22 #include "base/log/log.h"
23 #include "core/event/ace_event_helper.h"
24 #include "core/event/back_end_event_manager.h"
25 #include "frameworks/bridge/js_frontend/frontend_delegate_impl.h"
26 
27 namespace OHOS::Ace {
28 
29 namespace {
30 
31 constexpr char RICH_TEXT_METHOD_CHANGE_VISIBILITY[] = "changeRichTextVisibility";
32 constexpr char RICH_TEXT_METHOD_HIDE_RICHTEXT_WHEN_POP[] = "hideRichTextWhenPop";
33 constexpr char RICH_TEXT_METHOD_HIDE_RICHTEXT_WHEN_PUSH[] = "hideRichTextWhenPush";
34 constexpr char RICH_TEXT_METHOD_SHOW_RICHTEXT[] = "showRichText";
35 constexpr char RICH_TEXT_METHOD_UPDATE_CONTENT[] = "updateRichTextContent";
36 constexpr char RICH_TEXT_METHOD_UPDATE_TRANSLATE[] = "updateTranslate";
37 constexpr char RICH_TEXT_METHOD_UPDATE_CONTENT_TRANSLATE[] = "updateContentTranslate";
38 
39 constexpr char RICH_TEXT_EVENT_LOAD_START[] = "onPageStarted";
40 constexpr char RICH_TEXT_EVENT_LOAD_FINISHED[] = "onPageFinished";
41 constexpr char RICH_TEXT_EVENT_GOT_LAYOUT_PARAM[] = "onGotLayoutParam";
42 constexpr char RICH_TEXT_EVENT_LOAD_ERROR[] = "onPageError";
43 
44 constexpr char RICH_TEXT_RESOURCE_NAME[] = "richtext";
45 constexpr char NTC_PARAM_RICH_TEXT[] = "richtext";
46 
47 constexpr char NTC_PARAM_LEFT[] = "left";
48 constexpr char NTC_PARAM_TOP[] = "top";
49 constexpr char NTC_PARAM_URL[] = "url";
50 constexpr char NTC_PARAM_X[] = "x";
51 constexpr char NTC_PARAM_Y[] = "y";
52 constexpr char NTC_PARAM_LAYOUT_HEIGHT[] = "layoutHeight";
53 constexpr char NTC_PARAM_LAYOUT_WIDTH[] = "layoutWidth";
54 constexpr char NTC_PARAM_CONTENT_HEIGHT[] = "contentHeight";
55 constexpr char NTC_PARAM_RICHTEXT_VISIBILITY[] = "visibility";
56 constexpr char NTC_PARAM_PAGE_PATH[] = "pageRoutePath";
57 constexpr char NTC_PARAM_DESCRIPTION[] = "description";
58 constexpr char NTC_PARAM_CONTENT_DATA[] = "data";
59 constexpr char NTC_PARAM_ERROR_CODE[] = "errorCode";
60 
61 constexpr char NTC_ERROR[] = "create error";
62 constexpr char RICH_TEXT_ERROR_CODE_CREATEFAIL[] = "error-rich-text-delegate-000001";
63 constexpr char RICH_TEXT_ERROR_MSG_CREATEFAIL[] = "create rich text delegate failed.";
64 
65 } // namespace
66 
~RichTextDelegate()67 RichTextDelegate::~RichTextDelegate()
68 {
69     ReleasePlatformResource();
70 }
71 
ReleasePlatformResource()72 void RichTextDelegate::ReleasePlatformResource()
73 {
74     Stop();
75     Release();
76 }
77 
Stop()78 void RichTextDelegate::Stop()
79 {
80     auto context = context_.Upgrade();
81     if (!context) {
82         LOGE("fail to get context when stop");
83         return;
84     }
85     auto platformTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(),
86         TaskExecutor::TaskType::PLATFORM);
87     if (platformTaskExecutor.IsRunOnCurrentThread()) {
88         UnregisterEvent();
89     } else {
90         platformTaskExecutor.PostTask(
91             [weak = WeakClaim(this)] {
92                 auto delegate = weak.Upgrade();
93                 if (delegate) {
94                     delegate->UnregisterEvent();
95                 }
96             }, "ArkUIRichTextUnregisterEvent");
97     }
98 }
99 
UnregisterEvent()100 void RichTextDelegate::UnregisterEvent()
101 {
102     auto context = context_.Upgrade();
103     if (!context) {
104         LOGE("fail to get context when unregister event");
105         return;
106     }
107     auto resRegister = context->GetPlatformResRegister();
108     resRegister->UnregisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_START));
109     resRegister->UnregisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_FINISHED));
110     resRegister->UnregisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_ERROR));
111     resRegister->UnregisterEvent(MakeEventHash(RICH_TEXT_EVENT_GOT_LAYOUT_PARAM));
112 }
113 
CreatePlatformResource(const WeakPtr<PipelineContext> & context,const int32_t top,const int32_t left,const bool visible)114 void RichTextDelegate::CreatePlatformResource(
115     const WeakPtr<PipelineContext>& context, const int32_t top, const int32_t left, const bool visible)
116 {
117     context_ = context;
118     CreatePluginResource(context, top, left, visible);
119     InitWebEvent();
120 }
121 
CreatePluginResource(const WeakPtr<PipelineContext> & context,const int32_t top,const int32_t left,const bool visible)122 void RichTextDelegate::CreatePluginResource(
123     const WeakPtr<PipelineContext>& context, const int32_t top, const int32_t left, const bool visible)
124 {
125     state_ = State::CREATING;
126 
127     auto webCom = webComponent_.Upgrade();
128     if (!webCom) {
129         state_ = State::CREATEFAILED;
130         OnError(NTC_ERROR, "fail to call WebDelegate::Create due to webComponent is null");
131         return;
132     }
133 
134     auto pipelineContext = context.Upgrade();
135     if (!pipelineContext) {
136         state_ = State::CREATEFAILED;
137         OnError(NTC_ERROR, "fail to call WebDelegate::Create due to context is null");
138         return;
139     }
140     context_ = context;
141     auto platformTaskExecutor = SingleTaskExecutor::Make(pipelineContext->GetTaskExecutor(),
142                                                          TaskExecutor::TaskType::PLATFORM);
143     auto resRegister = pipelineContext->GetPlatformResRegister();
144     auto weakRes = AceType::WeakClaim(AceType::RawPtr(resRegister));
145     platformTaskExecutor.PostTask(
146         [weak = WeakClaim(this), weakRes, top, left, visible] {
147             auto delegate = weak.Upgrade();
148             if (!delegate) {
149                 return;
150             }
151             auto webCom = delegate->webComponent_.Upgrade();
152             if (!webCom) {
153                 delegate->OnError(NTC_ERROR, "fail to call WebDelegate::SetSrc PostTask");
154             }
155             auto resRegister = weakRes.Upgrade();
156             if (!resRegister) {
157                 delegate->OnError(RICH_TEXT_ERROR_CODE_CREATEFAIL, RICH_TEXT_ERROR_MSG_CREATEFAIL);
158                 return;
159             }
160 
161             auto context = delegate->context_.Upgrade();
162             if (!context) {
163                 LOGE("context is null");
164                 return;
165             }
166 
167             delegate->id_ = CREATING_ID;
168 
169             std::string pageUrl;
170             int32_t pageId;
171             OHOS::Ace::Framework::DelegateClient::GetInstance().GetWebPageUrl(pageUrl, pageId);
172             std::string richTextVisible = visible ? "true" : "false";
173             delegate->pageUrl_ = pageUrl;
174             delegate->pageId_ = pageId;
175 
176             std::stringstream paramStream;
177             paramStream << NTC_PARAM_RICH_TEXT << RICHTEXT_PARAM_EQUALS << delegate->id_ << RICHTEXT_PARAM_AND
178                         << NTC_PARAM_CONTENT_DATA << RICHTEXT_PARAM_EQUALS << webCom->GetData() << RICHTEXT_PARAM_AND
179                         << NTC_PARAM_LEFT << RICHTEXT_PARAM_EQUALS << left << RICHTEXT_PARAM_AND
180                         << NTC_PARAM_TOP << RICHTEXT_PARAM_EQUALS << top << RICHTEXT_PARAM_AND
181                         << NTC_PARAM_RICHTEXT_VISIBILITY << RICHTEXT_PARAM_EQUALS << richTextVisible
182                         << RICHTEXT_PARAM_AND << NTC_PARAM_PAGE_PATH << RICHTEXT_PARAM_EQUALS << pageUrl;
183 
184             std::string param = paramStream.str();
185             delegate->id_ = resRegister->CreateResource(RICH_TEXT_RESOURCE_NAME, param);
186             if (delegate->id_ == INVALID_ID) {
187                 delegate->OnError(RICH_TEXT_ERROR_CODE_CREATEFAIL, RICH_TEXT_ERROR_MSG_CREATEFAIL);
188                 return;
189             }
190             delegate->state_ = State::CREATED;
191             delegate->hash_ = delegate->MakeResourceHash();
192             delegate->RegisterWebEvent();
193             delegate->BindPopPageSuccessMethod();
194             delegate->BindIsPagePathInvalidMethod();
195         }, "ArkUIRichTextCreatePluginResource");
196 }
197 
InitWebEvent()198 void RichTextDelegate::InitWebEvent()
199 {
200     auto webCom = webComponent_.Upgrade();
201     if (!webCom) {
202         state_ = State::CREATEFAILED;
203         OnError(NTC_ERROR, "fail to call get event due to rich text component is null");
204         return;
205     }
206     if (!webCom->GetPageStartedEventId().IsEmpty()) {
207         onPageStarted_ =
208             AceAsyncEvent<void(const std::string&)>::Create(webCom->GetPageStartedEventId(), context_);
209     }
210     if (!webCom->GetPageFinishedEventId().IsEmpty()) {
211         onPageFinished_ =
212             AceAsyncEvent<void(const std::string&)>::Create(webCom->GetPageFinishedEventId(), context_);
213     }
214     if (!webCom->GetPageErrorEventId().IsEmpty()) {
215         onPageError_ =
216             AceAsyncEvent<void(const std::string&)>::Create(webCom->GetPageErrorEventId(), context_);
217     }
218 }
219 
RegisterWebEvent()220 void RichTextDelegate::RegisterWebEvent()
221 {
222     auto context = context_.Upgrade();
223     if (!context) {
224         return;
225     }
226     auto resRegister = context->GetPlatformResRegister();
227     resRegister->RegisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_START),
228         [weak = WeakClaim(this)](const std::string& param) {
229             auto delegate = weak.Upgrade();
230             if (delegate) {
231                 delegate->OnPageStarted(param);
232             }
233         });
234     resRegister->RegisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_FINISHED),
235         [weak = WeakClaim(this)](const std::string& param) {
236             auto delegate = weak.Upgrade();
237             if (delegate) {
238                 delegate->OnPageFinished(param);
239             }
240         });
241     resRegister->RegisterEvent(MakeEventHash(RICH_TEXT_EVENT_GOT_LAYOUT_PARAM),
242         [weak = WeakClaim(this)](const std::string& param) {
243             auto delegate = weak.Upgrade();
244             if (delegate) {
245                 delegate->OnGotLayoutParam(param);
246             }
247         });
248     resRegister->RegisterEvent(MakeEventHash(RICH_TEXT_EVENT_LOAD_ERROR),
249         [weak = WeakClaim(this)](const std::string& param) {
250             auto delegate = weak.Upgrade();
251             if (delegate) {
252                 delegate->OnPageError(param);
253             }
254         });
255 }
256 
257 // upper ui component which inherits from WebComponent
258 // could implement some curtain createdCallback to customized controller interface
259 // eg: web.loadurl.
AddCreatedCallback(const CreatedCallback & createdCallback)260 void RichTextDelegate::AddCreatedCallback(const CreatedCallback& createdCallback)
261 {
262     if (!createdCallback || state_ == State::RELEASED) {
263         return;
264     }
265     createdCallbacks_.emplace_back(createdCallback);
266 }
267 
AddWebLayoutChangeCallback(const UpdateWebViewLayoutCallback & layoutChangeCallback)268 void RichTextDelegate::AddWebLayoutChangeCallback(const UpdateWebViewLayoutCallback& layoutChangeCallback)
269 {
270     if (!layoutChangeCallback || state_ == State::RELEASED) {
271         return;
272     }
273     webviewLayoutCallback_ = layoutChangeCallback;
274 }
275 
RemoveCreatedCallback()276 void RichTextDelegate::RemoveCreatedCallback()
277 {
278     if (state_ == State::RELEASED) {
279         return;
280     }
281     createdCallbacks_.pop_back();
282 }
283 
AddReleasedCallback(const ReleasedCallback & releasedCallback)284 void RichTextDelegate::AddReleasedCallback(const ReleasedCallback& releasedCallback)
285 {
286     if (!releasedCallback || state_ == State::RELEASED) {
287         return;
288     }
289     releasedCallbacks_.emplace_back(releasedCallback);
290 }
291 
RemoveReleasedCallback()292 void RichTextDelegate::RemoveReleasedCallback()
293 {
294     if (state_ == State::RELEASED) {
295         return;
296     }
297     releasedCallbacks_.pop_back();
298 }
299 
UpdateRichTextData(const std::string & data)300 void RichTextDelegate::UpdateRichTextData(const std::string& data)
301 {
302     if (id_ == INVALID_ID) {
303         return;
304     }
305 
306     hash_ = MakeResourceHash();
307     Method updateContentMethod = MakeMethodHash(RICH_TEXT_METHOD_UPDATE_CONTENT);
308     std::stringstream paramStream;
309     paramStream << NTC_PARAM_CONTENT_DATA << RICHTEXT_PARAM_EQUALS << data;
310     std::string param = paramStream.str();
311     CallResRegisterMethod(updateContentMethod, param, nullptr);
312 }
313 
UpdateWebPostion(const int32_t top,const int32_t left)314 void RichTextDelegate::UpdateWebPostion(const int32_t top, const int32_t left)
315 {
316     hash_ = MakeResourceHash();
317 
318     Method updateLayoutPositionMethod = MakeMethodHash(RICH_TEXT_METHOD_UPDATE_TRANSLATE);
319     std::stringstream paramStream;
320     paramStream << NTC_PARAM_LEFT << RICHTEXT_PARAM_EQUALS << left << RICHTEXT_PARAM_AND
321     << NTC_PARAM_TOP << RICHTEXT_PARAM_EQUALS << top;
322     std::string param = paramStream.str();
323     CallResRegisterMethod(updateLayoutPositionMethod, param, nullptr);
324 }
325 
UpdateContentScroll(const int32_t x,const int32_t y)326 void RichTextDelegate::UpdateContentScroll(const int32_t x, const int32_t y)
327 {
328     hash_ = MakeResourceHash();
329 
330     Method updateLayoutPositionMethod = MakeMethodHash(RICH_TEXT_METHOD_UPDATE_CONTENT_TRANSLATE);
331     std::stringstream paramStream;
332     paramStream << NTC_PARAM_X << RICHTEXT_PARAM_EQUALS << x << RICHTEXT_PARAM_AND
333                 << NTC_PARAM_Y << RICHTEXT_PARAM_EQUALS << y;
334     std::string param = paramStream.str();
335     CallResRegisterMethod(updateLayoutPositionMethod, param, nullptr);
336 }
337 
CallPopPageSuccessPageUrl(const std::string & url,const int32_t pageId)338 void RichTextDelegate::CallPopPageSuccessPageUrl(const std::string& url, const int32_t pageId)
339 {
340     if (url == pageUrl_ && pageId == pageId_) {
341         hash_ = MakeResourceHash();
342         Method reShowRichTextMethod = MakeMethodHash(RICH_TEXT_METHOD_SHOW_RICHTEXT);
343         std::stringstream paramStream;
344         paramStream << NTC_PARAM_PAGE_PATH << RICHTEXT_PARAM_EQUALS << url;
345         std::string param = paramStream.str();
346         CallResRegisterMethod(reShowRichTextMethod, param, nullptr);
347     }
348 }
349 
CallIsPagePathInvalid(const bool & isPageInvalid)350 void RichTextDelegate::CallIsPagePathInvalid(const bool& isPageInvalid)
351 {
352     hash_ = MakeResourceHash();
353     Method hideRichTextMethod = MakeMethodHash(RICH_TEXT_METHOD_HIDE_RICHTEXT_WHEN_PUSH);
354     std::stringstream paramStream;
355     paramStream << RICHTEXT_PARAM_NONE;
356     std::string param = paramStream.str();
357     CallResRegisterMethod(hideRichTextMethod, param, nullptr);
358 }
359 
HideRichText()360 void RichTextDelegate::HideRichText()
361 {
362     std::string pageUrl;
363     int32_t pageId;
364     OHOS::Ace::Framework::DelegateClient::GetInstance().GetWebPageUrl(pageUrl, pageId);
365 
366     if (pageUrl != pageUrl_ || pageId != pageId_) {
367         hash_ = MakeResourceHash();
368         Method hideRichTextMethod = MakeMethodHash(RICH_TEXT_METHOD_HIDE_RICHTEXT_WHEN_POP);
369         std::stringstream paramStream;
370         paramStream << RICHTEXT_PARAM_NONE;
371         std::string param = paramStream.str();
372         CallResRegisterMethod(hideRichTextMethod, param, nullptr);
373     }
374 }
375 
ChangeRichTextVisibility(const bool visible)376 void RichTextDelegate::ChangeRichTextVisibility(const bool visible)
377 {
378     hash_ = MakeResourceHash();
379     Method hideRichTextMethod = MakeMethodHash(RICH_TEXT_METHOD_CHANGE_VISIBILITY);
380     std::string richTextVisible = visible ? "true" : "false";
381     std::stringstream paramStream;
382     paramStream << NTC_PARAM_RICHTEXT_VISIBILITY << RICHTEXT_PARAM_EQUALS << richTextVisible;
383     std::string param = paramStream.str();
384     CallResRegisterMethod(hideRichTextMethod, param, nullptr);
385 }
OnPageStarted(const std::string & param)386 void RichTextDelegate::OnPageStarted(const std::string& param)
387 {
388     if (onPageStarted_) {
389         std::string param = std::string(R"("start", null, null)");
390         onPageStarted_(param);
391     }
392 }
393 
OnPageFinished(const std::string & param)394 void RichTextDelegate::OnPageFinished(const std::string& param)
395 {
396     if (onPageFinished_) {
397         std::string param = std::string(R"("complete", null, null)");
398         onPageFinished_(param);
399     }
400 }
401 
OnGotLayoutParam(const std::string & param)402 void RichTextDelegate::OnGotLayoutParam(const std::string& param)
403 {
404     int32_t layoutHeight = GetIntParam(param, NTC_PARAM_LAYOUT_HEIGHT);
405     int32_t contentHeight = GetIntParam(param, NTC_PARAM_CONTENT_HEIGHT);
406     int32_t layoutWidth = GetIntParam(param, NTC_PARAM_LAYOUT_WIDTH);
407     if (webviewLayoutCallback_) {
408         webviewLayoutCallback_(layoutWidth, layoutHeight, contentHeight);
409     }
410 }
411 
OnPageError(const std::string & param)412 void RichTextDelegate::OnPageError(const std::string& param)
413 {
414     if (onPageError_) {
415         int32_t errorCode = GetIntParam(param, NTC_PARAM_ERROR_CODE);
416         std::string url = GetUrlStringParam(param, NTC_PARAM_URL);
417         std::string description = GetStringParam(param, NTC_PARAM_DESCRIPTION);
418 
419         std::string paramUrl = std::string(R"(")").append(url)
420                                                   .append(std::string(R"(")"))
421                                                   .append(",");
422 
423         std::string paramErrorCode = std::string(R"(")").append(NTC_PARAM_ERROR_CODE)
424                                                         .append(std::string(R"(")"))
425                                                         .append(":")
426                                                         .append(std::to_string(errorCode))
427                                                         .append(",");
428 
429         std::string paramDesc = std::string(R"(")").append(NTC_PARAM_DESCRIPTION)
430                                                    .append(std::string(R"(")"))
431                                                    .append(":")
432                                                    .append(std::string(R"(")")
433                                                    .append(description)
434                                                    .append(std::string(R"(")")));
435         std::string param = std::string(R"("error",{"url":)")
436                                            .append((paramUrl + paramErrorCode + paramDesc)
437                                            .append("},null"));
438         onPageError_(param);
439     }
440 }
441 
GetUrlStringParam(const std::string & param,const std::string & name) const442 std::string RichTextDelegate::GetUrlStringParam(const std::string& param, const std::string& name) const
443 {
444     size_t len = name.length();
445     size_t posErrorCode = param.find(NTC_PARAM_ERROR_CODE);
446     size_t pos = param.find(name);
447     std::string result;
448 
449     if (pos != std::string::npos && posErrorCode != std::string::npos) {
450         std::stringstream ss;
451 
452         ss << param.substr(pos + 1 + len, posErrorCode - 5);
453         ss >> result;
454     }
455     return result;
456 }
457 
BindPopPageSuccessMethod()458 void RichTextDelegate::BindPopPageSuccessMethod()
459 {
460     auto context = context_.Upgrade();
461     if (context) {
462         context->SetPopPageSuccessEventHandler(
463             [weak = WeakClaim(this)](const std::string& pageUrl, const int32_t pageId) {
464                 std::string url = pageUrl.substr(0, pageUrl.length() - 3);
465                 auto delegate = weak.Upgrade();
466                 if (delegate) {
467                     delegate->CallPopPageSuccessPageUrl(url, pageId);
468                 }
469         });
470     }
471 }
472 
BindIsPagePathInvalidMethod()473 void RichTextDelegate::BindIsPagePathInvalidMethod()
474 {
475     auto context = context_.Upgrade();
476     if (context) {
477         context->SetIsPagePathInvalidEventHandler([weak = WeakClaim(this)](bool& isPageInvalid) {
478             auto delegate = weak.Upgrade();
479             if (delegate) {
480                 delegate->CallIsPagePathInvalid(isPageInvalid);
481             }
482         });
483     }
484 }
485 
486 } // namespace OHOS::Ace
487