@modos189/nativescript-webview-x-gecko 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,12 +55,15 @@ _None._
55
55
  | `userAgent` | `string` | Set a custom User-Agent string |
56
56
  | `debugMode` | `boolean` | Enable remote WebView debugging |
57
57
  | `supportPopups` | `boolean` | Open `window.open()` / `target="_blank"` links in a native popup. Default: `true` |
58
+ | `autoInjectJSBridge` | `boolean` | Inject `window.nsWebViewBridge` on every `loadFinished`. Default: `true` |
58
59
 
59
60
  ### Methods
60
61
 
61
62
  | Method | Returns | Description |
62
63
  | --- | --- | --- |
63
64
  | `getTitle()` | `Promise<string | undefined>` | Return the current page title |
65
+ | `executeJavaScript(code: string)` | `Promise<any>` | Execute JavaScript in the page context and return the JSON-serialised result |
66
+ | `emitToWebView(eventName: string, data: any)` | `void` | Emit an event into the page's `nsWebViewBridge` (calls `onNativeEvent` inside the WebView) |
64
67
 
65
68
  ### Events
66
69
 
@@ -0,0 +1 @@
1
+ export declare const geckoBridgeJs = "(function(){if(!window.nsWebViewBridge){var i={};window.nsWebViewBridge={on:function(n,e){i[n]||(i[n]=[]),i[n].push(e)},addEventListener:function(n,e){this.on(n,e)},off:function(n,e){if(!n){i={};return}if(i[n]){if(!e){delete i[n];return}i[n]=i[n].filter(function(t){return t!==e}),i[n].length===0&&delete i[n]}},removeEventListener:function(n,e){this.off(n,e)},emit:function(n,e){document.dispatchEvent(new CustomEvent(\"__ns_bridge_emit__\",{detail:{eventName:n,data:JSON.stringify(e)}}))},onNativeEvent:function(n,e){var t=i[n];if(t)for(var f=0;f<t.length&&!(t[f]&&t[f](e)===!1);f++);}};var r=window;typeof CustomEvent<\"u\"?r.dispatchEvent(new CustomEvent(\"ns-bridge-ready\",{detail:r.nsWebViewBridge})):r.dispatchEvent(new Event(\"ns-bridge-ready\")),r.dispatchEvent(new Event(\"ns-brige-ready\"))}})();";
@@ -0,0 +1,4 @@
1
+ // AUTO-GENERATED by tools/scripts/generate-gecko-bridge.js - do not edit directly.
2
+ // Source: packages/webview-x-gecko/www-src/bridge.gecko.js
3
+ export const geckoBridgeJs = '(function(){if(!window.nsWebViewBridge){var i={};window.nsWebViewBridge={on:function(n,e){i[n]||(i[n]=[]),i[n].push(e)},addEventListener:function(n,e){this.on(n,e)},off:function(n,e){if(!n){i={};return}if(i[n]){if(!e){delete i[n];return}i[n]=i[n].filter(function(t){return t!==e}),i[n].length===0&&delete i[n]}},removeEventListener:function(n,e){this.off(n,e)},emit:function(n,e){document.dispatchEvent(new CustomEvent("__ns_bridge_emit__",{detail:{eventName:n,data:JSON.stringify(e)}}))},onNativeEvent:function(n,e){var t=i[n];if(t)for(var f=0;f<t.length&&!(t[f]&&t[f](e)===!1);f++);}};var r=window;typeof CustomEvent<"u"?r.dispatchEvent(new CustomEvent("ns-bridge-ready",{detail:r.nsWebViewBridge})):r.dispatchEvent(new Event("ns-bridge-ready")),r.dispatchEvent(new Event("ns-brige-ready"))}})();';
4
+ //# sourceMappingURL=gecko-bridge-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gecko-bridge-loader.js","sourceRoot":"","sources":["../../../packages/webview-x-gecko/gecko-bridge-loader.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GACxB,gyBAAgyB,CAAC"}
@@ -4,6 +4,7 @@ export declare const srcProperty: Property<WebViewX, string>;
4
4
  export declare const debugModeProperty: Property<WebViewX, boolean>;
5
5
  export declare const supportPopupsProperty: Property<WebViewX, boolean>;
