diff options
-rw-r--r-- | chrome_frame/CFInstall.js | 232 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_unittests.cc | 25 | ||||
-rw-r--r-- | chrome_frame/test/data/CFInstall_basic.html | 41 | ||||
-rw-r--r-- | chrome_frame/test/data/CFInstall_dismiss.html | 53 | ||||
-rw-r--r-- | chrome_frame/test/data/CFInstall_overlay.html | 67 | ||||
-rw-r--r-- | chrome_frame/test/data/CFInstall_place.html | 74 | ||||
-rw-r--r-- | chrome_frame/test/data/chrome_frame_tester_helpers.js | 6 | ||||
-rw-r--r-- | chrome_frame/test/http_server.cc | 16 |
8 files changed, 460 insertions, 54 deletions
diff --git a/chrome_frame/CFInstall.js b/chrome_frame/CFInstall.js index 915f958..b7315bd 100644 --- a/chrome_frame/CFInstall.js +++ b/chrome_frame/CFInstall.js @@ -14,12 +14,12 @@ return; } - /** + /** * returns an item based on DOM ID. Optionally a document may be provided to * specify the scope to search in. If a node is passed, it's returned as-is. * @param {string|Node} id The ID of the node to be located or a node * @param {Node} doc Optional A document to search for id. - * @return {Node} + * @return {Node} */ var byId = function(id, doc) { return (typeof id == 'string') ? (doc || document).getElementById(id) : id; @@ -28,86 +28,140 @@ ///////////////////////////////////////////////////////////////////////////// // Plugin Detection ///////////////////////////////////////////////////////////////////////////// - - var cachedAvailable; - /** + /** * Checks to find out if ChromeFrame is available as a plugin - * @return {Boolean} + * @return {Boolean} */ var isAvailable = function() { - if (typeof cachedAvailable != 'undefined') { - return cachedAvailable; + // For testing purposes. + if (scope.CFInstall._force) { + return scope.CFInstall._forceValue; } - cachedAvailable = false; - // Look for CF in the User Agent before trying more expensive checks var ua = navigator.userAgent.toLowerCase(); - if (ua.indexOf("chromeframe") >= 0 || ua.indexOf("x-clock") >= 0) { - cachedAvailable = true; - return cachedAvailable; + if (ua.indexOf("chromeframe") >= 0) { + return true; } if (typeof window['ActiveXObject'] != 'undefined') { try { var obj = new ActiveXObject('ChromeTab.ChromeFrame'); if (obj) { - cachedAvailable = true; + return true; } } catch(e) { // squelch } } - return cachedAvailable; + return false; }; - - /** @type {boolean} */ - var cfStyleTagInjected = false; - - /** - * Creates a style sheet in the document which provides default styling for - * ChromeFrame instances. Successive calls should have no additive effect. + /** + * Creates a style sheet in the document containing the passed rules. */ - var injectCFStyleTag = function() { - if (cfStyleTagInjected) { - // Once and only once - return; - } + var injectStyleSheet = function(rules) { try { - var rule = '.chromeFrameInstallDefaultStyle {' + - 'width: 500px;' + - 'height: 400px;' + - 'padding: 0;' + - 'border: 1px solid #0028c4;' + - 'margin: 0;' + - '}'; var ss = document.createElement('style'); ss.setAttribute('type', 'text/css'); if (ss.styleSheet) { - ss.styleSheet.cssText = rule; + ss.styleSheet.cssText = rules; } else { - ss.appendChild(document.createTextNode(rule)); + ss.appendChild(document.createTextNode(rules)); } var h = document.getElementsByTagName('head')[0]; var firstChild = h.firstChild; h.insertBefore(ss, firstChild); - cfStyleTagInjected = true; } catch (e) { // squelch } }; + /** @type {boolean} */ + var cfStyleTagInjected = false; + /** @type {boolean} */ + var cfHiddenInjected = false; - /** + /** + * Injects style rules into the document to handle formatting of Chrome Frame + * prompt. Multiple calls have no effect. + */ + var injectCFStyleTag = function() { + if (cfStyleTagInjected) { + // Once and only once + return; + } + var rules = '.chromeFrameInstallDefaultStyle {' + + 'width: 800px;' + + 'height: 600px;' + + 'position: absolute;' + + 'left: 50%;' + + 'top: 50%;' + + 'margin-left: -400px;' + + 'margin-top: -300px;' + + '}' + + '.chromeFrameOverlayContent {' + + 'position: absolute;' + + 'margin-left: -400px;' + + 'margin-top: -300px;' + + 'left: 50%;' + + 'top: 50%;' + + 'border: 1px solid #93B4D9;' + + 'background-color: white;' + + '}' + + '.chromeFrameOverlayContent iframe {' + + 'width: 800px;' + + 'height: 600px;' + + 'border: none;' + + '}' + + '.chromeFrameOverlayCloseBar {' + + 'height: 1em;' + + 'text-align: right;' + + 'background-color: #CADEF4;' + + '}' + + '.chromeFrameOverlayUnderlay {' + + 'position: absolute;' + + 'width: 100%;' + + 'height: 100%;' + + 'background-color: white;' + + 'opacity: 0.5;' + + '-moz-opacity: 0.5;' + + '-webkit-opacity: 0.5;' + + '-ms-filter: ' + + '"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";' + + 'filter: alpha(opacity=50);' + + '}'; + injectStyleSheet(rules); + cfStyleTagInjected = true; + }; + + /** + * Injects style rules to hide the overlay version of the GCF prompt. + * Multiple calls have no effect. + */ + var closeOverlay = function() { + // IE has a limit to the # of <style> tags allowed, so we avoid + // tempting the fates. + if (cfHiddenInjected) { + return; + } + var rules = '.chromeFrameOverlayContent { display: none; }' + + '.chromeFrameOverlayUnderlay { display: none; }'; + injectStyleSheet(rules); + // Hide the dialog for a year (or until cookies are deleted). + var age = 365 * 24 * 60 * 60 * 1000; + document.cookie = "disableGCFCheck=1;path=/;max-age="+age; + cfHiddenInjected = true; + }; + + /** * Plucks properties from the passed arguments and sets them on the passed * DOM node * @param {Node} node The node to set properties on * @param {Object} args A map of user-specified properties to set */ var setProperties = function(node, args) { - injectCFStyleTag(); var srcNode = byId(args['node']); @@ -118,7 +172,7 @@ node.style.cssText = ' ' + cssText; var classText = args['className'] || ''; - node.className = 'chromeFrameInstallDefaultStyle ' + classText; + node.className = classText; // default if the browser doesn't so we don't show sad-tab var src = args['src'] || 'about:blank'; @@ -130,20 +184,70 @@ } }; - /** + /** * Creates an iframe. * @param {Object} args A bag of configuration properties, including values * like 'node', 'cssText', 'className', 'id', 'src', etc. - * @return {Node} + * @return {Node} */ var makeIframe = function(args) { var el = document.createElement('iframe'); + el.setAttribute('frameborder', '0'); + el.setAttribute('border', '0'); setProperties(el, args); return el; }; + /** + * Adds an unadorned iframe into the page, taking arguments to customize it. + * @param {Object} args A map of user-specified properties to set + */ + var makeInlinePrompt = function(args) { + args.className = 'chromeFrameInstallDefaultStyle ' + + (args.className || ''); + var ifr = makeIframe(args); + // TODO(slightlyoff): handle placement more elegantly! + if (!ifr.parentNode) { + var firstChild = document.body.firstChild; + document.body.insertBefore(ifr, firstChild); + } + }; + + /** + * Adds a styled, closable iframe into the page with a background that + * emulates a modal dialog. + * @param {Object} args A map of user-specified properties to set + */ + var makeOverlayPrompt = function(args) { + if (byId('chromeFrameOverlayContent')) { + return; // Was previously created. Bail. + } + + var n = document.createElement('span'); + n.innerHTML = '<div class="chromeFrameOverlayUnderlay"></div>' + + '<table class="chromeFrameOverlayContent"' + + 'id="chromeFrameOverlayContent"' + + 'cellpadding="0" cellspacing="0">' + + '<tr class="chromeFrameOverlayCloseBar">' + + '<td>' + + // TODO(slightlyoff): i18n + '<button id="chromeFrameCloseButton">close</button>' + + '</td>' + + '</tr>' + + '<tr>' + + '<td id="chromeFrameIframeHolder"></td>' + + '</tr>' + + '</table>'; + + document.body.appendChild(n); + var ifr = makeIframe(args); + byId('chromeFrameIframeHolder').appendChild(ifr); + byId('chromeFrameCloseButton').onclick = closeOverlay; + }; + var CFInstall = {}; - /** + + /** * Checks to see if Chrome Frame is available, if not, prompts the user to * install. Once installation is begun, a background timer starts, * checkinging for a successful install every 2 seconds. Upon detection of @@ -158,17 +262,40 @@ CFInstall.check = function(args) { args = args || {}; - // We currently only support CF in IE + // We currently only support CF in IE // TODO(slightlyoff): Update this should we support other browsers! - var ieRe = /MSIE (\S+)/; - if (!ieRe.test(navigator.userAgent)) { + var ua = navigator.userAgent; + var ieRe = /MSIE \S+; Windows NT/; + var bail = false; + if (ieRe.test(ua)) { + // We also only support Win2003/XPSP2 or better. See: + // http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx + if (parseFloat(ua.split(ieRe)[1]) < 6 && + ua.indexOf('SV1') >= 0) { + bail = true; + } + } else { + bail = true; + } + if (bail) { return; } + // Inject the default styles + injectCFStyleTag(); + if (document.cookie.indexOf("disableGCFCheck=1") >=0) { + // If we're supposed to hide the overlay prompt, add the rules to do it. + closeOverlay(); + } + + // When loaded in an alternate protocol (e.g., "file:"), still call out to + // the right location. + var currentProtocol = document.location.protocol; + var protocol = (currentProtocol == 'https:') ? 'https:' : 'http:'; // TODO(slightlyoff): Update this URL when a mini-installer page is // available. - var installUrl = '//www.google.com/chromeframe'; + var installUrl = protocol + '//www.google.com/chromeframe'; if (!isAvailable()) { if (args.onmissing) { args.onmissing(); @@ -180,12 +307,9 @@ if (!preventPrompt) { if (mode == 'inline') { - var ifr = makeIframe(args); - // TODO(slightlyoff): handle placement more elegantly! - if (!ifr.parentNode) { - var firstChild = document.body.firstChild; - document.body.insertBefore(ifr, firstChild); - } + makeInlinePrompt(args); + } else if (mode == 'overlay') { + makeOverlayPrompt(args); } else { window.open(args.src); } @@ -213,6 +337,8 @@ } }; + CFInstall._force = false; + CFInstall._forceValue = false; CFInstall.isAvailable = isAvailable; // expose CFInstall to the external scope. We've already checked to make diff --git a/chrome_frame/test/chrome_frame_unittests.cc b/chrome_frame/test/chrome_frame_unittests.cc index 1845101..6472310 100644 --- a/chrome_frame/test/chrome_frame_unittests.cc +++ b/chrome_frame/test/chrome_frame_unittests.cc @@ -564,6 +564,31 @@ TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceDefaultCtor) { SimpleBrowserTest(FIREFOX, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor"); } + +const wchar_t kCFInstallBasicTestPage[] = L"files/CFInstall_basic.html"; + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallBasic) { + SimpleBrowserTest(IE, kCFInstallBasicTestPage, L"CFInstallBasic"); +} + +const wchar_t kCFInstallPlaceTestPage[] = L"files/CFInstall_place.html"; + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallPlace) { + SimpleBrowserTest(IE, kCFInstallPlaceTestPage, L"CFInstallPlace"); +} + +const wchar_t kCFInstallOverlayTestPage[] = L"files/CFInstall_overlay.html"; + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallOverlay) { + SimpleBrowserTest(IE, kCFInstallOverlayTestPage, L"CFInstallOverlay"); +} + +const wchar_t kCFInstallDismissTestPage[] = L"files/CFInstall_dismiss.html"; + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallDismiss) { + SimpleBrowserTest(IE, kCFInstallDismissTestPage, L"CFInstallDismiss"); +} + const wchar_t kInitializeHiddenPage[] = L"files/initialize_hidden.html"; TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_InitializeHidden) { diff --git a/chrome_frame/test/data/CFInstall_basic.html b/chrome_frame/test/data/CFInstall_basic.html new file mode 100644 index 0000000..bbf7006 --- /dev/null +++ b/chrome_frame/test/data/CFInstall_basic.html @@ -0,0 +1,41 @@ +<html> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstall.js"></script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <script type="text/javascript"> + var testName = 'CFInstallBasic'; + (function(){ + try{ + // Testing over-rides for GCF detection code. + CFInstall._force = true; + CFInstall._forceValue = false; + + CFInstall.check({ + // TODO(slightlyoff): popup window tests? + mode: 'inline' + }); + + if (document.body.firstChild.tagName != 'IFRAME') { + onFailure(testName, 1, 'prompt placed incorrectly'); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + 'CFInstall basic test failed with error: '+e); + } + })(); + </script> + <p>Tests CFInstall basic prompt functionality</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstall_dismiss.html b/chrome_frame/test/data/CFInstall_dismiss.html new file mode 100644 index 0000000..35e2f80 --- /dev/null +++ b/chrome_frame/test/data/CFInstall_dismiss.html @@ -0,0 +1,53 @@ +<html> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstall.js"></script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <script type="text/javascript"> + var testName = 'CFInstallDismiss'; + (function(){ + try{ + // Testing over-rides for GCF detection code. + CFInstall._force = true; + CFInstall._forceValue = false; + + // Clobber prompt supression cookie if set. + document.cookie = 'disableGCFCheck=0;path=/'; + + CFInstall.check({ + id: 'prompt', + mode: 'overlay' + }); + + var p = byId('prompt'); + + if (!p) { + onFailure(testName, 1, 'prompt not created with correct ID'); + return; + } + + byId('chromeFrameCloseButton').click(); + + if (document.cookie.indexOf('disableGCFCheck=1') == -1) { + onFailure(testName, 1, 'dismiss cookie not set'); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + 'CFInstall overlay prompt failed with error: '+e); + } + })(); + </script> + <p>Tests CFInstall overlay prompt</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstall_overlay.html b/chrome_frame/test/data/CFInstall_overlay.html new file mode 100644 index 0000000..1d17422 --- /dev/null +++ b/chrome_frame/test/data/CFInstall_overlay.html @@ -0,0 +1,67 @@ +<html> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstall.js"></script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <script type="text/javascript"> + var testName = 'CFInstallOverlay'; + (function(){ + try{ + // Testing over-rides for GCF detection code. + CFInstall._force = true; + CFInstall._forceValue = false; + + // Clobber prompt supression cookie if set. + document.cookie = 'disableGCFCheck=0;path=/'; + + CFInstall.check({ + id: 'prompt', + mode: 'overlay' + }); + + var p = byId('prompt'); + + if (!p) { + onFailure(testName, 1, 'prompt not created with correct ID'); + return; + } + + // Make sure the prompt is parented in the display table. + if (p.parentNode != byId('chromeFrameIframeHolder')) { + onFailure(testName, 1, 'prompt not parented correctly'); + return; + } + + var lc = document.body.lastChild; + + if (lc.nodeType != 1 || !lc.firstChild && + lc.firstChild != byId('chromeFrameOverlayUnderlay')) { + onFailure(testName, 1, 'underlay placed incorrectly'); + return; + } + + if (lc.nodeType != 1 || !lc.firstChild && + lc.firstChild.nextSibling != byId('chromeFrameOverlayContent')) { + onFailure(testName, 1, 'prompt placed incorrectly'); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + 'CFInstall overlay prompt failed with error: '+e); + } + })(); + </script> + <p>Tests CFInstall overlay prompt</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstall_place.html b/chrome_frame/test/data/CFInstall_place.html new file mode 100644 index 0000000..a8e543b --- /dev/null +++ b/chrome_frame/test/data/CFInstall_place.html @@ -0,0 +1,74 @@ +<html> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstall.js"></script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = 'CFInstallPlace'; + (function(){ + try{ + // Testing over-rides for GCF detection code. + CFInstall._force = true; + CFInstall._forceValue = false; + + CFInstall.check({ + node: 'toBeReplaced', + id: 'prompt', + cssText: 'width: 400px; height: 300px;', + mode: 'inline' + }); + + var p = byId('prompt'); + + if (!p) { + onFailure(testName, 1, 'prompt not created with correct ID'); + return; + } + + var fc = document.body.firstChild; + + if (fc.nodeType == 1 && fc.tagName == 'IFRAME') { + onFailure(testName, 1, 'prompt placed incorrectly'); + return; + } + + if (p.tagName != 'IFRAME') { + onFailure(testName, 1, 'prompt has wrong tag type'); + return; + } + + // Ensure that it got dropped into the right bit of the DOM + if (byId('prev').nextSibling != p) { + onFailure(testName, 1, 'prompt placed incorrectly'); + return; + } + + // Make sure that the geometry took. + if (p.style.width != '400px') { + onFailure(testName, 1, 'prompt sized incorrectly'); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + 'CFInstall placement failed with error: '+e); + } + })(); + </script> + <p>Tests CFInstall prompt placement</p> + </body> +</html> diff --git a/chrome_frame/test/data/chrome_frame_tester_helpers.js b/chrome_frame/test/data/chrome_frame_tester_helpers.js index e18ab87..e7f2a83 100644 --- a/chrome_frame/test/data/chrome_frame_tester_helpers.js +++ b/chrome_frame/test/data/chrome_frame_tester_helpers.js @@ -9,6 +9,10 @@ function onFailure(name, id, status) { onFinished(name, id, status); } +function byId(id) { + return document.getElementById(id); +} + function getXHRObject(){ var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; @@ -91,7 +95,7 @@ function onFinished(name, id, result) { } function appendStatus(message) { - var statusPanel = document.getElementById("statusPanel"); + var statusPanel = byId("statusPanel"); if (statusPanel) { statusPanel.innerHTML += '<BR>' + message; } diff --git a/chrome_frame/test/http_server.cc b/chrome_frame/test/http_server.cc index f2cc333..d8223f2 100644 --- a/chrome_frame/test/http_server.cc +++ b/chrome_frame/test/http_server.cc @@ -20,6 +20,12 @@ void ChromeFrameHTTPServer::SetUp() { FILE_PATH_LITERAL("test")).Append( FILE_PATH_LITERAL("data")).Append( FILE_PATH_LITERAL("CFInstance.js"))); // NOLINT + + file_util::CopyFile(cf_source_path.Append(FILE_PATH_LITERAL("CFInstall.js")), + cf_source_path.Append( + FILE_PATH_LITERAL("test")).Append( + FILE_PATH_LITERAL("data")).Append( + FILE_PATH_LITERAL("CFInstall.js"))); // NOLINT } void ChromeFrameHTTPServer::TearDown() { @@ -37,6 +43,16 @@ void ChromeFrameHTTPServer::TearDown() { .Append(FILE_PATH_LITERAL("CFInstance.js")); file_util::Delete(cfi_path, false); + + cfi_path.empty(); + PathService::Get(base::DIR_SOURCE_ROOT, &cfi_path); + cfi_path = cfi_path + .Append(FILE_PATH_LITERAL("chrome_frame")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("CFInstall.js")); + + file_util::Delete(cfi_path, false); } bool ChromeFrameHTTPServer::WaitToFinish(int milliseconds) { |