Repository: cordova-js Updated Branches: refs/heads/master 5f4848f64 -> 227d83a12
CB-8002 CB-7735 ios: Properly address iframe bridge failing when <iframe> is removed from DOM. Project: http://git-wip-us.apache.org/repos/asf/cordova-js/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-js/commit/227d83a1 Tree: http://git-wip-us.apache.org/repos/asf/cordova-js/tree/227d83a1 Diff: http://git-wip-us.apache.org/repos/asf/cordova-js/diff/227d83a1 Branch: refs/heads/master Commit: 227d83a122733fdf0300ffe75507a221bcef2b06 Parents: d638080 Author: Andrew Grieve <agri...@chromium.org> Authored: Tue Nov 25 13:39:47 2014 -0500 Committer: Andrew Grieve <agri...@chromium.org> Committed: Tue Nov 25 13:40:46 2014 -0500 ---------------------------------------------------------------------- src/ios/exec.js | 105 +++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-js/blob/227d83a1/src/ios/exec.js ---------------------------------------------------------------------- diff --git a/src/ios/exec.js b/src/ios/exec.js index 5c7cc98..9e2e2e4 100644 --- a/src/ios/exec.js +++ b/src/ios/exec.js @@ -33,14 +33,13 @@ var cordova = require('cordova'), // IFRAME_NAV is the fastest. // IFRAME_HASH could be made to enable synchronous bridge calls if we wanted this feature. jsToNativeModes = { - IFRAME_NAV: 0, - XHR_NO_PAYLOAD: 1, - XHR_WITH_PAYLOAD: 2, - XHR_OPTIONAL_PAYLOAD: 3, - IFRAME_HASH_NO_PAYLOAD: 4, - // Bundling the payload turns out to be slower. Probably since it has to be URI encoded / decoded. - IFRAME_HASH_WITH_PAYLOAD: 5, - WK_WEBVIEW_BINDING: 6 + IFRAME_NAV: 0, // Default. Uses a new iframe for each poke. + XHR_NO_PAYLOAD: 1, // About the same speed as IFRAME_NAV. Performance not about the same as IFRAME_NAV, but more variable. + XHR_WITH_PAYLOAD: 2, // Flakey, and not as performant + XHR_OPTIONAL_PAYLOAD: 3, // Flakey, and not as performant + IFRAME_HASH_NO_PAYLOAD: 4, // Not fully baked. A bit faster than IFRAME_NAV, but risks jank since poke happens synchronously. + IFRAME_HASH_WITH_PAYLOAD: 5, // Slower than no payload. Maybe since it has to be URI encoded / decoded. + WK_WEBVIEW_BINDING: 6 // Only way that works for WKWebView :) }, bridgeMode, execIframe, @@ -50,26 +49,8 @@ var cordova = require('cordova'), requestCount = 0, vcHeaderValue = null, commandQueue = [], // Contains pending JS->Native messages. - isInContextOfEvalJs = 0; - -function createExecIframe(src, unloadListener) { - var iframe = document.createElement("iframe"); - iframe.style.display = 'none'; - // Both the unload listener and the src must be set before adding the iframe - // to the document in order to avoid race conditions. Callbacks from native - // can happen within the appendChild() call! - iframe.onunload = unloadListener; - iframe.src = src; - document.body.appendChild(iframe); - return iframe; -} - -function createHashIframe() { - var ret = createExecIframe('about:blank'); - // Hash changes don't work on about:blank, so switch it to file:///. - ret.contentWindow.history.replaceState(null, null, 'file:///#'); - return ret; -} + isInContextOfEvalJs = 0, + failSafeTimerId = 0; function shouldBundleCommandJson() { if (bridgeMode === jsToNativeModes.XHR_WITH_PAYLOAD) { @@ -202,19 +183,23 @@ function iOSExec() { // Also, if there is already a command in the queue, then we've already // poked the native side, so there is no reason to do so again. if (!isInContextOfEvalJs && commandQueue.length == 1) { - switch (bridgeMode) { - case jsToNativeModes.XHR_NO_PAYLOAD: - case jsToNativeModes.XHR_WITH_PAYLOAD: - case jsToNativeModes.XHR_OPTIONAL_PAYLOAD: - pokeNativeViaXhr(); - break; - default: // iframe-based. - pokeNativeViaIframe(); - } + pokeNative(); } } } +function pokeNative() { + switch (bridgeMode) { + case jsToNativeModes.XHR_NO_PAYLOAD: + case jsToNativeModes.XHR_WITH_PAYLOAD: + case jsToNativeModes.XHR_OPTIONAL_PAYLOAD: + pokeNativeViaXhr(); + break; + default: // iframe-based. + pokeNativeViaIframe(); + } +} + function pokeNativeViaXhr() { // This prevents sending an XHR when there is already one being sent. // This should happen only in rare circumstances (refer to unit tests). @@ -238,11 +223,6 @@ function pokeNativeViaXhr() { execXhr.send(null); } -function onIframeUnload() { - execIframe = null; - setTimeout(pokeNativeViaIframe, 0); -} - function pokeNativeViaIframe() { // CB-5488 - Don't attempt to create iframe before document.body is available. if (!document.body) { @@ -251,10 +231,12 @@ function pokeNativeViaIframe() { } if (bridgeMode === jsToNativeModes.IFRAME_HASH_NO_PAYLOAD || bridgeMode === jsToNativeModes.IFRAME_HASH_WITH_PAYLOAD) { // TODO: This bridge mode doesn't properly support being removed from the DOM (CB-7735) - execHashIframe = execHashIframe || createHashIframe(); - // Check if they've removed it from the DOM, and put it back if so. - if (!execHashIframe.contentWindow) { - execHashIframe = createHashIframe(); + if (!execHashIframe) { + execHashIframe = document.createElement('iframe'); + execHashIframe.style.display = 'none'; + document.body.appendChild(execHashIframe); + // Hash changes don't work on about:blank, so switch it to file:///. + execHashIframe.contentWindow.history.replaceState(null, null, 'file:///#'); } // The delegate method is called only when the hash changes, so toggle it back and forth. hashToggle = hashToggle ^ 3; @@ -266,13 +248,25 @@ function pokeNativeViaIframe() { } else { // Check if they've removed it from the DOM, and put it back if so. if (execIframe && execIframe.contentWindow) { - // Listen for unload, since it can happen (CB-7735) that the iframe gets - // removed from the DOM before it gets a chance to poke the native side. - execIframe.contentWindow.onunload = onIframeUnload; - execIframe.src = 'gap://ready'; + execIframe.contentWindow.location = 'gap://ready'; } else { - execIframe = createExecIframe('gap://ready', onIframeUnload); + execIframe = document.createElement('iframe'); + execIframe.style.display = 'none'; + execIframe.src = 'gap://ready'; + document.body.appendChild(execIframe); } + // Use a timer to protect against iframe being unloaded during the poke (CB-7735). + // This makes the bridge ~ 7% slower, but works around the poke getting lost + // when the iframe is removed from the DOM. + // An onunload listener could be used in the case where the iframe has just been + // created, but since unload events fire only once, it doesn't work in the normal + // case of iframe reuse (where unload will have already fired due to the attempted + // navigation of the page). + failSafeTimerId = setTimeout(function() { + if (commandQueue.length) { + pokeNative(); + } + }, 50); // Making this > 0 improves performance (marginally) in the normal case (where it doesn't fire). } } @@ -283,7 +277,9 @@ iOSExec.setJsToNativeBridgeMode = function(mode) { // can trigger browser bugs. // https://issues.apache.org/jira/browse/CB-593 if (execIframe) { - execIframe.parentNode.removeChild(execIframe); + if (execIframe.parentNode) { + execIframe.parentNode.removeChild(execIframe); + } execIframe = null; } bridgeMode = mode; @@ -291,8 +287,9 @@ iOSExec.setJsToNativeBridgeMode = function(mode) { iOSExec.nativeFetchMessages = function() { // Stop listing for window detatch once native side confirms poke. - if (execIframe && execIframe.contentWindow) { - execIframe.contentWindow.onunload = null; + if (failSafeTimerId) { + clearTimeout(failSafeTimerId); + failSafeTimerId = 0; } // Each entry in commandQueue is a JSON string already. if (!commandQueue.length) { --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org
