Reviewers: MarkM,

Description:
Derived from James Keane's patches
<https://github.com/google/caja/pull/1965> -- thanks to him for spotting
the problem. Issues in our tests and in onreadystatechange were spotted
in testing and additionally fixed.

Allegedly fixes <https://github.com/google/caja/issues/1878>.

* Pass the correct number of arguments to native XMLHttpRequest, rather
  than one too many. Among other things, this caused us to incorrectly
  default to synchronous XHR.
* Fixed onreadystatechange firing twice after synchronous XHR on Chrome
  due to using an unsound browser test rather than a feature test to
  decide whether to apply a workaround.
* Fixed lack of argument coercion for some uses of the async parameter.
  (Should have no observable effects, so this is just on general
  code structure principles.)

Supporting changes:
* Fixed test-cajajs-invocation testBuilderApiNetNoFetch accidentally
  depending on that synchronous XHR.
* Increased timeout for preliminary-meta from 10 to 30 seconds as
  I have experienced timeouts on that test only frequently, and it is
  inherently slow compared to others.

Please review this at https://codereview.appspot.com/251030043/

Affected files (+40, -16 lines):
  M src/com/google/caja/plugin/domado.js
  M tests/com/google/caja/plugin/MainBrowserTest.java
  M tests/com/google/caja/plugin/test-cajajs-invocation.js
  M tests/com/google/caja/plugin/test-domado-events-guest.html


Index: src/com/google/caja/plugin/domado.js
diff --git a/src/com/google/caja/plugin/domado.js b/src/com/google/caja/plugin/domado.js index 25518c1abb8e88ef50a8ea96b2cb5d556a28f112..67ef75dc64347b82402f1be78fdf51b64aba722a 100644
--- a/src/com/google/caja/plugin/domado.js
+++ b/src/com/google/caja/plugin/domado.js
@@ -951,6 +951,7 @@ var Domado = (function() {

         privates.async = undefined;
         privates.handler = undefined;
+        privates.nativeCompleteEventSeen = false;

         Object.preventExtensions(privates);
       });
