content/base/test/test_object.html

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 <!DOCTYPE html>
     2 <html>
     3   <head>
     4     <title>Plugin instantiation</title>
     5     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     6     <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
     7     <meta charset="utf-8">
     8   <body onload="onLoad()">
     9     <script class="testbody" type="text/javascript;version=1.8">
    11       "use strict";
    12       SimpleTest.waitForExplicitFinish();
    14       var pluginHost = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
    15                         .getService(SpecialPowers.Ci.nsIPluginHost);
    16       var pluginTags = pluginHost.getPluginTags();
    17       for (var tag of pluginTags) {
    18         if (tag.name == "Test Plug-in") {
    19           tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED;;
    20         }
    21       }
    23       // This can go away once embed also is on WebIDL
    24       let OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
    26       // Use string modes in this test to make the test easier to read/debug.
    27       // nsIObjectLoadingContent refers to this as "type", but I am using "mode"
    28       // in the test to avoid confusing with content-type.
    29       let prettyModes = {};
    30       prettyModes[OBJLC.TYPE_LOADING] = "loading";
    31       prettyModes[OBJLC.TYPE_IMAGE] = "image";
    32       prettyModes[OBJLC.TYPE_PLUGIN] = "plugin";
    33       prettyModes[OBJLC.TYPE_DOCUMENT] = "document";
    34       prettyModes[OBJLC.TYPE_NULL] = "none";
    36       let body = document.body;
    37       // A single-pixel white png
    38       let testPNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoIFiETNqbNRQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAACklEQVQIHWP4DwABAQEANl9ngAAAAABJRU5ErkJggg==';
    39       // An empty, but valid, SVG
    40       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>';
    41       // executeSoon wrapper to count pending callbacks
    42       let pendingCalls = 0;
    43       let afterPendingCalls = false;
    45       function runWhenDone(func) {
    46         if (!pendingCalls)
    47           func();
    48         else
    49           afterPendingCalls = func;
    50       }
    51       function runSoon(func) {
    52         pendingCalls++;
    53         SimpleTest.executeSoon(function() {
    54           func();
    55           if (--pendingCalls < 1 && afterPendingCalls)
    56             afterPendingCalls();
    57         });
    58       }
    59       function src(obj, state, uri) {
    60         // If we have a running plugin, src changing should always throw it out,
    61         // even if it causes us to load the same plugin again.
    62         if (uri && runningPlugin(obj, state)) {
    63           if (!state.oldPlugins)
    64             state.oldPlugins = [];
    65           try {
    66             state.oldPlugins.push(obj.getObjectValue());
    67           } catch (e) {
    68             ok(false, "Running plugin but cannot call getObjectValue?");
    69           }
    70         }
    72         var srcattr;
    73         if (state.tagName == "object")
    74           srcattr = "data";
    75         else if (state.tagName == "embed")
    76           srcattr = "src";
    77         else
    78           ok(false, "Internal test fail: Why are we setting the src of an applet");
    80         // Plugins should always go away immediately on src/data change
    81         state.initialPlugin = false;
    82         if (uri === null) {
    83           removeAttr(obj, srcattr);
    84           // TODO Bug 767631 - we don't trigger loadObject on UnsetAttr :(
    85           forceReload(obj, state);
    86         } else {
    87           setAttr(obj, srcattr, uri);
    88         }
    89       }
    90       // We have to be careful not to reach past the nsObjectLoadingContent
    91       // prototype to touch generic element attributes, as this will try to
    92       // spawn the plugin, breaking our ability to test for that.
    93       function getAttr(obj, attr) {
    94         return document.body.constructor.prototype.getAttribute.call(obj, attr);
    95       }
    96       function setAttr(obj, attr, val) {
    97         return document.body.constructor.prototype.setAttribute.call(obj, attr, val);
    98       }
    99       function hasAttr(obj, attr) {
   100         return document.body.constructor.prototype.hasAttribute.call(obj, attr);
   101       }
   102       function removeAttr(obj, attr) {
   103         return document.body.constructor.prototype.removeAttribute.call(obj, attr);
   104       }
   105       function setDisplayed(obj, display) {
   106         if (display)
   107           removeAttr(obj, 'style');
   108         else
   109           setAttr(obj, 'style', "display: none;");
   110       }
   111       function displayed(obj) {
   112         // Hacky, but that's all we use style for.
   113         return !hasAttr(obj, 'style');
   114       }
   115       function actualType(obj, state) {
   116         return state.getActualType.call(obj);
   117       }
   118       function getMode(obj, state) {
   119         return prettyModes[state.getDisplayedType.call(obj)];
   120       }
   121       function runningPlugin(obj, state) {
   122         return state.getHasRunningPlugin.call(obj);
   123       }
   125       // TODO this is hacky and might hide some failures, but is needed until
   126       // Bug 767635 lands -- which itself will probably mean tweaking this test.
   127       function forceReload(obj, state) {
   128         let attr;
   129         if (state.tagName == "object")
   130           attr = "data";
   131         else if (state.tagName == "embed")
   132           attr = "src";
   134         if (attr && hasAttr(obj, attr)) {
   135           src(obj, state, getAttr(obj, attr));
   136         } else if (body.contains(obj)) {
   137           body.appendChild(obj);
   138         } else {
   139           // Out of document nodes without data attributes simply can't be
   140           // reloaded currently. Bug 767635
   141         }
   142       };
   144       // Make a list of combinations of sub-lists, e.g.:
   145       // [ [a, b], [c, d] ]
   146       // ->
   147       // [ [a, c], [a, d], [b, c], [b, d] ]
   148       function eachList() {
   149         let all = [];
   150         if (!arguments.length)
   151           return all;
   152         let list = Array.prototype.slice.call(arguments, 0);
   153         for (let c of list[0]) {
   154           if (list.length > 1) {
   155             for (let x of eachList.apply(this,list.slice(1))) {
   156               all.push((c.length ? [c] : []).concat(x));
   157             }
   158           } else if (c.length) {
   159             all.push([c]);
   160           }
   161         }
   162         return all;
   163       }
   165       let states = {
   166         svg: function(obj, state) {
   167           removeAttr(obj, "type");
   168           src(obj, state, testSVG);
   169           state.noChannel = false;
   170           state.expectedType = "image/svg";
   171           // SVGs are actually image-like subdocuments
   172           state.expectedMode = "document";
   173         },
   174         image: function(obj, state) {
   175           removeAttr(obj, "type");
   176           src(obj, state, testPNG);
   177           state.noChannel = false;
   178           state.expectedMode = "image";
   179           state.expectedType = "image/png";
   180         },
   181         plugin: function(obj, state) {
   182           removeAttr(obj, "type");
   183           src(obj, state, "data:application/x-test,foo");
   184           state.noChannel = false;
   185           state.expectedType = "application/x-test";
   186           state.expectedMode = "plugin";
   187         },
   188         pluginExtension: function(obj, state) {
   189           src(obj, state, "./fake_plugin.tst");
   190           state.expectedMode = "plugin";
   191           state.pluginExtension = true;
   192           state.noChannel = false;
   193         },
   194         document: function(obj, state) {
   195           removeAttr(obj, "type");
   196           src(obj, state, "data:text/plain,I am a document");
   197           state.noChannel = false;
   198           state.expectedType = "text/plain";
   199           state.expectedMode = "document";
   200         },
   201         fallback: function(obj, state) {
   202           removeAttr(obj, "type");
   203           state.expectedType = "application/x-unknown";
   204           state.expectedMode = "none";
   205           state.noChannel = true;
   206           src(obj, state, null);
   207         },
   208         addToDoc: function(obj, state) {
   209           body.appendChild(obj);
   210         },
   211         removeFromDoc: function(obj, state) {
   212           if (body.contains(obj))
   213             body.removeChild(obj);
   214         },
   215         // Set the proper type
   216         setType: function(obj, state) {
   217           if (state.expectedType) {
   218             state.badType = false;
   219             setAttr(obj, 'type', state.expectedType);
   220             forceReload(obj, state);
   221           }
   222         },
   223         // Set an improper type
   224         setWrongType: function(obj, state) {
   225           // This should break no-channel-plugins but nothing else
   226           state.badType = true;
   227           setAttr(obj, 'type', "application/x-unknown");
   228           forceReload(obj, state);
   229         },
   230         // Set a plugin type
   231         setPluginType: function(obj, state) {
   232           // If an object/embed has a type set to a plugin type, it should not
   233           // use the channel type.
   234           state.badType = false;
   235           setAttr(obj, 'type', 'application/x-test');
   236           state.expectedType = "application/x-test";
   237           state.expectedMode = "plugin";
   238           forceReload(obj, state);
   239         },
   240         noChannel: function(obj, state) {
   241           src(obj, state, null);
   242           state.noChannel = true;
   243           state.pluginExtension = false;
   244         },
   245         displayNone: function(obj, state) {
   246           setDisplayed(obj, false);
   247         },
   248         displayInherit: function(obj, state) {
   249           setDisplayed(obj, true);
   250         }
   251       };
   254       function testObject(obj, state) {
   255         // If our test combination both sets noChannel but no explicit type
   256         // it shouldn't load ever.
   257         let expectedMode = state.expectedMode;
   258         let actualMode = getMode(obj, state);
   260         if (state.noChannel && !getAttr(obj, 'type')) {
   261           // Some combinations of test both set no type and no channel. This is
   262           // worth testing with the various combinations, but shouldn't load.
   263           expectedMode = "none";
   264         }
   266         // Embed tags should always try to load a plugin by type or extension
   267         // before falling back to opening a channel. See bug 803159
   268         if (state.tagName == "embed" &&
   269             (getAttr(obj, 'type') == "application/x-test" || state.pluginExtension)) {
   270           state.noChannel = true;
   271         }
   273         // with state.loading, unless we're loading with no channel, these types
   274         // should still be in loading state pending a channel.
   275         if (state.loading && (expectedMode == "image" || expectedMode == "document" ||
   276                              (expectedMode == "plugin" && !state.initialPlugin && !state.noChannel))) {
   277           expectedMode = "loading";
   278         }
   280         // With the exception of plugins with a proper type, nothing should
   281         // load without a channel
   282         if (state.noChannel && (expectedMode != "plugin" || state.badType) &&
   283             body.contains(obj)) {
   284           expectedMode = "none";
   285         }
   287         // embed tags should reject documents, except for SVG images which
   288         // render as such
   289         if (state.tagName == "embed" && expectedMode == "document" &&
   290             actualType(obj, state) != "image/svg+xml") {
   291           expectedMode = "none";
   292         }
   294         // Embeds with a plugin type should skip opening a channel prior to
   295         // loading, taking only type into account.
   296         if (state.tagName == 'embed' && getAttr(obj, 'type') == 'application/x-test' &&
   297             body.contains(obj)) {
   298           expectedMode = "plugin";
   299         }
   301         if (!body.contains(obj)
   302             && (!state.loading || expectedMode != "image")
   303             && (!state.initialPlugin || expectedMode != "plugin")) {
   304           // Images are handled by nsIImageLoadingContent so we dont track
   305           // their state change as they're detached and reattached. All other
   306           // types switch to state "loading", and are completely unloaded
   307           expectedMode = "loading";
   308         }
   310         is(actualMode, expectedMode, "check loaded mode");
   312         // If we're a plugin, check that we spawned successfully. state.loading
   313         // is set if we haven't had an event loop since applying state, in which
   314         // case the plugin would not have stopped yet if it was initially a
   315         // plugin.
   316         let shouldBeSpawnable = expectedMode == "plugin" && displayed(obj);
   317         let shouldSpawn = shouldBeSpawnable && (!state.loading || state.initialPlugin);
   318         let didSpawn = runningPlugin(obj, state);
   319         is(didSpawn, !!shouldSpawn, "check plugin spawned is " + !!shouldSpawn);
   321         // If we are a plugin, scripting should work. If we're not spawned we
   322         // should spawn synchronously.
   323         let scripted = false;
   324         try {
   325           let x = obj.getObjectValue();
   326           scripted = true;
   327         } catch(e) {}
   328         is(scripted, shouldBeSpawnable, "check plugin scriptability");
   330         // If this tag previously had other spawned plugins, make sure it
   331         // respawned between then and now
   332         if (state.oldPlugins && didSpawn) {
   333           let didRespawn = false;
   334           for (let oldp of state.oldPlugins) {
   335             // If this returns false or throws, it's not the same plugin
   336             try {
   337               didRespawn = !obj.checkObjectValue(oldp);
   338             } catch (e) {
   339               didRespawn = true;
   340             }
   341           }
   342           is(didRespawn, true, "Plugin should have re-spawned since old state ("+state.oldPlugins.length+")");
   343         }
   344       }
   346       let total = 0;
   347       let test_modes = {
   348         // Just apply from_state then to_state
   349         "immediate": function(obj, from_state, to_state, state) {
   350           for (let from of from_state)
   351             states[from](obj, state);
   352           for (let to of to_state)
   353             states[to](obj, state);
   355           // We don't spin the event loop between applying to_state and
   356           // running tests, so some types are still loading
   357           state.loading = true;
   358           info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / immediate");
   359           testObject(obj, state);
   361           if (body.contains(obj))
   362             body.removeChild(obj);
   364         },
   365         // Apply states, spin event loop, run tests.
   366         "cycle": function(obj, from_state, to_state, state) {
   367           for (let from of from_state)
   368             states[from](obj, state);
   369           for (let to of to_state)
   370             states[to](obj, state);
   371           // Because re-appending to the document creates a script blocker, but
   372           // plugins spawn asynchronously, we need to return to the event loop
   373           // twice to ensure the plugin has been given a chance to lazily spawn.
   374           runSoon(function() { runSoon(function() {
   375             info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycle");
   376             testObject(obj, state);
   378             if (body.contains(obj))
   379               body.removeChild(obj);
   380           }); });
   381         },
   382         // Apply initial state, spin event loop, apply final state, spin event
   383         // loop again.
   384         "cycleboth": function(obj, from_state, to_state, state) {
   385           for (let from of from_state) {
   386             states[from](obj, state);
   387           }
   388           runSoon(function() {
   389             for (let to of to_state) {
   390               states[to](obj, state);
   391             }
   392             // Because re-appending to the document creates a script blocker,
   393             // but plugins spawn asynchronously, we need to return to the event
   394             // loop twice to ensure the plugin has been given a chance to lazily
   395             // spawn.
   396             runSoon(function() { runSoon(function() {
   397               info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycleboth");
   398               testObject(obj, state);
   400               if (body.contains(obj))
   401                 body.removeChild(obj);
   402             }); });
   403           });
   404         },
   405         // Apply initial state, spin event loop, apply later state, test
   406         // immediately
   407         "cyclefirst": function(obj, from_state, to_state, state) {
   408           for (let from of from_state) {
   409             states[from](obj, state);
   410           }
   411           runSoon(function() {
   412             state.initialPlugin = runningPlugin(obj, state);
   413             for (let to of to_state) {
   414               states[to](obj, state);
   415             }
   416             info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cyclefirst");
   417             // We don't spin the event loop between applying to_state and
   418             // running tests, so some types are still loading
   419             state.loading = true;
   420             testObject(obj, state);
   422             if (body.contains(obj))
   423               body.removeChild(obj);
   424           });
   425         },
   426       };
   428       function test(testdat) {
   429         for (let from_state of testdat['from_states']) {
   430           for (let to_state of testdat['to_states']) {
   431             for (let mode of testdat['test_modes']) {
   432               for (let type of testdat['tag_types']) {
   433                 runSoon(function () {
   434                   let obj = document.createElement(type);
   435                   obj.width = 1; obj.height = 1;
   436                   let state = {};
   437                   state.noChannel = true;
   438                   state.tagName = type;
   439                   // Part of the test checks whether a plugin spawned or not,
   440                   // but touching the object prototype will attempt to
   441                   // synchronously spawn a plugin!  We use this terrible hack to
   442                   // get a privileged getter for the attributes we want to touch
   443                   // prior to applying any attributes.
   444                   // TODO when embed goes away we wont need to check for
   445                   //      QueryInterface any longer.
   446                   var lookup_on = obj.QueryInterface ? obj.QueryInterface(OBJLC): obj;
   447                   state.getDisplayedType = SpecialPowers.do_lookupGetter(lookup_on, 'displayedType');
   448                   state.getHasRunningPlugin = SpecialPowers.do_lookupGetter(lookup_on, 'hasRunningPlugin');
   449                   state.getActualType = SpecialPowers.do_lookupGetter(lookup_on, 'actualType');
   450                   test_modes[mode](obj, from_state, to_state, state);
   451                 });
   452               }
   453             }
   454           }
   455         }
   456       }
   458       function onLoad() {
   459         // Generic tests
   460         test({
   461           'tag_types': [ 'embed', 'object' ],
   462           // In all three modes
   463           'test_modes': [ 'immediate', 'cycle', 'cyclefirst', 'cycleboth' ],
   464           // Starting from a blank tag in and out of the document, a loading
   465           // plugin, and no-channel plugin (initial types only really have
   466           // odd cases with plugins)
   467           'from_states': [
   468             [ 'addToDoc' ],
   469             [ 'plugin' ],
   470             [ 'plugin', 'addToDoc' ],
   471             [ 'plugin', 'noChannel', 'setType', 'addToDoc' ],
   472             [],
   473           ],
   474           // To various combinations of loaded objects
   475           'to_states': eachList(
   476             [ 'svg', 'image', 'plugin', 'document', '' ],
   477             [ 'setType', 'setWrongType', 'setPluginType', '' ],
   478             [ 'noChannel', '' ],
   479             [ 'displayNone', 'displayInherit', '' ]
   480           )});
   481         // Special case test for embed tags with plugin-by-extension
   482         // TODO object tags should be tested here too -- they have slightly
   483         //      different behavior, but waiting on a file load requires a loaded
   484         //      event handler and wont work with just our event loop spinning.
   485         test({
   486           'tag_types': [ 'embed' ],
   487           'test_modes': [ 'immediate', 'cyclefirst', 'cycle', 'cycleboth' ],
   488           'from_states': eachList(
   489             [ 'svg', 'plugin', 'image', 'document' ],
   490             [ 'addToDoc' ]
   491           ),
   492           // Set extension along with valid ty
   493           'to_states': [
   494             [ 'pluginExtension' ]
   495           ]});
   496         // Test plugin add/remove from document with adding/removing frame, with
   497         // and without a channel.
   498         test({
   499           'tag_types': [ 'embed', 'object' ], // Ideally we'd test object too, but this gets exponentially long.
   500           'test_modes': [ 'immediate', 'cyclefirst', 'cycle' ],
   501           'from_states': [ [ 'displayNone', 'plugin', 'addToDoc' ],
   502                            [ 'displayNone', 'plugin', 'noChannel', 'addToDoc' ],
   503                            [ 'plugin', 'noChannel', 'addToDoc' ],
   504                            [ 'plugin', 'noChannel' ] ],
   505           'to_states': eachList(
   506             [ 'displayNone', '' ],
   507             [ 'removeFromDoc' ],
   508             [ 'image', 'displayNone', '' ],
   509             [ 'image', 'displayNone', '' ],
   510             [ 'addToDoc' ],
   511             [ 'displayInherit' ]
   512           )});
   513         runWhenDone(function() SimpleTest.finish());
   514       }
   515     </script>

mercurial