1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20/**
21 * @fileOverview
22 * page controls from native
23 *
24 * - init bundle
25 *
26 * corresponded with the API of page manager (framework.js)
27 */
28
29import {
30  Log
31} from '../../../utils/index';
32import { removePrefix } from '../../util/index';
33import {
34  defineFn,
35  bootstrap
36} from './bundle';
37import { updateActions } from '../api/misc';
38import { getPageGlobal } from '../../app/helper';
39import { Image } from '../Image';
40import { OffscreenCanvas } from '../OffscreenCanvas';
41import Page from '../index';
42import { Services } from '../../app/index';
43import { requireModule } from '../register';
44import { App } from '../../app/App';
45
46interface ParseOptions {
47  $app_define$(...args: any[]): void; // eslint-disable-line camelcase
48  $app_bootstrap$(name: string): void; // eslint-disable-line camelcase
49  $app_require$(name: string): void; // eslint-disable-line camelcase
50  Image(): void;
51  OffscreenCanvas(width, height): void;
52}
53
54/**
55 * Init a page by run code with data.
56 * @param {Page} page
57 * @param {string} code
58 * @param {Object} data
59 * @param {Services} services
60 * @return {*}
61 */
62export function init(page: Page, code: string | Function, data: object, services: Services): any {
63  Log.debug('Intialize a page with:\n', data);
64  let result;
65
66  // Methods to parse code.
67  const pageDefine = (...args) => defineFn(page, ...args);
68  const pageBoot = (name) => {
69    result = bootstrap(page, name, data);
70    updateActions(page);
71    page.doc.taskCenter.send('dom', { action: 'createFinish' }, []);
72    Log.debug(`After initialized a page(${page.id}).`);
73  };
74
75  const packageName = page.packageName;
76  const appFunction = () => {
77    if (page && page.doc) {
78      return page;
79    }
80    // card not has packageName
81    if (packageName === 'notset') {
82      return page;
83    }
84    const instance = App.pageMap.getTop(packageName);
85    return instance || page;
86  };
87
88  const pageRequireModule = name => requireModule(appFunction, removePrefix(name));
89
90  const imageObj: () => Image = function() {
91    return new Image(page);
92  };
93  const offscreenCanvasObj: (width, height) => OffscreenCanvas = function(width, height) {
94    return new OffscreenCanvas(page, width, height);
95  };
96  const options: ParseOptions = {
97    $app_define$: pageDefine,
98    $app_bootstrap$: pageBoot,
99    $app_require$: pageRequireModule,
100    Image: imageObj,
101    OffscreenCanvas: offscreenCanvasObj
102  };
103
104  // Support page global and init language.
105  global.__appProto__ = getPageGlobal(page.packageName);
106  global.language = page.options.language;
107  global.$app_define$ = pageDefine;
108  global.$app_require$ = pageRequireModule;
109  global.Image = imageObj;
110  global.OffscreenCanvas = offscreenCanvasObj;
111
112  let functionCode: string;
113  if (typeof code !== 'function') {
114    functionCode = `(function(global){\n\n"use strict";\n\n ${code} \n\n})(this.__appProto__)`;
115  }
116
117  // Compile js bundle code and get result.
118  if (typeof code === 'function') {
119    code.call(global, options);
120  } else {
121    compileBundle(functionCode, page.doc.url, options, services);
122  }
123  return result;
124}
125
126/**
127 * Run bundle code by a new function.
128 * @param {string} functionCode - Js bundle code.
129 * @param {Object[]} args - Global methods for compile js bundle code.
130 * @return {*}
131 */
132export function compileBundle(functionCode: string, file: string, ...args: object[]): any {
133  const funcKeys: string[] = [];
134  const funcValues: Function[] = [];
135  args.forEach((module) => {
136    for (const key in module) {
137      funcKeys.push(key);
138      funcValues.push(module[key]);
139    }
140  });
141
142  // If failed to run code on native, then run code on framework.
143  if (!compileBundleNative(funcKeys, funcValues, functionCode, file)) {
144    Log.error(`Compile js bundle failed, typeof code is not 'function'`)
145    return;
146  }
147}
148
149/**
150 * Call a new function generated on the V8 native side.
151 * @param {string[]} funcKeys
152 * @param {Function[]} funcValues
153 * @param {string} functionCode
154 * @return {boolean} Return true if no error occurred.
155 */
156function compileBundleNative(funcKeys: string[], funcValues: Function[], functionCode: string, file: string): boolean {
157  if (typeof compileAndRunBundle !== 'function') {
158    return false;
159  }
160
161  let isSuccess: boolean = false;
162  const bundle: string = `(function (${funcKeys.toString()}) {${functionCode}})`;
163  try {
164    const compileFunction: Function = compileAndRunBundle(bundle, file);
165    if (compileFunction && typeof compileFunction === 'function') {
166      compileFunction(...funcValues);
167      isSuccess = true;
168    }
169  } catch (e) {
170    Log.error(e);
171  }
172  return isSuccess;
173}
174