@@ -964,6 +965,11 @@ var Domado = (function() {
           // to DOM events.
           var self = this;
           privates.feral.onreadystatechange = function(event) {
+            if (privates.feral.readyState === 4) {
+ // Detect whether this event was fired. See open() for how this
+              // is used.
+              privates.nativeCompleteEventSeen = true;
+            }
             var evt = { target: self };
             try {
               return handler.call(void 0, evt);
@@ -1020,6 +1026,13 @@ var Domado = (function() {
           privates, method, URL, opt_async, opt_userName, opt_password) {
         method = String(method);
         URL = URI.utils.resolve(getBaseURL(), String(URL));
+        // Sanitizing these optionals up front; but note that we switch on
+        // arguments.length below, so this does not mean that opt_async
+        // defaults to false.
+        opt_async = Boolean(opt_async);
+        opt_userName = String(opt_userName);
+        opt_password = String(opt_password);
+
// The XHR interface does not tell us the MIME type in advance, so we
         // must assume the broadest possible.
         var safeUri = uriRewrite(
@@ -1036,28 +1049,27 @@ var Domado = (function() {
         if ('string' !== typeof safeUri) {
           throw 'URI violates security policy';
         }
-        switch (arguments.length) {
+        switch (arguments.length - 1) {
         case 2:
           privates.async = true;
           privates.feral.open(method, safeUri);
           break;
         case 3:
           privates.async = opt_async;
-          privates.feral.open(method, safeUri, Boolean(opt_async));
+          privates.feral.open(method, safeUri, opt_async);
           break;
         case 4:
           privates.async = opt_async;
           privates.feral.open(
-              method, safeUri, Boolean(opt_async), String(opt_userName));
+              method, safeUri, opt_async, opt_userName);
           break;
         case 5:
           privates.async = opt_async;
           privates.feral.open(
-              method, safeUri, Boolean(opt_async), String(opt_userName),
-              String(opt_password));
+              method, safeUri, opt_async, opt_userName, opt_password);
           break;
         default:
-          throw 'XMLHttpRequest cannot accept ' + arguments.length +
+          throw 'XMLHttpRequest cannot accept ' + (arguments.length - 1) +
               ' arguments';
           break;
         }
@@ -1066,6 +1078,8 @@ var Domado = (function() {
         privates.feral.setRequestHeader(String(label), String(value));
       }),
       send: Props.ampMethod(function(privates, opt_data) {
+        privates.nativeCompleteEventSeen = false;
+
         if (arguments.length === 0) {
// TODO(ihab.awad): send()-ing an empty string because send() with no
           // args does not work on FF3, others?
@@ -1080,12 +1094,10 @@ var Domado = (function() {
         // Firefox does not call the 'onreadystatechange' handler in
         // the case of a synchronous XHR. We simulate this behavior by
         // calling the handler explicitly.
-        if (privates.feral.overrideMimeType) {
-          // This is Firefox
-          if (!privates.async && privates.handler) {
-            var evt = { target: this };
-            privates.handler.call(void 0, evt);
-          }
+        if (!privates.async && !privates.nativeCompleteEventSeen &&
+            privates.handler) {
+          var evt = { target: this };
+          privates.handler.call(void 0, evt);
         }
       }),
       abort: Props.ampMethod(function(privates) {
Index: tests/com/google/caja/plugin/MainBrowserTest.java
diff --git a/tests/com/google/caja/plugin/MainBrowserTest.java b/tests/com/google/caja/plugin/MainBrowserTest.java index 0426fac50865e775fc5e4f1b6c2057543245b80b..6379cb58b343abafda2a50a27a8dd3294c0e6bcf 100644
--- a/tests/com/google/caja/plugin/MainBrowserTest.java
+++ b/tests/com/google/caja/plugin/MainBrowserTest.java
@@ -36,6 +36,8 @@ public class MainBrowserTest extends CatalogTestCase {
   protected int waitForCompletionTimeout() {
     if (entry.getLabel().startsWith("guest-scan-")) {
       return 800 * 1000;  // milliseconds
+    } else if (entry.getLabel().startsWith("preliminary-meta-")) {
+      return 30 * 1000;
     } else {
       return super.waitForCompletionTimeout();
     }
Index: tests/com/google/caja/plugin/test-cajajs-invocation.js
diff --git a/tests/com/google/caja/plugin/test-cajajs-invocation.js b/tests/com/google/caja/plugin/test-cajajs-invocation.js index 9114f0962814300a9095cecd3b65f704f532cc36..d3f1b5b07180c652ddbfa4ad180b9495390e52f1 100644
--- a/tests/com/google/caja/plugin/test-cajajs-invocation.js
+++ b/tests/com/google/caja/plugin/test-cajajs-invocation.js
@@ -340,10 +340,10 @@
       'var xhr = new XMLHttpRequest();' +
       'try {' +
       '  xhr.open("GET", "' + location.protocol + '//' + location.host +
-          '/nonexistent");' +
+          '/nonexistent", false);' +  // Note sync XHR so test can be sync.
       '} catch (e) { r("" + e); }' +
       'xhr.onreadystatechange = function() {' +
-      '  r(xhr.readyState + xhr.responseText);' +
+      '  r(xhr.readyState + " " + xhr.status);' +
       '};' +
       'xhr.send();' +
       '</script>';
@@ -388,7 +388,7 @@
           assertStringContains('http://fake2.url/foo', div.innerHTML);
           // TODO(kpreid): verify script did not load, as expected
           // XHR is independent of fetcher
-          assertEquals('init,4<html>\n', String(xhrRes).substr(0, 13));
+          assertEquals('init,4 404', String(xhrRes).substr(0, 13));
           jsunitPass('testBuilderApiNetNoFetch');
         }));
     }));
Index: tests/com/google/caja/plugin/test-domado-events-guest.html
diff --git a/tests/com/google/caja/plugin/test-domado-events-guest.html b/tests/com/google/caja/plugin/test-domado-events-guest.html index b7a7df61c5d3ae2427ab5ad0f5f1422c649ca851..3a35a001acba99c6607f6826047fef3494625ec8 100644
--- a/tests/com/google/caja/plugin/test-domado-events-guest.html
+++ b/tests/com/google/caja/plugin/test-domado-events-guest.html
@@ -756,17 +756,27 @@ TODO(felix8a): fix javascript url handling
     assertEquals('The quick brown fox', xhr.responseText);

     // Check that a synchronous XHR invokes its callback in ready state 4
+    var statesSeen = [];
     var responseText;
     xhr = new window.XMLHttpRequest();
     // Note we specify test id since we get preempted by xhr.send()
     xhr.onreadystatechange = jsunitCallback(
         function onreadystatechangecb() {
-          if (xhr.readyState == 4) { responseText = xhr.responseText; }
+          statesSeen.push(xhr.readyState);
+          if (xhr.readyState == 4) {
+            responseText = xhr.responseText;
+          }
         },
         'testXhrSync');
     xhr.open('GET', './xhrTest.txt', false);
+    // May or may not get a state 1 callback.
+    assertContains('' + statesSeen, ['', '1']);
+    statesSeen = [];
+
     console.log('About to do sync xhr.send() - may get preempted');
     xhr.send();
+
+    assertEquals('4', '' + statesSeen);
     assertEquals('The quick brown fox', responseText);

     pass('testXhrSync');


--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to