Commit a11936c1 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

updated ConsoleMessage class, updated evaluateJavascript method, fixed Android...

updated ConsoleMessage class, updated evaluateJavascript method, fixed Android JavaScriptBridgeInterface _callHandler method
parent e2bd3076
This diff is collapsed.
......@@ -36,6 +36,7 @@
### BREAKING CHANGES
- Deleted `WebResourceRequest` class
- Updated `WebResourceResponse` class
- Updated `ConsoleMessage` class
- Updated `ConsoleMessageLevel` class
- Updated `onLoadResource` event
- Updated `CookieManager` class
......
......@@ -439,8 +439,6 @@ public class InAppWebChromeClient extends WebChromeClient implements PluginRegis
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("sourceURL", consoleMessage.sourceId());
obj.put("lineNumber", consoleMessage.lineNumber());
obj.put("message", consoleMessage.message());
obj.put("messageLevel", consoleMessage.messageLevel().ordinal());
getChannel().invokeMethod("onConsoleMessage", obj);
......
......@@ -1097,15 +1097,13 @@ final public class InAppWebView extends InputAwareWebView {
}
public void injectDeferredObject(String source, String jsWrapper, final MethodChannel.Result result) {
String scriptToInject;
String scriptToInject = source;
if (jsWrapper != null) {
org.json.JSONArray jsonEsc = new org.json.JSONArray();
jsonEsc.put(source);
String jsonRepr = jsonEsc.toString();
String jsonSourceString = jsonRepr.substring(1, jsonRepr.length() - 1);
scriptToInject = String.format(jsWrapper, jsonSourceString);
} else {
scriptToInject = source;
}
final String finalScriptToInject = scriptToInject;
( (inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity ).runOnUiThread(new Runnable() {
......@@ -1120,37 +1118,7 @@ final public class InAppWebView extends InputAwareWebView {
public void onReceiveValue(String s) {
if (result == null)
return;
JsonReader reader = new JsonReader(new StringReader(s));
// Must set lenient to parse single values
reader.setLenient(true);
try {
String msg;
if (reader.peek() == JsonToken.STRING) {
msg = reader.nextString();
JsonReader reader2 = new JsonReader(new StringReader(msg));
reader2.setLenient(true);
if (reader2.peek() == JsonToken.STRING)
msg = reader2.nextString();
result.success(msg);
} else {
result.success("");
}
} catch (IOException e) {
Log.e(LOG_TAG, "IOException", e);
} finally {
try {
reader.close();
} catch (IOException e) {
// NOOP
}
}
result.success(s);
}
});
}
......@@ -1159,8 +1127,7 @@ final public class InAppWebView extends InputAwareWebView {
}
public void evaluateJavascript(String source, MethodChannel.Result result) {
String jsWrapper = "(function(){return JSON.stringify(eval(%s));})();";
injectDeferredObject(source, jsWrapper, result);
injectDeferredObject(source, null, result);
}
public void injectJavascriptFileFromUrl(String urlFile) {
......
......@@ -5,6 +5,7 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView;
......@@ -61,7 +62,7 @@ public class JavaScriptBridgeInterface {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];", (MethodChannel.Result) null);
webView.evaluateJavascript("window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];", (ValueCallback<String>) null);
}
else {
webView.loadUrl("javascript:window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];");
......
......@@ -93,7 +93,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
debuggingEnabled: true,
//clearCache: true,
clearCache: true,
useShouldOverrideUrlLoading: true,
useOnTargetBlank: true,
useOnLoadResource: true,
......@@ -196,10 +196,8 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
print("""
console output:
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel.toValue()}
messageLevel: ${consoleMessage.messageLevel.toString()}
""");
},
onDownloadStart: (InAppWebViewController controller, String url) async {
......
......@@ -60,8 +60,6 @@ class MyInAppBrowser extends InAppBrowser {
void onConsoleMessage(ConsoleMessage consoleMessage) {
print("""
console output:
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel.toValue()}
""");
......
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>InAppWebViewOnConsoleMessageTest</title>
</head>
<body>
<h1 class="cover-heading">InAppWebViewOnConsoleMessageTest</h1>
<script>
console.log("message");
</script>
</body>
</html>
\ No newline at end of file
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>InAppWebViewOnDownloadStartTest</title>
</head>
<body>
<h1>InAppWebViewOnDownloadStartTest</h1>
<a id="download-file" href="http://192.168.1.20:8082/test-download-file">download file</a>
<script>
window.addEventListener("flutterInAppBrowserPlatformReady", function(event) {
document.querySelector("#download-file").click();
});
</script>
</body>
</html>
\ No newline at end of file
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>InAppWebViewOnTargetBlankTest</title>
</head>
<body>
<h1>InAppWebViewOnTargetBlankTest</h1>
<a id="target-blank" href="https://flutter.dev/">target blank</a>
<script>
window.addEventListener("flutterInAppBrowserPlatformReady", function(event) {
document.querySelector("#target-blank").click();
});
</script>
</body>
</html>
\ No newline at end of file
......@@ -118,5 +118,53 @@ void main() {
expect(true, title.contains("Lorenzo Pichilli") && title.contains("200"));
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewShouldOverrideUrlLoadingTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewShouldOverrideUrlLoadingTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String url = await driver.getText(appBarTitle);
expect(url, "https://flutter.dev/");
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewOnConsoleMessageTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewOnConsoleMessageTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String title = await driver.getText(appBarTitle);
expect(title, "message LOG");
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewOnDownloadStartTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewOnDownloadStartTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String url = await driver.getText(appBarTitle);
expect(url, "http://192.168.1.20:8082/test-download-file");
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewOnTargetBlankTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewOnTargetBlankTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String url = await driver.getText(appBarTitle);
expect(url, "https://flutter.dev/");
}, timeout: new Timeout(new Duration(minutes: 5)));
});
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'main_test.dart';
import 'util_test.dart';
import 'custom_widget_test.dart';
class InAppWebViewOnConsoleMessageTest extends WidgetTest {
final InAppWebViewOnConsoleMessageTestState state = InAppWebViewOnConsoleMessageTestState();
@override
InAppWebViewOnConsoleMessageTestState createState() => state;
}
class InAppWebViewOnConsoleMessageTestState extends WidgetTestState {
String appBarTitle = "InAppWebViewOnConsoleMessageTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_console_message_test.html",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
setState(() {
appBarTitle = "true";
});
nextTest(context: context, state: this);
},
onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
setState(() {
appBarTitle = consoleMessage.message + " " + consoleMessage.messageLevel.toString();
});
nextTest(context: context, state: this);
},
),
),
),
])
)
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'main_test.dart';
import 'util_test.dart';
import 'custom_widget_test.dart';
class InAppWebViewOnDownloadStartTest extends WidgetTest {
final InAppWebViewOnDownloadStartTestState state = InAppWebViewOnDownloadStartTestState();
@override
InAppWebViewOnDownloadStartTestState createState() => state;
}
class InAppWebViewOnDownloadStartTestState extends WidgetTestState {
String appBarTitle = "InAppWebViewOnDownloadStartTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_downlaod_start_test.html",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true,
useOnDownloadStart: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
},
onDownloadStart: (InAppWebViewController controller, String url) {
setState(() {
appBarTitle = url;
});
nextTest(context: context, state: this);
},
),
),
),
])
)
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'main_test.dart';
import 'util_test.dart';
import 'custom_widget_test.dart';
class InAppWebViewOnTargetBlankTest extends WidgetTest {
final InAppWebViewOnTargetBlankTestState state = InAppWebViewOnTargetBlankTestState();
@override
InAppWebViewOnTargetBlankTestState createState() => state;
}
class InAppWebViewOnTargetBlankTestState extends WidgetTestState {
String appBarTitle = "InAppWebViewOnTargetBlankTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialFile: "test_assets/in_app_webview_on_target_blank_test.html",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true,
useOnTargetBlank: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
},
onTargetBlank: (InAppWebViewController controller, String url) {
setState(() {
appBarTitle = url;
});
nextTest(context: context, state: this);
},
),
),
),
])
)
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'custom_widget_test.dart';
import 'main_test.dart';
import 'util_test.dart';
class InAppWebViewShouldOverrideUrlLoadingTest extends WidgetTest {
final InAppWebViewShouldOverrideUrlLoadingTestState state = InAppWebViewShouldOverrideUrlLoadingTestState();
@override
InAppWebViewShouldOverrideUrlLoadingTestState createState() => state;
}
class InAppWebViewShouldOverrideUrlLoadingTestState extends WidgetTestState {
String url = "https://flutter.dev/";
String appBarTitle = "InAppWebViewShouldOverrideUrlLoadingTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "https://www.google.com/",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true,
useShouldOverrideUrlLoading: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
setState(() {
appBarTitle = url;
});
nextTest(context: context, state: this);
},
shouldOverrideUrlLoading: (InAppWebViewController controller, String url) {
controller.loadUrl(url: url);
},
),
),
),
])
)
);
}
}
......@@ -8,8 +8,12 @@ import 'in_app_webview_fetch_test.dart';
import 'in_app_webview_initial_file_test.dart';
import 'in_app_webview_initial_url_test.dart';
import 'in_app_webview_javascript_handler_test.dart';
import 'in_app_webview_on_console_message_test.dart';
import 'in_app_webview_on_download_start_test.dart';
import 'in_app_webview_on_load_resource_custom_scheme_test.dart';
import 'in_app_webview_on_load_resource_test.dart';
import 'in_app_webview_on_target_blank_test.dart';
import 'in_app_webview_should_override_url_loading_test.dart';
List<String> testRoutes = [];
Map<String, WidgetBuilder> buildRoutes({@required BuildContext context}) {
......@@ -21,6 +25,11 @@ Map<String, WidgetBuilder> buildRoutes({@required BuildContext context}) {
'/InAppWebViewAjaxTest': (context) => InAppWebViewAjaxTest(),
'/InAppWebViewOnLoadResourceCustomSchemeTest': (context) => InAppWebViewOnLoadResourceCustomSchemeTest(),
'/InAppWebViewFetchTest': (context) => InAppWebViewFetchTest(),
'/InAppWebViewFetchTest': (context) => InAppWebViewFetchTest(),
'/InAppWebViewShouldOverrideUrlLoadingTest': (context) => InAppWebViewShouldOverrideUrlLoadingTest(),
'/InAppWebViewOnConsoleMessageTest': (context) => InAppWebViewOnConsoleMessageTest(),
'/InAppWebViewOnDownloadStartTest': (context) => InAppWebViewOnDownloadStartTest(),
'/InAppWebViewOnTargetBlankTest': (context) => InAppWebViewOnTargetBlankTest(),
};
routes.forEach((k, v) => testRoutes.add(k));
return routes;
......
......@@ -1204,48 +1204,35 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func injectDeferredObject(source: String, withWrapper jsWrapper: String, result: FlutterResult?) {
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
if sourceArrayString != nil {
public func injectDeferredObject(source: String, withWrapper jsWrapper: String?, result: FlutterResult?) {
var jsToInject = source
if let wrapper = jsWrapper {
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
let jsToInject = String(format: jsWrapper, sourceString!)
evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
if result == nil {
return
}
if error != nil {
let userInfo = (error! as NSError).userInfo
self.onConsoleMessage(sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: 3)
}
if value == nil {
result!("")
return
}
do {
let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)!
let json: Array<Any> = try JSONSerialization.jsonObject(with: data, options: []) as! Array<Any>
if json[0] is String {
result!(json[0])
}
else {
result!(value)
}
} catch let error as NSError {
result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error))
}
})
jsToInject = String(format: wrapper, sourceString!)
}
evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
if result == nil {
return
}
if error != nil {
let userInfo = (error! as NSError).userInfo
self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: 3)
}
if value == nil {
result!("")
return
}
result!(value)
})
}
public func evaluateJavascript(source: String, result: FlutterResult?) {
let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();"
injectDeferredObject(source: source, withWrapper: jsWrapper, result: result)
injectDeferredObject(source: source, withWrapper: nil, result: result)
}
public func injectJavascriptFileFromUrl(urlFile: String) {
......@@ -2007,8 +1994,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func onConsoleMessage(sourceURL: String, lineNumber: Int, message: String, messageLevel: Int) {
var arguments: [String: Any] = ["sourceURL": sourceURL, "lineNumber": lineNumber, "message": message, "messageLevel": messageLevel]
public func onConsoleMessage(message: String, messageLevel: Int) {
var arguments: [String: Any] = ["message": message, "messageLevel": messageLevel]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
......@@ -2065,7 +2052,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
messageLevel = 1
break;
}
onConsoleMessage(sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
onConsoleMessage(message: message.body as! String, messageLevel: messageLevel)
}
else if message.name == "callHandler" {
let body = message.body as! [String: Any]
......
......@@ -418,11 +418,9 @@ class InAppWebViewController {
_inAppBrowser.shouldOverrideUrlLoading(url);
break;
case "onConsoleMessage":
String sourceURL = call.arguments["sourceURL"];
int lineNumber = call.arguments["lineNumber"];
String message = call.arguments["message"];
ConsoleMessageLevel messageLevel = ConsoleMessageLevel.fromValue(call.arguments["messageLevel"]);
ConsoleMessage consoleMessage = ConsoleMessage(sourceURL: sourceURL, lineNumber: lineNumber, message: message, messageLevel: messageLevel);
ConsoleMessage consoleMessage = ConsoleMessage(message: message, messageLevel: messageLevel);
if (_widget != null && _widget.onConsoleMessage != null)
_widget.onConsoleMessage(this, consoleMessage);
else if (_inAppBrowser != null)
......@@ -744,7 +742,7 @@ class InAppWebViewController {
InAppWebViewWidgetOptions options = await getOptions();
if (options != null && options.inAppWebViewOptions.javaScriptEnabled == true) {
html = await evaluateJavascript(source: "window.document.getElementsByTagName('html')[0].outerHTML;");
if (html.isNotEmpty)
if (html != null && html.isNotEmpty)
return html;
}
......@@ -1064,14 +1062,17 @@ class InAppWebViewController {
}
///Evaluates JavaScript code into the WebView and returns the result of the evaluation.
Future<String> evaluateJavascript({@required String source}) async {
Future<dynamic> evaluateJavascript({@required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
if (_inAppBrowserUuid != null && _inAppBrowser != null) {
_inAppBrowser.throwIsNotOpened();
args.putIfAbsent('uuid', () => _inAppBrowserUuid);
}
args.putIfAbsent('source', () => source);
return await _channel.invokeMethod('evaluateJavascript', args);
var data = await _channel.invokeMethod('evaluateJavascript', args);
if (data != null && Platform.isAndroid)
data = json.decode(data);
return data;
}
///Injects an external JavaScript file into the WebView from a defined url.
......
......@@ -33,6 +33,21 @@ class ConsoleMessageLevel {
return null;
}
toValue() => _value;
toString() {
switch(_value) {
case 0:
return "TIP";
case 2:
return "WARNING";
case 3:
return "ERROR";
case 4:
return "DEBUG";
case 1:
default:
return "LOG";
}
}
static const TIP = const ConsoleMessageLevel._internal(0);
static const LOG = const ConsoleMessageLevel._internal(1);
......@@ -40,7 +55,6 @@ class ConsoleMessageLevel {
static const ERROR = const ConsoleMessageLevel._internal(3);
static const DEBUG = const ConsoleMessageLevel._internal(4);
bool operator ==(value) => value == _value;
@override
......@@ -143,12 +157,10 @@ class CustomSchemeResponse {
///To receive notifications of these messages, use the [onConsoleMessage] event.
class ConsoleMessage {
String sourceURL;
int lineNumber;
String message;
ConsoleMessageLevel messageLevel;
ConsoleMessage({this.sourceURL = "", this.lineNumber = 1, this.message = "", this.messageLevel = ConsoleMessageLevel.LOG});
ConsoleMessage({this.message = "", this.messageLevel = ConsoleMessageLevel.LOG});
}
///WebHistory class.
......
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