6
6
  export declare const userAgentProperty: Property<WebViewX, string>;
7
+ export declare const autoInjectJSBridgeProperty: Property<WebViewX, boolean>;
7
8
  export declare class WebViewX extends View {
8
9
  static get popupNavigateEvent(): string;
9
10
  static get loadStartedEvent(): string;
@@ -20,13 +21,22 @@ export declare class WebViewX extends View {
20
21
  debugMode: boolean;
21
22
  supportPopups: boolean;
22
23
  userAgent: string;
24
+ autoInjectJSBridge: boolean;
25
+ private _bridgeListener;
23
26
  _onPopupNavigate(url: string): boolean;
24
27
  _onLoadStarted(url: string): void;
25
- _onLoadFinished(url: string, error?: string): void;
28
+ _onLoadFinished(url: string, error?: string): Promise<void>;
29
+ emitToWebView(eventName: string, data: any): void;
30
+ onWebViewEvent(eventName: string, data: any): void;
26
31
  _loadProgress(progress: number): void;
27
32
  _titleChanged(title: string): void;
28
33
  /** Returns the current page title (cached from GeckoDelegates.ContentDelegate.onTitleChange). */
29
34
  getTitle(): Promise<string | undefined>;
35
+ /**
36
+ * Execute JavaScript in the current page's context and return the result.
37
+ * Uses a built-in WebExtension bridge; requires GeckoView 65+.
38
+ */
39
+ executeJavaScript<T = any>(code: string): Promise<T>;
30
40
  createNativeView(): org.mozilla.geckoview.GeckoView;
31
41
  disposeNativeView(): void;
32
42
  }
package/index.android.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Application, View, Property, booleanConverter } from '@nativescript/core';
2
+ import { geckoBridgeJs as _GECKO_BRIDGE_JS } from './gecko-bridge-loader';
2
3
  import { LOAD_STARTED_EVENT, LOAD_FINISHED_EVENT, LOAD_PROGRESS_EVENT, TITLE_CHANGED_EVENT } from './common';
3
4
  export * from './common';
4
5
  const POPUP_NAVIGATE_EVENT = 'popupNavigate';
