Commit 3e6d7e14 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

[WebLayer] Load WebLayerImpl from another APK

With this change, WebLayerShell now only depends on the weblayer/public
dir, and has no other dependencies on chrome code. A new APK
WebLayerSupport has all the implementation and must be installed
alongside WebLayerShell for it to work.

Change-Id: Iab3b346831c8d8737a0b3ca509455b7499b31ac9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1779428Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#693775}
parent eafc9468
...@@ -23,8 +23,8 @@ JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -23,8 +23,8 @@ JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return -1; return -1;
weblayer::MainParams params; weblayer::MainParams params;
params.delegate = new weblayer::MainDelegateImpl; params.delegate = new weblayer::MainDelegateImpl;
params.pak_name = "weblayer_shell.pak"; params.pak_name = "weblayer_support.pak";
params.brand = "weblayer_shell"; params.brand = "WebLayer";
content::SetContentMainDelegate( content::SetContentMainDelegate(
new weblayer::ContentMainDelegateImpl(params)); new weblayer::ContentMainDelegateImpl(params));
......
...@@ -4,23 +4,37 @@ ...@@ -4,23 +4,37 @@
package org.chromium.weblayer_private; package org.chromium.weblayer_private;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.IBinder; import android.os.IBinder;
import android.util.AndroidRuntimeException;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType; import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.library_loader.ProcessInitException; import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.content_public.browser.BrowserStartupController; import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.ChildProcessCreationParams;
import org.chromium.content_public.browser.DeviceUtils; import org.chromium.content_public.browser.DeviceUtils;
import org.chromium.ui.base.ResourceBundle;
import org.chromium.weblayer_private.aidl.IProfile; import org.chromium.weblayer_private.aidl.IProfile;
import org.chromium.weblayer_private.aidl.IWebLayer; import org.chromium.weblayer_private.aidl.IWebLayer;
public final class WebLayerImpl extends IWebLayer.Stub { public final class WebLayerImpl extends IWebLayer.Stub {
// TODO: should there be one tag for all this code? // TODO: should there be one tag for all this code?
private static final String TAG = "WebLayer"; private static final String TAG = "WebLayer";
private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "weblayer";
// TODO: Configure this from the client.
private static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-command-line";
public static IBinder create() { public static IBinder create(Application application, Context implContext) {
return new WebLayerImpl(); return new WebLayerImpl(application, implContext);
} }
@Override @Override
...@@ -28,17 +42,33 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -28,17 +42,33 @@ public final class WebLayerImpl extends IWebLayer.Stub {
return new ProfileImpl(path); return new ProfileImpl(path);
} }
public WebLayerImpl() { private WebLayerImpl(Application application, Context implContext) {
ContextUtils.initApplicationContext(new ContextWrapper(application) {
@Override
public Resources getResources() {
// Always use resources from the implementation APK.
return implContext.getResources();
}
});
ResourceBundle.setNoAvailableLocalePaks();
PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
ApplicationStatus.initialize(application);
ChildProcessCreationParams.set(implContext.getPackageName(), true /* isExternalService */,
LibraryProcessType.PROCESS_CHILD, true /* bindToCaller */,
false /* ignoreVisibilityForImportance */);
if (!CommandLine.isInitialized()) {
CommandLine.initFromFile(COMMAND_LINE_FILE);
}
DeviceUtils.addDeviceSpecificUserAgentSwitch(); DeviceUtils.addDeviceSpecificUserAgentSwitch();
try { try {
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
} catch (ProcessInitException e) { } catch (ProcessInitException e) {
Log.e(TAG, "ContentView initialization failed.", e); Log.e(TAG, "ContentView initialization failed.", e);
// Since the library failed to initialize nothing in the application throw new AndroidRuntimeException(e);
// can work, so kill the whole application not just the activity
System.exit(-1);
return;
} }
try { try {
...@@ -47,7 +77,7 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -47,7 +77,7 @@ public final class WebLayerImpl extends IWebLayer.Stub {
/* singleProcess*/ false); /* singleProcess*/ false);
} catch (ProcessInitException e) { } catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e); Log.e(TAG, "Unable to load native library.", e);
System.exit(-1); throw new AndroidRuntimeException(e);
} }
} }
} }
...@@ -19,4 +19,8 @@ android_library("java") { ...@@ -19,4 +19,8 @@ android_library("java") {
deps = [ deps = [
"//weblayer/browser/java:client_java", "//weblayer/browser/java:client_java",
] ]
# Needed for android.webkit.WebViewDelegate.
alternative_android_sdk_dep =
"//third_party/android_sdk:public_framework_system_java"
} }
...@@ -4,20 +4,29 @@ ...@@ -4,20 +4,29 @@
package org.chromium.weblayer; package org.chromium.weblayer;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.AndroidRuntimeException; import android.util.AndroidRuntimeException;
import android.util.Log; import android.util.Log;
import android.webkit.WebViewDelegate;
import android.webkit.WebViewFactory;
import org.chromium.weblayer_private.aidl.IWebLayer; import org.chromium.weblayer_private.aidl.IWebLayer;
import java.io.File; import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/** /**
* WebLayer is responsible for initializing state necessary to use* any of the classes in web layer. * WebLayer is responsible for initializing state necessary to use* any of the classes in web layer.
*/ */
public final class WebLayer { public final class WebLayer {
private static final String TAG = "WebLayer"; private static final String TAG = "WebLayer";
private static final String PACKAGE_NAME = "org.chromium.weblayer.support";
private static WebLayer sInstance; private static WebLayer sInstance;
private IWebLayer mImpl; private IWebLayer mImpl;
...@@ -29,12 +38,33 @@ public final class WebLayer { ...@@ -29,12 +38,33 @@ public final class WebLayer {
return sInstance; return sInstance;
} }
WebLayer() { WebLayer() {}
public void init(Application application) {
try { try {
// TODO: Make asset loading work on L, where WebViewDelegate doesn't exist.
// WebViewDelegate.addWebViewAssetPath() accesses the currently loaded package info from
// WebViewFactory, so we have to fake it.
PackageInfo implPackageInfo = application.getPackageManager().getPackageInfo(
PACKAGE_NAME, PackageManager.GET_META_DATA);
Field packageInfo = WebViewFactory.class.getDeclaredField("sPackageInfo");
packageInfo.setAccessible(true);
packageInfo.set(null, implPackageInfo);
// TODO(torne): Figure out how to load assets for production.
// Load assets using the WebViewDelegate.
Constructor constructor = WebViewDelegate.class.getDeclaredConstructor();
constructor.setAccessible(true);
WebViewDelegate delegate = (WebViewDelegate) constructor.newInstance();
delegate.addWebViewAssetPath(application);
Context remoteContext = application.createPackageContext(
PACKAGE_NAME, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
mImpl = IWebLayer.Stub.asInterface( mImpl = IWebLayer.Stub.asInterface(
(IBinder) Class.forName("org.chromium.weblayer_private.WebLayerImpl") (IBinder) remoteContext.getClassLoader()
.getMethod("create") .loadClass("org.chromium.weblayer_private.WebLayerImpl")
.invoke(null)); .getMethod("create", Application.class, Context.class)
.invoke(null, application, remoteContext));
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Failed to get WebLayerImpl.", e); Log.e(TAG, "Failed to get WebLayerImpl.", e);
throw new AndroidRuntimeException(e); throw new AndroidRuntimeException(e);
...@@ -43,7 +73,7 @@ public final class WebLayer { ...@@ -43,7 +73,7 @@ public final class WebLayer {
@Override @Override
protected void finalize() { protected void finalize() {
// TODO(sky): figure out right assertion here if mProfile is non-null. // TODO(sky): figure out right assertion here if mImpl is non-null.
} }
public void destroy() { public void destroy() {
......
...@@ -144,7 +144,20 @@ group("resources") { ...@@ -144,7 +144,20 @@ group("resources") {
] ]
} }
repack("pak") { repack("shell_pak") {
testonly = true
sources = [
"$root_gen_dir/weblayer/shell/weblayer_shell_resources.pak",
]
deps = [
":resources",
]
output = "$root_out_dir/weblayer_shell.pak"
}
repack("support_pak") {
testonly = true testonly = true
sources = [ sources = [
...@@ -160,11 +173,9 @@ repack("pak") { ...@@ -160,11 +173,9 @@ repack("pak") {
"$root_gen_dir/ui/resources/webui_resources.pak", "$root_gen_dir/ui/resources/webui_resources.pak",
"$root_gen_dir/ui/strings/app_locale_settings_en-US.pak", "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
"$root_gen_dir/ui/strings/ui_strings_en-US.pak", "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
"$root_gen_dir/weblayer/shell/weblayer_shell_resources.pak",
] ]
deps = [ deps = [
":resources",
"//content:resources", "//content:resources",
"//content/app/resources", "//content/app/resources",
"//content/app/strings", "//content/app/strings",
...@@ -182,7 +193,22 @@ repack("pak") { ...@@ -182,7 +193,22 @@ repack("pak") {
sources += sources +=
[ "$root_gen_dir/ui/views/resources/views_resources_100_percent.pak" ] [ "$root_gen_dir/ui/views/resources/views_resources_100_percent.pak" ]
} }
output = "$root_out_dir/weblayer_shell.pak" output = "$root_out_dir/weblayer_support.pak"
}
repack("pak") {
testonly = true
sources = [
"$root_out_dir/weblayer_shell.pak",
"$root_out_dir/weblayer_support.pak",
]
deps = [
":shell_pak",
":support_pak",
]
output = "$root_out_dir/weblayer.pak"
} }
if (is_android) { if (is_android) {
...@@ -192,6 +218,12 @@ if (is_android) { ...@@ -192,6 +218,12 @@ if (is_android) {
"//weblayer/shell/android:weblayer_shell_apk", "//weblayer/shell/android:weblayer_shell_apk",
] ]
} }
group("weblayer_support") {
testonly = true
deps = [
"//weblayer/shell/android:weblayer_support_apk",
]
}
} else { } else {
executable("weblayer_shell") { executable("weblayer_shell") {
testonly = true testonly = true
......
...@@ -11,7 +11,7 @@ weblayer_shell_manifest = ...@@ -11,7 +11,7 @@ weblayer_shell_manifest =
"$target_gen_dir/weblayer_shell_manifest/AndroidManifest.xml" "$target_gen_dir/weblayer_shell_manifest/AndroidManifest.xml"
jinja_template("weblayer_shell_manifest") { jinja_template("weblayer_shell_manifest") {
input = "AndroidManifest.xml" input = "shell_apk/AndroidManifest.xml"
output = weblayer_shell_manifest output = weblayer_shell_manifest
} }
...@@ -23,14 +23,8 @@ android_assets("weblayer_shell_assets") { ...@@ -23,14 +23,8 @@ android_assets("weblayer_shell_assets") {
] ]
disable_compression = true disable_compression = true
deps = [ deps = [
"//third_party/icu:icu_assets", "//weblayer/shell:shell_pak",
"//weblayer/shell:pak",
] ]
if (use_v8_context_snapshot) {
deps += [ "//tools/v8_context_snapshot:v8_context_snapshot_assets" ]
} else {
deps += [ "//v8:v8_external_startup_data_assets" ]
}
} }
android_library("weblayer_shell_java") { android_library("weblayer_shell_java") {
...@@ -38,24 +32,11 @@ android_library("weblayer_shell_java") { ...@@ -38,24 +32,11 @@ android_library("weblayer_shell_java") {
deps = [ deps = [
":weblayer_shell_manifest", ":weblayer_shell_manifest",
"//base:base_java",
"//ui/android:ui_java",
"//weblayer/public/java",
]
# Transitive dependencies
deps += [
"//components/viz/service:service_java",
"//media/base/android:media_java",
"//media/capture/video/android:capture_java",
"//mojo/public/java:system_java",
"//net/android:net_java",
"//third_party/android_deps:android_support_v4_java", "//third_party/android_deps:android_support_v4_java",
"//weblayer/public/java",
] ]
java_files = [ java_files =
"src/org/chromium/weblayer/shell/WebLayerShellActivity.java", [ "shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java" ]
"src/org/chromium/weblayer/shell/WebLayerShellApplication.java",
]
android_manifest_for_lint = weblayer_shell_manifest android_manifest_for_lint = weblayer_shell_manifest
} }
...@@ -67,13 +48,62 @@ android_apk("weblayer_shell_apk") { ...@@ -67,13 +48,62 @@ android_apk("weblayer_shell_apk") {
":weblayer_shell_assets", ":weblayer_shell_assets",
":weblayer_shell_java", ":weblayer_shell_java",
":weblayer_shell_manifest", ":weblayer_shell_manifest",
"//base:base_java",
"//weblayer/browser/java",
] ]
apk_name = "WebLayerShell" apk_name = "WebLayerShell"
android_manifest = weblayer_shell_manifest android_manifest = weblayer_shell_manifest
min_sdk_version = 21 min_sdk_version = 21
target_sdk_version = 28 target_sdk_version = 28
android_manifest_dep = ":weblayer_shell_manifest" android_manifest_dep = ":weblayer_shell_manifest"
}
weblayer_support_manifest =
"$target_gen_dir/weblayer_support_manifest/AndroidManifest.xml"
jinja_template("weblayer_support_manifest") {
input = "support_apk/AndroidManifest.xml"
output = weblayer_support_manifest
}
android_assets("weblayer_support_assets") {
testonly = true
sources = [
"$root_out_dir/weblayer_support.pak",
]
disable_compression = true
deps = [
"//third_party/icu:icu_assets",
"//weblayer/shell:support_pak",
]
if (use_v8_context_snapshot) {
deps += [ "//tools/v8_context_snapshot:v8_context_snapshot_assets" ]
} else {
deps += [ "//v8:v8_external_startup_data_assets" ]
}
}
android_apk("weblayer_support_apk") {
testonly = true
deps = [
":weblayer_support_assets",
":weblayer_support_manifest",
"//base:base_java",
"//weblayer/browser/java",
]
# Transitive dependencies
deps += [
"//components/viz/service:service_java",
"//media/base/android:media_java",
"//media/capture/video/android:capture_java",
"//mojo/public/java:system_java",
"//net/android:net_java",
]
apk_name = "WebLayerSupport"
android_manifest = weblayer_support_manifest
min_sdk_version = 21
target_sdk_version = 28
android_manifest_dep = ":weblayer_support_manifest"
shared_libraries = [ "//weblayer:libweblayer" ] shared_libraries = [ "//weblayer:libweblayer" ]
} }
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.weblayer.shell">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:label="WebLayer shell">
<activity android:name="WebLayerShellActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
noparent = True
include_rules = [
"+weblayer/public/java",
]
...@@ -20,7 +20,6 @@ import android.widget.LinearLayout; ...@@ -20,7 +20,6 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import org.chromium.base.CommandLine;
import org.chromium.weblayer.BrowserController; import org.chromium.weblayer.BrowserController;
import org.chromium.weblayer.BrowserObserver; import org.chromium.weblayer.BrowserObserver;
import org.chromium.weblayer.Profile; import org.chromium.weblayer.Profile;
...@@ -54,13 +53,10 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -54,13 +53,10 @@ public class WebLayerShellActivity extends FragmentActivity {
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Only call init for main process.
WebLayer.getInstance().init(getApplication());
// TODO: move this to WebLayer. super.onCreate(savedInstanceState);
// Initializing the command line must occur before loading the library.
if (!CommandLine.isInitialized()) {
((WebLayerShellApplication) getApplication()).initCommandLine();
}
LinearLayout mainView = new LinearLayout(this); LinearLayout mainView = new LinearLayout(this);
int viewId = View.generateViewId(); int viewId = View.generateViewId();
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer.shell;
import android.app.Application;
import android.content.Context;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BuildConfig;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.PathUtils;
import org.chromium.base.multidex.ChromiumMultiDexInstaller;
import org.chromium.ui.base.ResourceBundle;
/**
* Entry point for the demo shell application.
*/
public class WebLayerShellApplication extends Application {
public static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-shell-command-line";
private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "weblayer_shell";
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
boolean isBrowserProcess = !ContextUtils.getProcessName().contains(":");
ContextUtils.initApplicationContext(this);
ResourceBundle.setNoAvailableLocalePaks();
if (isBrowserProcess) {
if (BuildConfig.IS_MULTIDEX_ENABLED) {
ChromiumMultiDexInstaller.install(this);
}
PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
}
ApplicationStatus.initialize(this);
}
public void initCommandLine() {
if (!CommandLine.isInitialized()) {
CommandLine.initFromFile(COMMAND_LINE_FILE);
}
}
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.weblayer.shell"> package="org.chromium.weblayer.support">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
...@@ -19,18 +19,7 @@ ...@@ -19,18 +19,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:name="WebLayerShellApplication" <application android:label="WebLayer support">
android:label="WebLayer shell">
<activity android:name="WebLayerShellActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- The following service entries exist in order to allow us to <!-- The following service entries exist in order to allow us to
start more than one sandboxed process. --> start more than one sandboxed process. -->
...@@ -43,7 +32,8 @@ ...@@ -43,7 +32,8 @@
<service android:name="org.chromium.content.app.SandboxedProcessService{{ i }}" <service android:name="org.chromium.content.app.SandboxedProcessService{{ i }}"
android:process=":sandboxed_process{{ i }}" android:process=":sandboxed_process{{ i }}"
android:isolatedProcess="true" android:isolatedProcess="true"
android:exported="false" /> android:externalService="true"
android:exported="true" />
{% endfor %} {% endfor %}
{% set num_privileged_services = 5 %} {% set num_privileged_services = 5 %}
...@@ -53,7 +43,7 @@ ...@@ -53,7 +43,7 @@
<service android:name="org.chromium.content.app.PrivilegedProcessService{{ i }}" <service android:name="org.chromium.content.app.PrivilegedProcessService{{ i }}"
android:process=":privileged_process{{ i }}" android:process=":privileged_process{{ i }}"
android:isolatedProcess="false" android:isolatedProcess="false"
android:exported="false" /> android:exported="true" />
{% endfor %} {% endfor %}
</application> </application>
</manifest> </manifest>
...@@ -96,7 +96,7 @@ weblayer::MainParams CreateMainParams() { ...@@ -96,7 +96,7 @@ weblayer::MainParams CreateMainParams() {
base::PathService::Get(base::DIR_EXE, &params.log_filename); base::PathService::Get(base::DIR_EXE, &params.log_filename);
params.log_filename = params.log_filename.AppendASCII("weblayer_shell.log"); params.log_filename = params.log_filename.AppendASCII("weblayer_shell.log");
params.pak_name = "weblayer_shell.pak"; params.pak_name = "weblayer.pak";
params.brand = "weblayer_shell"; params.brand = "weblayer_shell";
params.full_version = WEBLAYER_SHELL_VERSION; params.full_version = WEBLAYER_SHELL_VERSION;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment