tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

001.html (19345B)


      1 <!doctype html>
      2 <html>
      3    <head>
      4        <title>history.pushState tests</title>
      5        <script type="text/javascript" src="/resources/testharness.js"></script>
      6        <script type="text/javascript" src="/resources/testharnessreport.js"></script>
      7        <script type="text/javascript">
      8 //does not test for firing of popstate onload, because this was dropped from the specification on 25 March 2011
      9 //covers history.state after load, in accordance with the specification draft from 25 March 2011
     10 //history.state before load is tested in 006 and 007
     11 //does not test for structured cloning of FileList, File or Blob interfaces, as these require manual file selection
     12 
     13 //**This test assumes that assignments to location.hash will be synchronous - this is how all browsers implement it.
     14 //The spec (as of 25 March 2011) disagrees.**//
     15 
     16 var histlength, atstep = 0, lasttimer;
     17 setup({explicit_done:true}); //tests should take under 6 seconds + execution time
     18 
     19 window.onload = function () {
     20    if( location.protocol == 'file:' ) {
     21        document.getElementsByTagName('p')[0].innerHTML = 'ERROR: This test cannot be run from file: (URL resolving will not work). It must be loaded over HTTP.';
     22        return;
     23    } else if( location.protocol == 'https:' ) {
     24        document.getElementsByTagName('p')[0].innerHTML += '<br>WARNING: Browsers may intentionally fail to update history.length when pages are loaded over HTTPS, as a privacy restriction. If possible, load this page over HTTP.';
     25    }
     26    //use a timeout, because some browsers intentionally do not add history entries for URL changes in the onload thread
     27    setTimeout(testinit,100);
     28 };
     29 function testinit() {
     30    atstep = 1;
     31    histlength = history.length;
     32    iframe = document.getElementsByTagName('iframe')[0].src = 'blank2.html';
     33    //reportload will now be called by the onload handler for the iframe
     34 }
     35 function reportload() {
     36    var iframe = document.getElementsByTagName('iframe')[0], hashchng = false;
     37    var canvassup = false, cloneobj;
     38 
     39    function tests1() {
     40        //Firefox may fail when reloading, because it recovers iframe state, and therefore does not see the need to alter history length
     41        test(function () { assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' ); }, 'history.length should update when loading pages in an iframe');
     42        histlength = history.length;
     43        iframe.contentWindow.location.hash = 'test'; //should be synchronous **SEE COMMENT AT TOP OF FILE
     44        test(function () {
     45            assert_equals( history.length, histlength + 1, 'make sure that you loaded the test in a new tab/window' );
     46        }, 'history.length should update when setting location.hash');
     47        test(function () { assert_true( !!history.pushState, 'critical test; ignore any failures after this' ); }, 'history.pushState must exist'); //assert_own_property does not allow prototype inheritance
     48        test(function () { assert_true( !!iframe.contentWindow.history.pushState, 'critical test; ignore any failures after this' ); }, 'history.pushState must exist within iframes');
     49        test(function () {
     50            assert_equals( iframe.contentWindow.history.state, null );
     51        }, 'initial history.state should be null');
     52        test(function () {
     53            histlength = history.length;
     54            iframe.contentWindow.history.pushState('','');
     55            assert_equals( history.length, histlength + 1 );
     56        }, 'history.length should update when pushing a state');
     57        test(function () {
     58            assert_equals( iframe.contentWindow.history.state, '' );
     59        }, 'history.state should update after a state is pushed');
     60        histlength = history.length;
     61        iframe.contentWindow.addEventListener("popstate", tests2, {once: true});
     62        history.back();
     63    }
     64    function tests2() {
     65        test(function () {
     66            assert_equals( history.length, histlength );
     67        }, 'history.length should not decrease after going back');
     68        test(function () {
     69            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' );
     70        }, 'traversing history must traverse pushed states');
     71        iframe.contentWindow.addEventListener("hashchange", tests3, {once: true});
     72        history.go(-1);
     73    }
     74    function tests3() {
     75        test(function () {
     76            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), '', '(this could cause other failures later on)' );
     77        }, 'traversing history must also traverse hash changes');
     78 
     79        iframe.contentWindow.addEventListener("hashchange", tests4, {once: true});
     80        //Safari 5.0.3 fails here - it navigates *this* document to the *iframe's* location, instead of just navigating the iframe
     81        history.go(2);
     82    }
     83    async function tests4() {
     84        test(function () {
     85            //Firefox 4 beta 11 has a messed up error object, which does not have the right error type or .SECURITY_ERR property
     86            assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','//exa mple'); });
     87        }, 'pushState must not be allowed to create invalid URLs');
     88        test(function () {
     89            assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','http://www.example.com/'); });
     90        }, 'pushState must not be allowed to create cross-origin URLs');
     91        test(function () {
     92            assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','about:blank'); });
     93        }, 'pushState must not be allowed to create cross-origin URLs (about:blank)');
     94        test(function () {
     95            assert_throws_dom('SECURITY_ERR',function () { history.pushState('','','data:text/html,'); });
     96        }, 'pushState must not be allowed to create cross-origin URLs (data:URI)');
     97        test(function () {
     98            assert_throws_dom('SECURITY_ERR', iframe.contentWindow.DOMException, function () { iframe.contentWindow.history.pushState('','','http://www.example.com/'); });
     99        }, 'security errors are expected to be thrown in the context of the document that owns the history object');
    100        let hashchange =
    101          new Promise(function (resolve) {
    102            iframe.contentWindow.addEventListener("hashchange", resolve, {once: true});
    103          });
    104 
    105        test(function () {
    106            iframe.contentWindow.location.hash = 'test2';
    107            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test2', 'location.hash did not change when told to' );
    108        }, 'location.hash must be allowed to change (part 1)');
    109        await hashchange;
    110        history.go(-1);
    111        iframe.contentWindow.addEventListener("hashchange", tests5, {once: true});
    112    }
    113    function tests5() {
    114        test(function () {
    115            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test', 'location.hash did not change when going back' );
    116        }, 'location.hash must be allowed to change (part 2)');
    117        test(function () {
    118            iframe.contentWindow.history.pushState('','');
    119            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test', 'location.hash changed when an unrelated state was pushed' );
    120        }, 'pushState must not alter location.hash when no URL is provided');
    121        history.go(1); //should do nothing, since the pushState should have removed the forward history
    122        setTimeout(tests6,50); //.go is queued to end of thread
    123    }
    124    function tests6() {
    125        test(function () {
    126            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test' );
    127        }, 'pushState must remove all history after the current state');
    128        test(function () {
    129            iframe.contentWindow.history.pushState('','','#test3');
    130            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' );
    131        }, 'pushState must be able to set location.hash');
    132        //begin setup for "remove any tasks queued by the history traversal task source"
    133        iframe.contentWindow.location.hash = '#test4';
    134        iframe.contentWindow.history.go(-1); //must be queued
    135        try {
    136            //must remove the queued navigation in the same browsing context
    137            iframe.contentWindow.history.pushState('','');
    138        } catch(unsuperr) {}
    139        //allow the browser to mistakenly run the .go if it is going to
    140        //do not put two .go commands in the same thread, in case the browser mistakenly calculates the history position when
    141        //calling .go instead of when executing the traversal task - that could give a false PASS in the next test otherwise
    142        setTimeout(tests7,50);
    143    }
    144    function tests7() {
    145        iframe.contentWindow.history.go(-1); //must be queued, but should not be removed this time
    146        iframe.contentWindow.addEventListener("popstate", tests8, {once: true});
    147    }
    148    function tests8() {
    149        test(function () {
    150            assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test4' );
    151        }, 'pushState must remove any tasks queued by the history traversal task source');
    152        //end "remove any tasks queued by the history traversal task source"
    153        window.addEventListener('hashchange',function () { hashchng = true; },false);
    154        try {
    155            //push a state that changes the hash
    156            iframe.contentWindow.history.pushState('','',iframe.contentWindow.location.pathname+'#test5');
    157        } catch(unsuperr) {}
    158        setTimeout(tests9,50); //allow the hashchange event to process, if the browser has mistakenly fired it
    159    }
    160    function tests9() {
    161        test(function () {
    162            assert_false( hashchng );
    163        }, 'pushState must not fire hashchange events');
    164        test(function () {
    165            iframe.contentWindow.history.pushState('','','/testing_ignore_me_404');
    166            assert_equals( iframe.contentWindow.location.pathname, '/testing_ignore_me_404' );
    167        }, 'pushState must be able to set location.pathname');
    168        test(function () {
    169            var newURL = location.href.replace(/\/[^\/]*$/)+'/testing_ignore_me_404/';
    170            iframe.contentWindow.history.pushState('','',newURL);
    171            assert_equals( iframe.contentWindow.location.href, newURL );
    172        }, 'pushState must be able to set absolute URLs to the same host');
    173        test(function () {
    174            assert_throws_dom( 'DATA_CLONE_ERR', function () {
    175                history.pushState({dummy:function () {}},'');
    176            } );
    177        }, 'pushState must not be able to use a function as data');
    178        test(function () {
    179            assert_throws_dom( 'DATA_CLONE_ERR', function () {
    180                history.pushState({dummy:window},'');
    181            } );
    182        }, 'pushState must not be able to use a DOM node as data');
    183        test(function () {
    184            try { a.b = c; } catch(errdata) {
    185                history.pushState({dummy:errdata},'');
    186                assert_equals(ReferenceError.prototype, Object.getPrototypeOf(history.state.dummy));
    187            }
    188        }, 'pushState must be able to use an error object as data');
    189        test(function () {
    190            assert_throws_dom('DATA_CLONE_ERR', iframe.contentWindow.DOMException, function () {
    191                iframe.contentWindow.history.pushState(document,'');
    192            });
    193        }, 'security errors are expected to be thrown in the context of the document that owns the history object (2)');
    194        cloneobj = {
    195            nulldata: null,
    196            udefdata: window.undefined,
    197            booldata: true,
    198            numdata: 1,
    199            strdata: 'string data',
    200            boolobj: new Boolean(true),
    201            numobj: new Number(1),
    202            strobj: new String('string data'),
    203            datedata: new Date(),
    204            regdata: /a/g,
    205            arrdata: [1]
    206        };
    207        cloneobj.regdata.lastIndex = 1;
    208        cloneobj.looped = cloneobj;
    209        //test the ImageData type, if the browser supports it
    210        var canvas = document.createElement('canvas');
    211        if( canvas.getContext && ( canvas = canvas.getContext('2d') ) && canvas.createImageData ) {
    212            canvassup = true;
    213            cloneobj.imgdata = canvas.createImageData(1,1);
    214        }
    215        test(function () {
    216            try {
    217                iframe.contentWindow.history.pushState(cloneobj,'new title');
    218            } catch(e) {
    219                cloneobj.looped = null;
    220                //try again because this object is needed for future tests
    221                iframe.contentWindow.history.pushState(cloneobj,'new title');
    222                //rethrow so the browser gets a FAIL for not coping with the circular reference; "internal structured cloning algorithm" step 1
    223                throw(e);
    224            }
    225        }, 'pushState must be able to make structured clones of complex objects');
    226        test(function () {
    227            assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' );
    228        }, 'history.state should also reference a clone of the original object');
    229        test(function () {
    230            assert_not_equals( cloneobj, iframe.contentWindow.history.state );
    231        }, 'history.state should be a clone of the original object, not a reference to it');
    232        /*
    233        behaviour is not defined per spec, and no known implementations do this
    234        test(function () {
    235            assert_equals( iframe.contentDocument.title, 'new title', 'not required for specification conformance' );
    236        }, 'pushState MIGHT set the document title');
    237        */
    238        history.go(-1);
    239        iframe.contentWindow.addEventListener("popstate", tests10, {once: true});
    240    }
    241    function tests10() {
    242        var eventtime = setTimeout(function () { tests11(false); },500); //should be cleared by the event handler long before it has a chance to fire
    243        iframe.contentWindow.addEventListener('popstate',function (e) { clearTimeout(eventtime); tests11(true,e); },false);
    244        history.forward();
    245    }
    246    function tests11(hasFired,ev) {
    247        test(function () {
    248            assert_true( hasFired );
    249        }, 'popstate event should fire when navigation occurs');
    250        test(function () {
    251            assert_true( !!ev && typeof(ev.state) != 'undefined', 'state information was not passed' );
    252            assert_true( !!ev.state, 'state information does not contain the expected value - browser is probably stuck in the wrong history position' );
    253            assert_equals( ev.state.nulldata, null, 'state null data was not correct' );
    254            assert_equals( ev.state.udefdata, window.undefined, 'state undefined data was not correct' );
    255            assert_true( ev.state.booldata, 'state boolean data was not correct' );
    256            assert_equals( ev.state.numdata, 1, 'state numeric data was not correct' );
    257            assert_equals( ev.state.strdata, 'string data', 'state string data was not correct' );
    258            assert_true( !!ev.state.datedata.getTime, 'state date data was not correct' );
    259            assert_own_property( ev.state, 'regdata', 'state regex data was not correct' );
    260            assert_equals( ev.state.regdata.source, 'a', 'state regex pattern data was not correct' );
    261            assert_true( ev.state.regdata.global, 'state regex flag data was not correct' );
    262            assert_equals( ev.state.regdata.lastIndex, 0, 'state regex lastIndex data was not correct' );
    263            assert_equals( ev.state.arrdata.length, 1, 'state array data was not correct' );
    264            assert_true( ev.state.boolobj.valueOf(), 'state boolean data was not correct' );
    265            assert_equals( ev.state.numobj.valueOf(), 1, 'state numeric data was not correct' );
    266            assert_equals( ev.state.strobj.valueOf(), 'string data', 'state string data was not correct' );
    267            if( canvassup ) {
    268                assert_equals( ev.state.imgdata.width, 1, 'state ImageData was not correct' );
    269            }
    270        }, 'popstate event should pass the state data');
    271        test(function () {
    272            assert_equals( ev.state.looped, ev.state );
    273        }, 'state data should cope with circular object references');
    274        test(function () {
    275            assert_not_equals( cloneobj, ev.state );
    276        }, 'state data should be a clone of the original object, not a reference to it');
    277        test(function () {
    278            assert_equals( iframe.contentWindow.history.state && iframe.contentWindow.history.state.strdata, 'string data' );
    279        }, 'history.state should also reference a clone of the original object (2)');
    280        test(function () {
    281            assert_not_equals( cloneobj, iframe.contentWindow.history.state );
    282        }, 'history.state should be a clone of the original object, not a reference to it (2)');
    283        test(function () {
    284            assert_equals( iframe.contentWindow.history.state, ev.state );
    285        }, 'history.state should be identical to the object passed to the event handler unless history.state is updated');
    286        try {
    287            iframe.contentWindow.persistval = true;
    288            iframe.contentWindow.history.pushState('','', location.href.replace(/\/[^\/]*$/,'/blank3.html') );
    289        } catch(unsuperr) {}
    290        //it's already cached, so this should be very fast if the browser mistakenly loads it
    291        //it should not need to load at all, since it's just a pushed state
    292        setTimeout(tests12,1000);
    293    }
    294    function tests12() {
    295        test(function () {
    296            assert_true( iframe.contentWindow.persistval && !iframe.contentWindow.forreal );
    297        }, 'pushState should not actually load the new URL');
    298        atstep = 3;
    299        iframe.contentWindow.location.reload(); //load the real URL
    300        lasttimer = setTimeout(function () { tests13(false); },3000); //should be cleared by the onload handler long before it has a chance to fire
    301    }
    302    function tests13(passed) {
    303        test(function () {
    304            assert_true( passed, 'expected a load event to fire when reloading the URL from cache, gave up waiting after 3 seconds' );
    305        }, 'reloading a pushed state should actually load the new URL');
    306        //try to make browsers behave when reloading so that the correct URL is recovered - does not always work
    307        iframe.contentWindow.location.href = location.href.replace(/\/[^\/]*$/,'/blank.html');
    308        done();
    309    }
    310 
    311    if( atstep == 1 ) {
    312        //blank2 has loaded
    313        atstep = 2;
    314        //use a timeout, because some browsers intentionally do not add history entries for URL changes in an onload thread
    315        setTimeout(tests1,100);
    316    } else if( atstep == 3 ) {
    317        //blank3 should now have loaded after the .reload() command
    318        atstep = 4;
    319        clearTimeout(lasttimer);
    320        tests13(true);
    321    }
    322 }
    323 
    324 
    325 
    326        </script>
    327    </head>
    328    <body>
    329 
    330        <noscript><p>Enable JavaScript and reload</p></noscript>
    331        <p>WARNING: This test should always be loaded in a new tab/window, to avoid browsers attempting to recover the state of frames, and history length. Do not reload the test.</p>
    332        <div id="log">Running test...</div>
    333        <p><iframe onload="reportload();" src="blank.html"></iframe></p>
    334        <p><iframe src="blank.html"></iframe></p>
    335        <p><iframe src="blank2.html"></iframe></p>
    336        <p><iframe src="blank3.html"></iframe></p>
    337 
    338    </body>
    339 </html>