@@ -20,6 +21,11 @@ export const supportPopupsProperty = new Property({
20
21
  export const userAgentProperty = new Property({
21
22
  name: 'userAgent',
22
23
  });
24
+ export const autoInjectJSBridgeProperty = new Property({
25
+ name: 'autoInjectJSBridge',
26
+ defaultValue: true,
27
+ valueConverter: booleanConverter,
28
+ });
23
29
  export class WebViewX extends View {
24
30
  constructor() {
25
31
  super(...arguments);
@@ -28,6 +34,7 @@ export class WebViewX extends View {
28
34
  this._currentUrl = '';
29
35
  this._currentTitle = '';
30
36
  this._tempSuspendSrcLoading = false;
37
+ this._bridgeListener = null;
31
38
  }
32
39
  static get popupNavigateEvent() {
33
40
  return POPUP_NAVIGATE_EVENT;
@@ -62,7 +69,7 @@ export class WebViewX extends View {
62
69
  url,
63
70
  });
64
71
  }
65
- _onLoadFinished(url, error) {
72
+ async _onLoadFinished(url, error) {
66
73
  if (!error) {
67
74
  try {
68
75
  this._tempSuspendSrcLoading = true;
@@ -72,6 +79,14 @@ export class WebViewX extends View {
72
79
  this._tempSuspendSrcLoading = false;
73
80
  }
74
81
  }
82
+ if (!error && this.autoInjectJSBridge) {
83
+ try {
84
+ await this.executeJavaScript(_GECKO_BRIDGE_JS);
85
+ }
86
+ catch {
87
+ // bridge injection is best-effort
88
+ }
89
+ }
75
90
  this.notify({
76
91
  eventName: LOAD_FINISHED_EVENT,
77
92
  object: this,
@@ -79,6 +94,15 @@ export class WebViewX extends View {
79
94
  error,
80
95
  });
81
96
  }
97
+ emitToWebView(eventName, data) {
98
+ const code = `window.nsWebViewBridge&&nsWebViewBridge.onNativeEvent(${JSON.stringify(eventName)},${JSON.stringify(data)});`;
99
+ this.executeJavaScript(code).catch(() => {
100
+ // ignore — page may not have bridge
101
+ });
102
+ }
103
+ onWebViewEvent(eventName, data) {
104
+ this.notify({ eventName, data });
105
+ }
82
106
  _loadProgress(progress) {
83
107
  this.notify({
84
108
  eventName: LOAD_PROGRESS_EVENT,
@@ -100,8 +124,44 @@ export class WebViewX extends View {
100
124
  async getTitle() {
101
125
  return this._currentTitle || undefined;
102
126
  }
127
+ /**
128
+ * Execute JavaScript in the current page's context and return the result.
129
+ * Uses a built-in WebExtension bridge; requires GeckoView 65+.
130
+ */
131
+ executeJavaScript(code) {
132
+ return new Promise((resolve, reject) => {
133
+ const bridge = com.modos189.webviewxgecko.GeckoJsBridge.getInstance();
134
+ const id = bridge.nextId();
135
+ bridge.executeScript(id, code, new com.modos189.webviewxgecko.GeckoJsBridge.JsCallback({
136
+ onResult(jsonResult) {
137
+ resolve(_parseJsResult(jsonResult));
138
+ },
139
+ onError(error) {
140
+ reject(new Error(error));
141
+ },
142
+ }));
143
+ });
144
+ }
103
145
  createNativeView() {
104
146
  const runtime = com.modos189.webviewxgecko.GeckoPopupHelper.getRuntime(Application.android.context);
147
+ com.modos189.webviewxgecko.GeckoJsBridge.getInstance().setup(runtime);
148
+ const selfRef = new WeakRef(this);
149
+ this._bridgeListener = new com.modos189.webviewxgecko.GeckoJsBridge.BridgeEventListener({
150
+ onBridgeEvent(eventName, dataJson) {
151
+ const owner = selfRef.deref();
152
+ if (!owner)
153
+ return;
154
+ let data;
155
+ try {
156
+ data = JSON.parse(dataJson);
157
+ }
158
+ catch {
159
+ data = dataJson;
160
+ }
161
+ owner.onWebViewEvent(eventName, data);
162
+ },
163
+ });
164
+ com.modos189.webviewxgecko.GeckoJsBridge.getInstance().addBridgeListener(this._bridgeListener);
105
165
  const session = new org.mozilla.geckoview.GeckoSession();
106
166
  this._popupHelper = new com.modos189.webviewxgecko.GeckoPopupHelper(session, this._context, true);
107
167
  const interceptor = new com.modos189.webviewxgecko.GeckoPopupHelper.PopupUrlInterceptor({
@@ -127,6 +187,10 @@ export class WebViewX extends View {
127
187
  return geckoView;
128
188
  }
129
189
  disposeNativeView() {
190
+ if (this._bridgeListener) {
191
+ com.modos189.webviewxgecko.GeckoJsBridge.getInstance().removeBridgeListener(this._bridgeListener);
192
+ this._bridgeListener = null;
193
+ }
130
194
  this._popupHelper = null;
131
195
  if (this._session) {
132
196
  this._session.setProgressDelegate(null);
@@ -152,9 +216,23 @@ export class WebViewX extends View {
152
216
  [userAgentProperty.setNative](value) {
153
217
  this._session?.getSettings().setUserAgentOverride(value || null);
154
218
  }
219
+ [autoInjectJSBridgeProperty.setNative](_value) {
220
+ // value stored as a property field; read in _onLoadFinished
221
+ }
155
222
  }
156
223
  srcProperty.register(WebViewX);
157
224
  debugModeProperty.register(WebViewX);
158
225
  supportPopupsProperty.register(WebViewX);
159
226
  userAgentProperty.register(WebViewX);
227
+ autoInjectJSBridgeProperty.register(WebViewX);
228
+ function _parseJsResult(jsonString) {
229
+ if (!jsonString || jsonString === 'null')
230
+ return null;
231
+ try {
232
+ return JSON.parse(jsonString);
233
+ }
234
+ catch {
235
+ return jsonString;
236
+ }
237
+ }
160
238
  //# sourceMappingURL=index.android.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.android.js","sourceRoot":"","sources":["../../../packages/webview-x-gecko/index.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAA6F,MAAM,UAAU,CAAC;AAExM,cAAc,UAAU,CAAC;AAEzB,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAE7C,2FAA2F;AAC3F,MAAM,CAAC,MAAM,WAAW,GAA+B,IAAI,QAAQ,CAAmB;IACpF,IAAI,EAAE,KAAK;IACX,YAAY,EAAE,EAAE;CACjB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,QAAQ,CAAoB;IAC5F,IAAI,EAAE,WAAW;IACjB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,gBAAgB;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAgC,IAAI,QAAQ,CAAoB;IAChG,IAAI,EAAE,eAAe;IACrB,YAAY,EAAE,IAAI;IAClB,cAAc,EAAE,gBAAgB;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAA+B,IAAI,QAAQ,CAAmB;IAC1F,IAAI,EAAE,WAAW;CAClB,CAAC,CAAC;AAEH,MAAM,OAAO,QAAS,SAAQ,IAAI;IAAlC;;QAsBU,aAAQ,GAA8C,IAAI,CAAC;QAC3D,iBAAY,GAAuD,IAAI,CAAC;QAEhF,gBAAW,GAAW,EAAE,CAAC;QACzB,kBAAa,GAAW,EAAE,CAAC;QAC3B,2BAAsB,GAAY,KAAK,CAAC;IAwI1C,CAAC;IAlKC,MAAM,KAAK,kBAAkB;QAC3B,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,MAAM,KAAK,gBAAgB;QACzB,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,MAAM,KAAK,iBAAiB;QAC1B,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,iBAAiB;QAC1B,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,iBAAiB;QAC1B,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAeD,gBAAgB,CAAC,GAAW;QAC1B,MAAM,IAAI,GAAQ;YAChB,SAAS,EAAE,oBAAoB;YAC/B,GAAG;YACH,MAAM,EAAE,KAAK;SACd,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,cAAc,CAAC,GAAW;QACxB,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC,CAAC,2CAA2C;QACpE,IAAI,CAAC,MAAM,CAAC;YACV,SAAS,EAAE,kBAAkB;YAC7B,MAAM,EAAE,IAAI;YACZ,GAAG;SACoB,CAAC,CAAC;IAC7B,CAAC;IAED,eAAe,CAAC,GAAW,EAAE,KAAc;QACzC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC;gBACH,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;gBACnC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;YACjB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,sBAAsB,GAAG,KAAK,CAAC;YACtC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,CAAC;YACV,SAAS,EAAE,mBAAmB;YAC9B,MAAM,EAAE,IAAI;YACZ,GAAG;YACH,KAAK;SACmB,CAAC,CAAC;IAC9B,CAAC;IAED,aAAa,CAAC,QAAgB;QAC5B,IAAI,CAAC,MAAM,CAAC;YACV,SAAS,EAAE,mBAAmB;YAC9B,MAAM,EAAE,IAAI;YACZ,GAAG,EAAE,IAAI,CAAC,WAAW;YACrB,QAAQ;SACgB,CAAC,CAAC;IAC9B,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC;YACV,SAAS,EAAE,mBAAmB;YAC9B,MAAM,EAAE,IAAI;YACZ,GAAG,EAAE,IAAI,CAAC,WAAW;YACrB,KAAK;SACmB,CAAC,CAAC;IAC9B,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,QAAQ;QACZ,OAAO,IAAI,CAAC,aAAa,IAAI,SAAS,CAAC;IACzC,CAAC;IAED,gBAAgB;QACd,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACpG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;QAEzD,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClG,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,mBAAmB,CAAC;YACtF,sBAAsB,EAAE,CAAC,GAAW,EAAE,EAAE;gBACtC,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5D,CAAC;SACF,CAAC,CAAC;QACH,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAEjD,6FAA6F;QAC7F,kFAAkF;QAClF,OAAO,CAAC,mBAAmB,CACzB,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,gBAAgB,CAC5D,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,gBAAgB,CAAC;YAC7D,WAAW,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;YACtD,UAAU,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YAC7G,gBAAgB,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;SACrE,CAAC,CACH,CACF,CAAC;QAEF,OAAO,CAAC,kBAAkB,CACxB,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,eAAe,CAC3D,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,eAAe,CAAC;YAC5D,aAAa,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;SAC5D,CAAC,CACH,CACF,CAAC;QAEF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrE,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,KAAK,CAAC,iBAAiB,EAAE,CAAC;IAC5B,CAAC;IAED,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,KAAa;QACnC,IAAI,IAAI,CAAC,sBAAsB;YAAE,OAAO;QACxC,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,KAAc;QAC1C,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACjF,CAAC;IAED,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,KAAc;QAC9C,IAAI,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,KAAa;QACxC,IAAI,CAAC,QAAgB,EAAE,WAAW,EAAE,CAAC,oBAAoB,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IAC5E,CAAC;CACF;AAED,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC/B,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACrC,qBAAqB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"index.android.js","sourceRoot":"","sources":["../../../packages/webview-x-gecko/index.android.ts"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Property } from '@nativescript/core';
4
4
  export declare const srcProperty: Property<WebViewX, string>;
5
5
  export declare const debugModeProperty: Property<WebViewX, boolean>;
6
6
  export declare const supportPopupsProperty: Property<WebViewX, boolean>;
7
+ export declare const autoInjectJSBridgeProperty: Property<WebViewX, boolean>;
7
8
 
8
9
  export * from './common';
9
10
  import { LoadStartedEventData, LoadFinishedEventData, LoadProgressEventData, TitleChangedEventData } from './common';
@@ -19,6 +20,7 @@ export declare class WebViewX extends View {
19
20
  debugMode: boolean;
20
21
  supportPopups: boolean;
21
22
  userAgent: string;
23
+ autoInjectJSBridge: boolean;
22
24
 
23
25
  on(event: 'loadStarted', callback: (args: LoadStartedEventData) => void, thisArg?: any): void;
24
26
  on(event: 'loadFinished', callback: (args: LoadFinishedEventData) => void, thisArg?: any): void;
@@ -28,4 +30,13 @@ export declare class WebViewX extends View {
28
30
 
29
31
  /** Returns the current page title (last value received from the engine). */
30
32
  getTitle(): Promise<string | undefined>;
33
+
34
+ /** Execute JavaScript in the current page's context and return the result. */
35
+ executeJavaScript<T = any>(code: string): Promise<T>;
36
+
37
+ /** Emit an event into the page's nsWebViewBridge. */
38
+ emitToWebView(eventName: string, data: any): void;
39
+
40
+ /** Called when the page's nsWebViewBridge.emit() fires. Dispatches a NativeScript event. */
41
+ onWebViewEvent(eventName: string, data: any): void;
31
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modos189/nativescript-webview-x-gecko",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Add a plugin description",
5
5
  "main": "index",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // "native" matches the name passed to extension.setMessageDelegate(bridge, "native")
4
+ const port = browser.runtime.connectNative('native');
5
+
6
+ async function sendMessageToTab(message) {
7
+ const tabs = await browser.tabs.query({});
8
+ if (tabs.length === 0) {
9
+ throw new Error('no tabs available');
10
+ }
11
+ return await browser.tabs.sendMessage(tabs[tabs.length - 1].id, message);
12
+ }
13
+
14
+ // Forward nsWebViewBridge.emit() events from content script to native
15
+ browser.runtime.onMessage.addListener((message) => {
16
+ if (message.type === 'bridge-emit') {
17
+ port.postMessage(message);
18
+ return false;
19
+ }
20
+ });
21
+
22
+ port.onMessage.addListener((request) => {
23
+ sendMessageToTab(request)
24
+ .then((resp) => port.postMessage(resp))
25
+ .catch((e) => port.postMessage({ id: request.id, error: e.toString() }));
26
+ });
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ browser.runtime.onMessage.addListener((data) => {
4
+ if (data.inject === undefined) {
5
+ return Promise.resolve();
6
+ }
7
+ try {
8
+ // wrappedJSObject bypasses Xray vision so eval runs in the page's JS context
9
+ const result = window.wrappedJSObject.eval(data.inject);
10
+ let serialized;
11
+ if (result === undefined) {
12
+ serialized = 'null';
13
+ } else {
14
+ try {
15
+ serialized = JSON.stringify(result);
16
+ if (serialized === undefined) serialized = 'null';
17
+ } catch (_e) {
18
+ serialized = String(result);
19
+ }
20
+ }
21
+ return Promise.resolve({ id: data.id, result: serialized });
22
+ } catch (e) {
23
+ return Promise.resolve({ id: data.id, error: e.toString() });
24
+ }
25
+ });
26
+
27
+ // Forward nsWebViewBridge.emit() calls from the page to the native layer.
28
+ // The bridge JS (injected on loadFinished) dispatches this CustomEvent on document
29
+ // when window.nsWebViewBridge.emit() is called.
30
+ document.addEventListener('__ns_bridge_emit__', (event) => {
31
+ browser.runtime.sendMessage({
32
+ type: 'bridge-emit',
33
+ eventName: event.detail.eventName,
34
+ data: event.detail.data,
35
+ });
36
+ });
@@ -0,0 +1,22 @@
1
+ {
2
+ "manifest_version": 2,
3
+ "name": "execute-js",
4
+ "version": "1.0",
5
+ "browser_specific_settings": {
6
+ "gecko": {
7
+ "strict_min_version": "65.0",
8
+ "id": "execute-js@app.com"
9
+ }
10
+ },
11
+ "background": {
12
+ "scripts": ["background.js"]
13
+ },
14
+ "permissions": ["nativeMessaging", "geckoViewAddons", "tabs", "<all_urls>"],
15
+ "content_scripts": [
16
+ {
17
+ "matches": ["<all_urls>"],
18
+ "js": ["content.js"],
19
+ "run_at": "document_start"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,173 @@
1
+ package com.modos189.webviewxgecko;
2
+
3
+ import android.os.Handler;
4
+ import android.os.Looper;
5
+ import android.util.Log;
6
+
7
+ import org.json.JSONException;
8
+ import org.json.JSONObject;
9
+ import org.mozilla.geckoview.GeckoRuntime;
10
+ import org.mozilla.geckoview.WebExtension;
11
+ import org.mozilla.geckoview.WebExtensionController;
12
+
13
+ import java.util.ArrayList;
14
+ import java.util.List;
15
+ import java.util.concurrent.ConcurrentHashMap;
16
+ import java.util.concurrent.CopyOnWriteArrayList;
17
+ import java.util.concurrent.atomic.AtomicInteger;
18
+ import java.lang.ref.WeakReference;
19
+
20
+ /**
21
+ * Singleton bridge for executing JavaScript in GeckoView pages via a built-in WebExtension.
22
+ * NativeScript cannot implement WebExtension delegate interfaces directly (Java 8 default
23
+ * methods break the DEX proxy factory), so this class acts as the concrete delegate.
24
+ */
25
+ public class GeckoJsBridge implements WebExtension.MessageDelegate, WebExtension.PortDelegate {
26
+
27
+ private static final String TAG = "GeckoJsBridge";
28
+ private static final String EXTENSION_ID = "execute-js@app.com";
29
+ private static final String EXTENSION_URL = "resource://android/assets/execute-js/";
30
+ private static final String PORT_NAME = "native";
31
+
32
+ private static volatile GeckoJsBridge sInstance;
33
+
34
+ private WebExtension.Port mPort;
35
+ private final ConcurrentHashMap<String, JsCallback> mPendingCallbacks = new ConcurrentHashMap<>();
36
+ // Accessed only on main thread after posting via mMainHandler
37
+ private final List<JSONObject> mPendingMessages = new ArrayList<>();
38
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
39
+ private final AtomicInteger mIdCounter = new AtomicInteger(0);
40
+
41
+ public interface JsCallback {
42
+ void onResult(String jsonResult);
43
+ void onError(String error);
44
+ }
45
+
46
+ public interface BridgeEventListener {
47
+ void onBridgeEvent(String eventName, String dataJson);
48
+ }
49
+
50
+ private final CopyOnWriteArrayList<WeakReference<BridgeEventListener>> mBridgeListeners = new CopyOnWriteArrayList<>();
51
+
52
+ public void addBridgeListener(BridgeEventListener listener) {
53
+ mBridgeListeners.add(new WeakReference<>(listener));
54
+ }
55
+
56
+ public void removeBridgeListener(BridgeEventListener listener) {
57
+ mBridgeListeners.removeIf(ref -> {
58
+ BridgeEventListener l = ref.get();
59
+ return l == null || l == listener;
60
+ });
61
+ }
62
+
63
+ private void notifyBridgeListeners(String eventName, String dataJson) {
64
+ for (WeakReference<BridgeEventListener> ref : mBridgeListeners) {
65
+ BridgeEventListener listener = ref.get();
66
+ if (listener != null) {
67
+ listener.onBridgeEvent(eventName, dataJson);
68
+ }
69
+ }
70
+ }
71
+
72
+ public static GeckoJsBridge getInstance() {
73
+ if (sInstance == null) {
74
+ synchronized (GeckoJsBridge.class) {
75
+ if (sInstance == null) {
76
+ sInstance = new GeckoJsBridge();
77
+ }
78
+ }
79
+ }
80
+ return sInstance;
81
+ }
82
+
83
+ private GeckoJsBridge() {}
84
+
85
+ public String nextId() {
86
+ return String.valueOf(mIdCounter.incrementAndGet());
87
+ }
88
+
89
+ /** Idempotent - safe to call on every WebViewX creation. */
90
+ public void setup(GeckoRuntime runtime) {
91
+ runtime.getWebExtensionController()
92
+ .ensureBuiltIn(EXTENSION_URL, EXTENSION_ID)
93
+ .accept(
94
+ extension -> mMainHandler.post(() -> {
95
+ if (extension == null) return;
96
+ // Disable then re-enable so the background script reconnects
97
+ // and triggers onConnect even if the extension was already loaded.
98
+ runtime.getWebExtensionController()
99
+ .disable(extension, WebExtensionController.EnableSource.APP);
100
+ runtime.getWebExtensionController()
101
+ .enable(extension, WebExtensionController.EnableSource.APP);
102
+ extension.setMessageDelegate(GeckoJsBridge.this, PORT_NAME);
103
+ }),
104
+ e -> Log.e(TAG, "Failed to install WebExtension", e)
105
+ );
106
+ }
107
+
108
+ public void executeScript(final String id, final String code, final JsCallback callback) {
109
+ final JSONObject msg;
110
+ try {
111
+ msg = new JSONObject();
112
+ msg.put("id", id);
113
+ msg.put("inject", code);
114
+ } catch (JSONException e) {
115
+ callback.onError(e.getMessage());
116
+ return;
117
+ }
118
+
119
+ mPendingCallbacks.put(id, callback);
120
+
121
+ mMainHandler.post(() -> {
122
+ if (mPort != null) {
123
+ mPort.postMessage(msg);
124
+ } else {
125
+ mPendingMessages.add(msg);
126
+ }
127
+ });
128
+ }
129
+
130
+ @Override
131
+ public void onConnect(WebExtension.Port port) {
132
+ mPort = port;
133
+ port.setDelegate(this);
134
+
135
+ // Flush any calls that arrived before the port was ready
136
+ for (JSONObject msg : mPendingMessages) {
137
+ port.postMessage(msg);
138
+ }
139
+ mPendingMessages.clear();
140
+ }
141
+
142
+ @Override
143
+ public void onPortMessage(Object message, WebExtension.Port port) {
144
+ if (!(message instanceof JSONObject)) return;
145
+ final JSONObject obj = (JSONObject) message;
146
+ try {
147
+ if ("bridge-emit".equals(obj.optString("type"))) {
148
+ final String eventName = obj.optString("eventName", "");
149
+ final String data = obj.optString("data", "null");
150
+ mMainHandler.post(() -> notifyBridgeListeners(eventName, data));
151
+ return;
152
+ }
153
+
154
+ final String id = obj.getString("id");
155
+ final JsCallback callback = mPendingCallbacks.remove(id);
156
+ if (callback == null) return;
157
+
158
+ if (obj.has("error")) {
159
+ callback.onError(obj.getString("error"));
160
+ } else {
161
+ // optString falls back to "null" when key is absent or value is JSON null
162
+ callback.onResult(obj.optString("result", "null"));
163
+ }
164
+ } catch (JSONException e) {
165
+ Log.e(TAG, "Error processing JS result message", e);
166
+ }
167
+ }
168
+
169
+ @Override
170
+ public void onDisconnect(WebExtension.Port port) {
171
+ mPort = null;
172
+ }
173
+ }