content/base/test/test_object.html

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/base/test/test_object.html	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,515 @@
     1.4 +<!DOCTYPE html>
     1.5 +<html>
     1.6 +  <head>
     1.7 +    <title>Plugin instantiation</title>
     1.8 +    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     1.9 +    <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
    1.10 +    <meta charset="utf-8">
    1.11 +  <body onload="onLoad()">
    1.12 +    <script class="testbody" type="text/javascript;version=1.8">
    1.13 +
    1.14 +      "use strict";
    1.15 +      SimpleTest.waitForExplicitFinish();
    1.16 +
    1.17 +      var pluginHost = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
    1.18 +                        .getService(SpecialPowers.Ci.nsIPluginHost);
    1.19 +      var pluginTags = pluginHost.getPluginTags();
    1.20 +      for (var tag of pluginTags) {
    1.21 +        if (tag.name == "Test Plug-in") {
    1.22 +          tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED;;
    1.23 +        }
    1.24 +      }
    1.25 +
    1.26 +      // This can go away once embed also is on WebIDL
    1.27 +      let OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
    1.28 +
    1.29 +      // Use string modes in this test to make the test easier to read/debug.
    1.30 +      // nsIObjectLoadingContent refers to this as "type", but I am using "mode"
    1.31 +      // in the test to avoid confusing with content-type.
    1.32 +      let prettyModes = {};
    1.33 +      prettyModes[OBJLC.TYPE_LOADING] = "loading";
    1.34 +      prettyModes[OBJLC.TYPE_IMAGE] = "image";
    1.35 +      prettyModes[OBJLC.TYPE_PLUGIN] = "plugin";
    1.36 +      prettyModes[OBJLC.TYPE_DOCUMENT] = "document";
    1.37 +      prettyModes[OBJLC.TYPE_NULL] = "none";
    1.38 +
    1.39 +      let body = document.body;
    1.40 +      // A single-pixel white png
    1.41 +      let testPNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoIFiETNqbNRQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAACklEQVQIHWP4DwABAQEANl9ngAAAAABJRU5ErkJggg==';
    1.42 +      // An empty, but valid, SVG
    1.43 +      let testSVG = 'data:image/svg+xml,<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"></svg>';
    1.44 +      // executeSoon wrapper to count pending callbacks
    1.45 +      let pendingCalls = 0;
    1.46 +      let afterPendingCalls = false;
    1.47 +
    1.48 +      function runWhenDone(func) {
    1.49 +        if (!pendingCalls)
    1.50 +          func();
    1.51 +        else
    1.52 +          afterPendingCalls = func;
    1.53 +      }
    1.54 +      function runSoon(func) {
    1.55 +        pendingCalls++;
    1.56 +        SimpleTest.executeSoon(function() {
    1.57 +          func();
    1.58 +          if (--pendingCalls < 1 && afterPendingCalls)
    1.59 +            afterPendingCalls();
    1.60 +        });
    1.61 +      }
    1.62 +      function src(obj, state, uri) {
    1.63 +        // If we have a running plugin, src changing should always throw it out,
    1.64 +        // even if it causes us to load the same plugin again.
    1.65 +        if (uri && runningPlugin(obj, state)) {
    1.66 +          if (!state.oldPlugins)
    1.67 +            state.oldPlugins = [];
    1.68 +          try {
    1.69 +            state.oldPlugins.push(obj.getObjectValue());
    1.70 +          } catch (e) {
    1.71 +            ok(false, "Running plugin but cannot call getObjectValue?");
    1.72 +          }
    1.73 +        }
    1.74 +
    1.75 +        var srcattr;
    1.76 +        if (state.tagName == "object")
    1.77 +          srcattr = "data";
    1.78 +        else if (state.tagName == "embed")
    1.79 +          srcattr = "src";
    1.80 +        else
    1.81 +          ok(false, "Internal test fail: Why are we setting the src of an applet");
    1.82 +
    1.83 +        // Plugins should always go away immediately on src/data change
    1.84 +        state.initialPlugin = false;
    1.85 +        if (uri === null) {
    1.86 +          removeAttr(obj, srcattr);
    1.87 +          // TODO Bug 767631 - we don't trigger loadObject on UnsetAttr :(
    1.88 +          forceReload(obj, state);
    1.89 +        } else {
    1.90 +          setAttr(obj, srcattr, uri);
    1.91 +        }
    1.92 +      }
    1.93 +      // We have to be careful not to reach past the nsObjectLoadingContent
    1.94 +      // prototype to touch generic element attributes, as this will try to
    1.95 +      // spawn the plugin, breaking our ability to test for that.
    1.96 +      function getAttr(obj, attr) {
    1.97 +        return document.body.constructor.prototype.getAttribute.call(obj, attr);
    1.98 +      }
    1.99 +      function setAttr(obj, attr, val) {
   1.100 +        return document.body.constructor.prototype.setAttribute.call(obj, attr, val);
   1.101 +      }
   1.102 +      function hasAttr(obj, attr) {
   1.103 +        return document.body.constructor.prototype.hasAttribute.call(obj, attr);
   1.104 +      }
   1.105 +      function removeAttr(obj, attr) {
   1.106 +        return document.body.constructor.prototype.removeAttribute.call(obj, attr);
   1.107 +      }
   1.108 +      function setDisplayed(obj, display) {
   1.109 +        if (display)
   1.110 +          removeAttr(obj, 'style');
   1.111 +        else
   1.112 +          setAttr(obj, 'style', "display: none;");
   1.113 +      }
   1.114 +      function displayed(obj) {
   1.115 +        // Hacky, but that's all we use style for.
   1.116 +        return !hasAttr(obj, 'style');
   1.117 +      }
   1.118 +      function actualType(obj, state) {
   1.119 +        return state.getActualType.call(obj);
   1.120 +      }
   1.121 +      function getMode(obj, state) {
   1.122 +        return prettyModes[state.getDisplayedType.call(obj)];
   1.123 +      }
   1.124 +      function runningPlugin(obj, state) {
   1.125 +        return state.getHasRunningPlugin.call(obj);
   1.126 +      }
   1.127 +
   1.128 +      // TODO this is hacky and might hide some failures, but is needed until
   1.129 +      // Bug 767635 lands -- which itself will probably mean tweaking this test.
   1.130 +      function forceReload(obj, state) {
   1.131 +        let attr;
   1.132 +        if (state.tagName == "object")
   1.133 +          attr = "data";
   1.134 +        else if (state.tagName == "embed")
   1.135 +          attr = "src";
   1.136 +
   1.137 +        if (attr && hasAttr(obj, attr)) {
   1.138 +          src(obj, state, getAttr(obj, attr));
   1.139 +        } else if (body.contains(obj)) {
   1.140 +          body.appendChild(obj);
   1.141 +        } else {
   1.142 +          // Out of document nodes without data attributes simply can't be
   1.143 +          // reloaded currently. Bug 767635
   1.144 +        }
   1.145 +      };
   1.146 +
   1.147 +      // Make a list of combinations of sub-lists, e.g.:
   1.148 +      // [ [a, b], [c, d] ]
   1.149 +      // ->
   1.150 +      // [ [a, c], [a, d], [b, c], [b, d] ]
   1.151 +      function eachList() {
   1.152 +        let all = [];
   1.153 +        if (!arguments.length)
   1.154 +          return all;
   1.155 +        let list = Array.prototype.slice.call(arguments, 0);
   1.156 +        for (let c of list[0]) {
   1.157 +          if (list.length > 1) {
   1.158 +            for (let x of eachList.apply(this,list.slice(1))) {
   1.159 +              all.push((c.length ? [c] : []).concat(x));
   1.160 +            }
   1.161 +          } else if (c.length) {
   1.162 +            all.push([c]);
   1.163 +          }
   1.164 +        }
   1.165 +        return all;
   1.166 +      }
   1.167 +
   1.168 +      let states = {
   1.169 +        svg: function(obj, state) {
   1.170 +          removeAttr(obj, "type");
   1.171 +          src(obj, state, testSVG);
   1.172 +          state.noChannel = false;
   1.173 +          state.expectedType = "image/svg";
   1.174 +          // SVGs are actually image-like subdocuments
   1.175 +          state.expectedMode = "document";
   1.176 +        },
   1.177 +        image: function(obj, state) {
   1.178 +          removeAttr(obj, "type");
   1.179 +          src(obj, state, testPNG);
   1.180 +          state.noChannel = false;
   1.181 +          state.expectedMode = "image";
   1.182 +          state.expectedType = "image/png";
   1.183 +        },
   1.184 +        plugin: function(obj, state) {
   1.185 +          removeAttr(obj, "type");
   1.186 +          src(obj, state, "data:application/x-test,foo");
   1.187 +          state.noChannel = false;
   1.188 +          state.expectedType = "application/x-test";
   1.189 +          state.expectedMode = "plugin";
   1.190 +        },
   1.191 +        pluginExtension: function(obj, state) {
   1.192 +          src(obj, state, "./fake_plugin.tst");
   1.193 +          state.expectedMode = "plugin";
   1.194 +          state.pluginExtension = true;
   1.195 +          state.noChannel = false;
   1.196 +        },
   1.197 +        document: function(obj, state) {
   1.198 +          removeAttr(obj, "type");
   1.199 +          src(obj, state, "data:text/plain,I am a document");
   1.200 +          state.noChannel = false;
   1.201 +          state.expectedType = "text/plain";
   1.202 +          state.expectedMode = "document";
   1.203 +        },
   1.204 +        fallback: function(obj, state) {
   1.205 +          removeAttr(obj, "type");
   1.206 +          state.expectedType = "application/x-unknown";
   1.207 +          state.expectedMode = "none";
   1.208 +          state.noChannel = true;
   1.209 +          src(obj, state, null);
   1.210 +        },
   1.211 +        addToDoc: function(obj, state) {
   1.212 +          body.appendChild(obj);
   1.213 +        },
   1.214 +        removeFromDoc: function(obj, state) {
   1.215 +          if (body.contains(obj))
   1.216 +            body.removeChild(obj);
   1.217 +        },
   1.218 +        // Set the proper type
   1.219 +        setType: function(obj, state) {
   1.220 +          if (state.expectedType) {
   1.221 +            state.badType = false;
   1.222 +            setAttr(obj, 'type', state.expectedType);
   1.223 +            forceReload(obj, state);
   1.224 +          }
   1.225 +        },
   1.226 +        // Set an improper type
   1.227 +        setWrongType: function(obj, state) {
   1.228 +          // This should break no-channel-plugins but nothing else
   1.229 +          state.badType = true;
   1.230 +          setAttr(obj, 'type', "application/x-unknown");
   1.231 +          forceReload(obj, state);
   1.232 +        },
   1.233 +        // Set a plugin type
   1.234 +        setPluginType: function(obj, state) {
   1.235 +          // If an object/embed has a type set to a plugin type, it should not
   1.236 +          // use the channel type.
   1.237 +          state.badType = false;
   1.238 +          setAttr(obj, 'type', 'application/x-test');
   1.239 +          state.expectedType = "application/x-test";
   1.240 +          state.expectedMode = "plugin";
   1.241 +          forceReload(obj, state);
   1.242 +        },
   1.243 +        noChannel: function(obj, state) {
   1.244 +          src(obj, state, null);
   1.245 +          state.noChannel = true;
   1.246 +          state.pluginExtension = false;
   1.247 +        },
   1.248 +        displayNone: function(obj, state) {
   1.249 +          setDisplayed(obj, false);
   1.250 +        },
   1.251 +        displayInherit: function(obj, state) {
   1.252 +          setDisplayed(obj, true);
   1.253 +        }
   1.254 +      };
   1.255 +
   1.256 +
   1.257 +      function testObject(obj, state) {
   1.258 +        // If our test combination both sets noChannel but no explicit type
   1.259 +        // it shouldn't load ever.
   1.260 +        let expectedMode = state.expectedMode;
   1.261 +        let actualMode = getMode(obj, state);
   1.262 +
   1.263 +        if (state.noChannel && !getAttr(obj, 'type')) {
   1.264 +          // Some combinations of test both set no type and no channel. This is
   1.265 +          // worth testing with the various combinations, but shouldn't load.
   1.266 +          expectedMode = "none";
   1.267 +        }
   1.268 +
   1.269 +        // Embed tags should always try to load a plugin by type or extension
   1.270 +        // before falling back to opening a channel. See bug 803159
   1.271 +        if (state.tagName == "embed" &&
   1.272 +            (getAttr(obj, 'type') == "application/x-test" || state.pluginExtension)) {
   1.273 +          state.noChannel = true;
   1.274 +        }
   1.275 +
   1.276 +        // with state.loading, unless we're loading with no channel, these types
   1.277 +        // should still be in loading state pending a channel.
   1.278 +        if (state.loading && (expectedMode == "image" || expectedMode == "document" ||
   1.279 +                             (expectedMode == "plugin" && !state.initialPlugin && !state.noChannel))) {
   1.280 +          expectedMode = "loading";
   1.281 +        }
   1.282 +
   1.283 +        // With the exception of plugins with a proper type, nothing should
   1.284 +        // load without a channel
   1.285 +        if (state.noChannel && (expectedMode != "plugin" || state.badType) &&
   1.286 +            body.contains(obj)) {
   1.287 +          expectedMode = "none";
   1.288 +        }
   1.289 +
   1.290 +        // embed tags should reject documents, except for SVG images which
   1.291 +        // render as such
   1.292 +        if (state.tagName == "embed" && expectedMode == "document" &&
   1.293 +            actualType(obj, state) != "image/svg+xml") {
   1.294 +          expectedMode = "none";
   1.295 +        }
   1.296 +
   1.297 +        // Embeds with a plugin type should skip opening a channel prior to
   1.298 +        // loading, taking only type into account.
   1.299 +        if (state.tagName == 'embed' && getAttr(obj, 'type') == 'application/x-test' &&
   1.300 +            body.contains(obj)) {
   1.301 +          expectedMode = "plugin";
   1.302 +        }
   1.303 +
   1.304 +        if (!body.contains(obj)
   1.305 +            && (!state.loading || expectedMode != "image")
   1.306 +            && (!state.initialPlugin || expectedMode != "plugin")) {
   1.307 +          // Images are handled by nsIImageLoadingContent so we dont track
   1.308 +          // their state change as they're detached and reattached. All other
   1.309 +          // types switch to state "loading", and are completely unloaded
   1.310 +          expectedMode = "loading";
   1.311 +        }
   1.312 +
   1.313 +        is(actualMode, expectedMode, "check loaded mode");
   1.314 +
   1.315 +        // If we're a plugin, check that we spawned successfully. state.loading
   1.316 +        // is set if we haven't had an event loop since applying state, in which
   1.317 +        // case the plugin would not have stopped yet if it was initially a
   1.318 +        // plugin.
   1.319 +        let shouldBeSpawnable = expectedMode == "plugin" && displayed(obj);
   1.320 +        let shouldSpawn = shouldBeSpawnable && (!state.loading || state.initialPlugin);
   1.321 +        let didSpawn = runningPlugin(obj, state);
   1.322 +        is(didSpawn, !!shouldSpawn, "check plugin spawned is " + !!shouldSpawn);
   1.323 +
   1.324 +        // If we are a plugin, scripting should work. If we're not spawned we
   1.325 +        // should spawn synchronously.
   1.326 +        let scripted = false;
   1.327 +        try {
   1.328 +          let x = obj.getObjectValue();
   1.329 +          scripted = true;
   1.330 +        } catch(e) {}
   1.331 +        is(scripted, shouldBeSpawnable, "check plugin scriptability");
   1.332 +
   1.333 +        // If this tag previously had other spawned plugins, make sure it
   1.334 +        // respawned between then and now
   1.335 +        if (state.oldPlugins && didSpawn) {
   1.336 +          let didRespawn = false;
   1.337 +          for (let oldp of state.oldPlugins) {
   1.338 +            // If this returns false or throws, it's not the same plugin
   1.339 +            try {
   1.340 +              didRespawn = !obj.checkObjectValue(oldp);
   1.341 +            } catch (e) {
   1.342 +              didRespawn = true;
   1.343 +            }
   1.344 +          }
   1.345 +          is(didRespawn, true, "Plugin should have re-spawned since old state ("+state.oldPlugins.length+")");
   1.346 +        }
   1.347 +      }
   1.348 +
   1.349 +      let total = 0;
   1.350 +      let test_modes = {
   1.351 +        // Just apply from_state then to_state
   1.352 +        "immediate": function(obj, from_state, to_state, state) {
   1.353 +          for (let from of from_state)
   1.354 +            states[from](obj, state);
   1.355 +          for (let to of to_state)
   1.356 +            states[to](obj, state);
   1.357 +
   1.358 +          // We don't spin the event loop between applying to_state and
   1.359 +          // running tests, so some types are still loading
   1.360 +          state.loading = true;
   1.361 +          info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / immediate");
   1.362 +          testObject(obj, state);
   1.363 +
   1.364 +          if (body.contains(obj))
   1.365 +            body.removeChild(obj);
   1.366 +
   1.367 +        },
   1.368 +        // Apply states, spin event loop, run tests.
   1.369 +        "cycle": function(obj, from_state, to_state, state) {
   1.370 +          for (let from of from_state)
   1.371 +            states[from](obj, state);
   1.372 +          for (let to of to_state)
   1.373 +            states[to](obj, state);
   1.374 +          // Because re-appending to the document creates a script blocker, but
   1.375 +          // plugins spawn asynchronously, we need to return to the event loop
   1.376 +          // twice to ensure the plugin has been given a chance to lazily spawn.
   1.377 +          runSoon(function() { runSoon(function() {
   1.378 +            info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycle");
   1.379 +            testObject(obj, state);
   1.380 +
   1.381 +            if (body.contains(obj))
   1.382 +              body.removeChild(obj);
   1.383 +          }); });
   1.384 +        },
   1.385 +        // Apply initial state, spin event loop, apply final state, spin event
   1.386 +        // loop again.
   1.387 +        "cycleboth": function(obj, from_state, to_state, state) {
   1.388 +          for (let from of from_state) {
   1.389 +            states[from](obj, state);
   1.390 +          }
   1.391 +          runSoon(function() {
   1.392 +            for (let to of to_state) {
   1.393 +              states[to](obj, state);
   1.394 +            }
   1.395 +            // Because re-appending to the document creates a script blocker,
   1.396 +            // but plugins spawn asynchronously, we need to return to the event
   1.397 +            // loop twice to ensure the plugin has been given a chance to lazily
   1.398 +            // spawn.
   1.399 +            runSoon(function() { runSoon(function() {
   1.400 +              info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycleboth");
   1.401 +              testObject(obj, state);
   1.402 +
   1.403 +              if (body.contains(obj))
   1.404 +                body.removeChild(obj);
   1.405 +            }); });
   1.406 +          });
   1.407 +        },
   1.408 +        // Apply initial state, spin event loop, apply later state, test
   1.409 +        // immediately
   1.410 +        "cyclefirst": function(obj, from_state, to_state, state) {
   1.411 +          for (let from of from_state) {
   1.412 +            states[from](obj, state);
   1.413 +          }
   1.414 +          runSoon(function() {
   1.415 +            state.initialPlugin = runningPlugin(obj, state);
   1.416 +            for (let to of to_state) {
   1.417 +              states[to](obj, state);
   1.418 +            }
   1.419 +            info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cyclefirst");
   1.420 +            // We don't spin the event loop between applying to_state and
   1.421 +            // running tests, so some types are still loading
   1.422 +            state.loading = true;
   1.423 +            testObject(obj, state);
   1.424 +
   1.425 +            if (body.contains(obj))
   1.426 +              body.removeChild(obj);
   1.427 +          });
   1.428 +        },
   1.429 +      };
   1.430 +
   1.431 +      function test(testdat) {
   1.432 +        for (let from_state of testdat['from_states']) {
   1.433 +          for (let to_state of testdat['to_states']) {
   1.434 +            for (let mode of testdat['test_modes']) {
   1.435 +              for (let type of testdat['tag_types']) {
   1.436 +                runSoon(function () {
   1.437 +                  let obj = document.createElement(type);
   1.438 +                  obj.width = 1; obj.height = 1;
   1.439 +                  let state = {};
   1.440 +                  state.noChannel = true;
   1.441 +                  state.tagName = type;
   1.442 +                  // Part of the test checks whether a plugin spawned or not,
   1.443 +                  // but touching the object prototype will attempt to
   1.444 +                  // synchronously spawn a plugin!  We use this terrible hack to
   1.445 +                  // get a privileged getter for the attributes we want to touch
   1.446 +                  // prior to applying any attributes.
   1.447 +                  // TODO when embed goes away we wont need to check for
   1.448 +                  //      QueryInterface any longer.
   1.449 +                  var lookup_on = obj.QueryInterface ? obj.QueryInterface(OBJLC): obj;
   1.450 +                  state.getDisplayedType = SpecialPowers.do_lookupGetter(lookup_on, 'displayedType');
   1.451 +                  state.getHasRunningPlugin = SpecialPowers.do_lookupGetter(lookup_on, 'hasRunningPlugin');
   1.452 +                  state.getActualType = SpecialPowers.do_lookupGetter(lookup_on, 'actualType');
   1.453 +                  test_modes[mode](obj, from_state, to_state, state);
   1.454 +                });
   1.455 +              }
   1.456 +            }
   1.457 +          }
   1.458 +        }
   1.459 +      }
   1.460 +
   1.461 +      function onLoad() {
   1.462 +        // Generic tests
   1.463 +        test({
   1.464 +          'tag_types': [ 'embed', 'object' ],
   1.465 +          // In all three modes
   1.466 +          'test_modes': [ 'immediate', 'cycle', 'cyclefirst', 'cycleboth' ],
   1.467 +          // Starting from a blank tag in and out of the document, a loading
   1.468 +          // plugin, and no-channel plugin (initial types only really have
   1.469 +          // odd cases with plugins)
   1.470 +          'from_states': [
   1.471 +            [ 'addToDoc' ],
   1.472 +            [ 'plugin' ],
   1.473 +            [ 'plugin', 'addToDoc' ],
   1.474 +            [ 'plugin', 'noChannel', 'setType', 'addToDoc' ],
   1.475 +            [],
   1.476 +          ],
   1.477 +          // To various combinations of loaded objects
   1.478 +          'to_states': eachList(
   1.479 +            [ 'svg', 'image', 'plugin', 'document', '' ],
   1.480 +            [ 'setType', 'setWrongType', 'setPluginType', '' ],
   1.481 +            [ 'noChannel', '' ],
   1.482 +            [ 'displayNone', 'displayInherit', '' ]
   1.483 +          )});
   1.484 +        // Special case test for embed tags with plugin-by-extension
   1.485 +        // TODO object tags should be tested here too -- they have slightly
   1.486 +        //      different behavior, but waiting on a file load requires a loaded
   1.487 +        //      event handler and wont work with just our event loop spinning.
   1.488 +        test({
   1.489 +          'tag_types': [ 'embed' ],
   1.490 +          'test_modes': [ 'immediate', 'cyclefirst', 'cycle', 'cycleboth' ],
   1.491 +          'from_states': eachList(
   1.492 +            [ 'svg', 'plugin', 'image', 'document' ],
   1.493 +            [ 'addToDoc' ]
   1.494 +          ),
   1.495 +          // Set extension along with valid ty
   1.496 +          'to_states': [
   1.497 +            [ 'pluginExtension' ]
   1.498 +          ]});
   1.499 +        // Test plugin add/remove from document with adding/removing frame, with
   1.500 +        // and without a channel.
   1.501 +        test({
   1.502 +          'tag_types': [ 'embed', 'object' ], // Ideally we'd test object too, but this gets exponentially long.
   1.503 +          'test_modes': [ 'immediate', 'cyclefirst', 'cycle' ],
   1.504 +          'from_states': [ [ 'displayNone', 'plugin', 'addToDoc' ],
   1.505 +                           [ 'displayNone', 'plugin', 'noChannel', 'addToDoc' ],
   1.506 +                           [ 'plugin', 'noChannel', 'addToDoc' ],
   1.507 +                           [ 'plugin', 'noChannel' ] ],
   1.508 +          'to_states': eachList(
   1.509 +            [ 'displayNone', '' ],
   1.510 +            [ 'removeFromDoc' ],
   1.511 +            [ 'image', 'displayNone', '' ],
   1.512 +            [ 'image', 'displayNone', '' ],
   1.513 +            [ 'addToDoc' ],
   1.514 +            [ 'displayInherit' ]
   1.515 +          )});
   1.516 +        runWhenDone(function() SimpleTest.finish());
   1.517 +      }
   1.518 +    </script>

mercurial