UNPKG

16.3 kBJavaScriptView Raw
1/*
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 */
21
22/* eslint-disable standard/no-callback-literal */
23/* global Windows, setImmediate */
24
25var cordova = require('cordova');
26var urlutil = require('cordova/urlutil');
27
28var browserWrap,
29 popup,
30 navigationButtonsDiv,
31 navigationButtonsDivInner,
32 backButton,
33 forwardButton,
34 closeButton,
35 bodyOverflowStyle,
36 navigationEventsCallback,
37 hardwareBackCallback;
38
39// x-ms-webview is available starting from Windows 8.1 (platformId is 'windows')
40// http://msdn.microsoft.com/en-us/library/windows/apps/dn301831.aspx
41var isWebViewAvailable = cordova.platformId === 'windows';
42
43function attachNavigationEvents (element, callback) {
44 if (isWebViewAvailable) {
45 element.addEventListener('MSWebViewNavigationStarting', function (e) {
46 callback({ type: 'loadstart', url: e.uri }, { keepCallback: true });
47 });
48
49 element.addEventListener('MSWebViewNavigationCompleted', function (e) {
50 if (e.isSuccess) {
51 callback({ type: 'loadstop', url: e.uri }, { keepCallback: true });
52 } else {
53 callback(
54 {
55 type: 'loaderror',
56 url: e.uri,
57 code: e.webErrorStatus,
58 message: 'Navigation failed with error code ' + e.webErrorStatus
59 },
60 { keepCallback: true }
61 );
62 }
63 });
64
65 element.addEventListener('MSWebViewUnviewableContentIdentified', function (e) {
66 // WebView found the content to be not HTML.
67 // http://msdn.microsoft.com/en-us/library/windows/apps/dn609716.aspx
68 callback(
69 { type: 'loaderror', url: e.uri, code: e.webErrorStatus, message: 'Navigation failed with error code ' + e.webErrorStatus },
70 { keepCallback: true }
71 );
72 });
73
74 element.addEventListener('MSWebViewContentLoading', function (e) {
75 if (navigationButtonsDiv && popup) {
76 if (popup.canGoBack) {
77 backButton.removeAttribute('disabled');
78 } else {
79 backButton.setAttribute('disabled', 'true');
80 }
81
82 if (popup.canGoForward) {
83 forwardButton.removeAttribute('disabled');
84 } else {
85 forwardButton.setAttribute('disabled', 'true');
86 }
87 }
88 });
89 } else {
90 var onError = function () {
91 callback({ type: 'loaderror', url: this.contentWindow.location }, { keepCallback: true });
92 };
93
94 element.addEventListener('unload', function () {
95 callback({ type: 'loadstart', url: this.contentWindow.location }, { keepCallback: true });
96 });
97
98 element.addEventListener('load', function () {
99 callback({ type: 'loadstop', url: this.contentWindow.location }, { keepCallback: true });
100 });
101
102 element.addEventListener('error', onError);
103 element.addEventListener('abort', onError);
104 }
105}
106
107var IAB = {
108 close: function (win, lose) {
109 setImmediate(function () {
110 if (browserWrap) {
111 if (navigationEventsCallback) {
112 navigationEventsCallback({ type: 'exit' });
113 }
114
115 browserWrap.parentNode.removeChild(browserWrap);
116 // Reset body overflow style to initial value
117 document.body.style.msOverflowStyle = bodyOverflowStyle;
118 browserWrap = null;
119 popup = null;
120
121 document.removeEventListener('backbutton', hardwareBackCallback, false);
122 }
123 });
124 },
125 show: function (win, lose) {
126 setImmediate(function () {
127 if (browserWrap) {
128 browserWrap.style.display = 'block';
129 }
130 });
131 },
132 hide: function (win, lose) {
133 if (browserWrap) {
134 browserWrap.style.display = 'none';
135 }
136 },
137 open: function (win, lose, args) {
138 // make function async so that we can add navigation events handlers before view is loaded and navigation occured
139 setImmediate(function () {
140 var strUrl = args[0];
141 var target = args[1];
142 var features = args[2];
143 var url;
144
145 navigationEventsCallback = win;
146
147 if (target === '_system') {
148 url = new Windows.Foundation.Uri(strUrl);
149 Windows.System.Launcher.launchUriAsync(url);
150 } else if (target === '_self' || !target) {
151 window.location = strUrl;
152 } else {
153 // "_blank" or anything else
154 if (!browserWrap) {
155 var browserWrapStyle = document.createElement('link');
156 browserWrapStyle.rel = 'stylesheet';
157 browserWrapStyle.type = 'text/css';
158 browserWrapStyle.href = urlutil.makeAbsolute('/www/css/inappbrowser.css');
159
160 document.head.appendChild(browserWrapStyle);
161
162 browserWrap = document.createElement('div');
163 browserWrap.className = 'inAppBrowserWrap';
164
165 if (features.indexOf('fullscreen=yes') > -1) {
166 browserWrap.classList.add('inAppBrowserWrapFullscreen');
167 }
168
169 // Save body overflow style to be able to reset it back later
170 bodyOverflowStyle = document.body.style.msOverflowStyle;
171
172 browserWrap.onclick = function () {
173 setTimeout(function () {
174 IAB.close(navigationEventsCallback);
175 }, 0);
176 };
177
178 document.body.appendChild(browserWrap);
179 // Hide scrollbars for the whole body while inappbrowser's window is open
180 document.body.style.msOverflowStyle = 'none';
181 }
182
183 if (features.indexOf('hidden=yes') !== -1) {
184 browserWrap.style.display = 'none';
185 }
186
187 popup = document.createElement(isWebViewAvailable ? 'x-ms-webview' : 'iframe');
188 if (popup instanceof HTMLIFrameElement) {
189 // eslint-disable-line no-undef
190 // For iframe we need to override bacground color of parent element here
191 // otherwise pages without background color set will have transparent background
192 popup.style.backgroundColor = 'white';
193 }
194 popup.style.borderWidth = '0px';
195 popup.style.width = '100%';
196 popup.style.marginBottom = '-5px';
197
198 browserWrap.appendChild(popup);
199
200 var closeHandler = function (e) {
201 setTimeout(function () {
202 IAB.close(navigationEventsCallback);
203 }, 0);
204 };
205
206 if (features.indexOf('hardwareback=yes') > -1 || features.indexOf('hardwareback') === -1) {
207 hardwareBackCallback = function () {
208 if (browserWrap.style.display === 'none') {
209 // NOTE: backbutton handlers have to throw an exception in order to prevent
210 // returning 'true' inside cordova-js, which would mean that the event is handled by user.
211 // Throwing an exception means that the default/system navigation behavior will take place,
212 // which is to exit the app if the navigation stack is empty.
213 throw 'Exit the app'; // eslint-disable-line no-throw-literal
214 }
215
216 if (popup.canGoBack) {
217 popup.goBack();
218 } else {
219 closeHandler();
220 }
221 };
222 } else if (features.indexOf('hardwareback=no') > -1) {
223 hardwareBackCallback = function () {
224 if (browserWrap.style.display === 'none') {
225 // See comment above
226 throw 'Exit the app'; // eslint-disable-line no-throw-literal
227 }
228
229 closeHandler();
230 };
231 }
232
233 document.addEventListener('backbutton', hardwareBackCallback, false);
234
235 if (features.indexOf('location=yes') !== -1 || features.indexOf('location') === -1) {
236 popup.style.height = 'calc(100% - 70px)';
237
238 navigationButtonsDiv = document.createElement('div');
239 navigationButtonsDiv.className = 'inappbrowser-app-bar';
240 navigationButtonsDiv.onclick = function (e) {
241 e.cancelBubble = true;
242 };
243
244 navigationButtonsDivInner = document.createElement('div');
245 navigationButtonsDivInner.className = 'inappbrowser-app-bar-inner';
246 navigationButtonsDivInner.onclick = function (e) {
247 e.cancelBubble = true;
248 };
249
250 backButton = document.createElement('div');
251 backButton.innerText = 'back';
252 backButton.className = 'app-bar-action action-back';
253 backButton.addEventListener('click', function (e) {
254 if (popup.canGoBack) {
255 popup.goBack();
256 }
257 });
258
259 forwardButton = document.createElement('div');
260 forwardButton.innerText = 'forward';
261 forwardButton.className = 'app-bar-action action-forward';
262 forwardButton.addEventListener('click', function (e) {
263 if (popup.canGoForward) {
264 popup.goForward();
265 }
266 });
267
268 closeButton = document.createElement('div');
269 closeButton.innerText = 'close';
270 closeButton.className = 'app-bar-action action-close';
271 closeButton.addEventListener('click', closeHandler);
272
273 if (!isWebViewAvailable) {
274 // iframe navigation is not yet supported
275 backButton.setAttribute('disabled', 'true');
276 forwardButton.setAttribute('disabled', 'true');
277 }
278
279 navigationButtonsDivInner.appendChild(backButton);
280 navigationButtonsDivInner.appendChild(forwardButton);
281 navigationButtonsDivInner.appendChild(closeButton);
282 navigationButtonsDiv.appendChild(navigationButtonsDivInner);
283
284 browserWrap.appendChild(navigationButtonsDiv);
285 } else {
286 popup.style.height = '100%';
287 }
288
289 // start listening for navigation events
290 attachNavigationEvents(popup, navigationEventsCallback);
291
292 if (isWebViewAvailable) {
293 strUrl = strUrl.replace('ms-appx://', 'ms-appx-web://');
294 }
295 popup.src = strUrl;
296 }
297 });
298 },
299
300 injectScriptCode: function (win, fail, args) {
301 setImmediate(function () {
302 var code = args[0];
303 var hasCallback = args[1];
304
305 if (isWebViewAvailable && browserWrap && popup) {
306 var op = popup.invokeScriptAsync('eval', code);
307 op.oncomplete = function (e) {
308 if (hasCallback) {
309 // return null if event target is unavailable by some reason
310 var result = e && e.target ? [e.target.result] : [null];
311 win(result);
312 }
313 };
314 op.onerror = function () {};
315 op.start();
316 }
317 });
318 },
319
320 injectScriptFile: function (win, fail, args) {
321 setImmediate(function () {
322 var filePath = args[0];
323 var hasCallback = args[1];
324
325 if (filePath) {
326 filePath = urlutil.makeAbsolute(filePath);
327 }
328
329 if (isWebViewAvailable && browserWrap && popup) {
330 // CB-12364 getFileFromApplicationUriAsync does not support ms-appx-web
331 var uri = new Windows.Foundation.Uri(filePath.replace('ms-appx-web:', 'ms-appx:'));
332 Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).done(function (file) {
333 Windows.Storage.FileIO.readTextAsync(file).done(function (code) {
334 var op = popup.invokeScriptAsync('eval', code);
335 op.oncomplete = function (e) {
336 if (hasCallback) {
337 var result = [e.target.result];
338 win(result);
339 }
340 };
341 op.onerror = function () {};
342 op.start();
343 });
344 });
345 }
346 });
347 },
348
349 injectStyleCode: function (win, fail, args) {
350 setImmediate(function () {
351 var code = args[0];
352 var hasCallback = args[1];
353
354 if (isWebViewAvailable && browserWrap && popup) {
355 injectCSS(popup, code, hasCallback && win);
356 }
357 });
358 },
359
360 injectStyleFile: function (win, fail, args) {
361 setImmediate(function () {
362 var filePath = args[0];
363 var hasCallback = args[1];
364
365 filePath = filePath && urlutil.makeAbsolute(filePath);
366
367 if (isWebViewAvailable && browserWrap && popup) {
368 // CB-12364 getFileFromApplicationUriAsync does not support ms-appx-web
369 var uri = new Windows.Foundation.Uri(filePath.replace('ms-appx-web:', 'ms-appx:'));
370 Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
371 .then(function (file) {
372 return Windows.Storage.FileIO.readTextAsync(file);
373 })
374 .done(
375 function (code) {
376 injectCSS(popup, code, hasCallback && win);
377 },
378 function () {
379 // no-op, just catch an error
380 }
381 );
382 }
383 });
384 }
385};
386
387function injectCSS (webView, cssCode, callback) {
388 // This will automatically escape all thing that we need (quotes, slashes, etc.)
389 var escapedCode = JSON.stringify(cssCode);
390 var evalWrapper = "(function(d){var c=d.createElement('style');c.innerHTML=%s;d.head.appendChild(c);})(document)".replace(
391 '%s',
392 escapedCode
393 );
394
395 var op = webView.invokeScriptAsync('eval', evalWrapper);
396 op.oncomplete = function () {
397 if (callback) {
398 callback([]);
399 }
400 };
401 op.onerror = function () {};
402 op.start();
403}
404
405module.exports = IAB;
406
407require('cordova/exec/proxy').add('InAppBrowser', module.exports);