| (async function(testRunner) { |
| var {page, session, dp} = await testRunner.startBlank( |
| `Tests that evaluating V8-embedder callbacks allows side-effect-free methods. Should not crash.`); |
| |
| await session.evaluate(` |
| var global_getSelection = window.getSelection; |
| var global_getComputedStyle = window.getComputedStyle; |
| |
| var namespace = 'http://www.w3.org/1999/xhtml'; |
| document.documentElement.setAttribute('xmlns', namespace); |
| |
| var div = document.createElement('div'); |
| div.setAttribute('attr1', 'attr1-value'); |
| div.setAttribute('attr2', 'attr2-value'); |
| document.body.appendChild(div); |
| |
| var divNamed = document.createElement('div'); |
| divNamed.setAttribute('name', 'div-name'); |
| document.body.appendChild(divNamed); |
| |
| var spanWithClass = document.createElement('span'); |
| spanWithClass.className = 'foo bar'; |
| div.appendChild(spanWithClass); |
| |
| var textNode = document.createTextNode('footext'); |
| var divNoAttrs = document.createElement('div'); |
| |
| var htmlCollection = document.getElementsByTagName('div'); |
| var nodeList = document.getElementsByName('div-name'); |
| var domTokenList = spanWithClass.classList; |
| var bodyStyle = document.body.style; |
| var namedNodeMap = div.attributes; |
| `); |
| await dp.Runtime.evaluate({expression: `var $$result = $$('div')`, includeCommandLineAPI: true}); |
| |
| // Sanity check: test that setters are not allowed on whitelisted methods. |
| await checkHasSideEffect(`document.querySelector('div').x = "foo"`); |
| |
| // Command Line API |
| await checkHasNoSideEffect(`$('div')`); |
| await checkHasNoSideEffect(`$$('div')`); |
| await checkHasNoSideEffect(`$x('//div')`); |
| await checkHasNoSideEffect(`getEventListeners(document)`); |
| await checkHasNoSideEffect(`$.toString()`); |
| await checkHasNoSideEffect(`$$.toString()`); |
| await checkHasNoSideEffect(`$x.toString()`); |
| await checkHasNoSideEffect(`getEventListeners.toString()`); |
| |
| // Unsafe Command Line API |
| await checkHasSideEffect(`monitorEvents()`); |
| await checkHasSideEffect(`unmonitorEvents()`); |
| await checkHasNoSideEffect(`monitorEvents.toString()`); |
| await checkHasNoSideEffect(`unmonitorEvents.toString()`); |
| |
| // Document |
| await checkHasNoSideEffect(`document.getElementsByTagName('div')`); |
| await checkHasNoSideEffect(`document.getElementsByTagNameNS(namespace, 'div')`); |
| await checkHasNoSideEffect(`document.getElementsByClassName('foo')`); |
| await checkHasNoSideEffect(`document.getElementsByName('div-name')`); |
| await checkHasNoSideEffect(`document.hasFocus()`); |
| |
| // DocumentOrShadowRoot |
| await checkHasNoSideEffect(`document.getSelection()`); |
| |
| // DOMTokenList |
| await checkHasNoSideEffect(`domTokenList.contains('foo')`); |
| await checkHasNoSideEffect(`domTokenList.contains({})`); |
| await checkHasNoSideEffect(`domTokenList.contains()`); |
| |
| // Element |
| await checkHasNoSideEffect(`div.getAttributeNames()`); |
| await checkHasNoSideEffect(`divNoAttrs.getAttributeNames()`); |
| await checkHasNoSideEffect(`div.getAttribute()`); |
| await checkHasNoSideEffect(`div.getAttribute('attr1')`); |
| await checkHasNoSideEffect(`div.getAttribute({})`); |
| await checkHasNoSideEffect(`divNoAttrs.getAttribute('attr1')`); |
| await checkHasNoSideEffect(`div.hasAttribute()`); |
| await checkHasNoSideEffect(`div.hasAttribute('attr1')`); |
| await checkHasNoSideEffect(`div.hasAttribute({})`); |
| await checkHasNoSideEffect(`divNoAttrs.hasAttribute('attr1')`); |
| |
| await checkHasNoSideEffect(`div.getAttributeNS(namespace, 'attr1')`); |
| await checkHasNoSideEffect(`div.getAttributeNS(namespace)`); |
| await checkHasNoSideEffect(`div.getAttributeNS()`); |
| await checkHasNoSideEffect(`divNoAttrs.getAttributeNS(namespace, 'attr1')`); |
| await checkHasNoSideEffect(`div.hasAttributeNS(namespace, 'attr1')`); |
| await checkHasNoSideEffect(`div.hasAttributeNS(namespace)`); |
| await checkHasNoSideEffect(`div.hasAttributeNS()`); |
| await checkHasNoSideEffect(`divNoAttrs.hasAttributeNS(namespace, 'attr1')`); |
| await checkHasNoSideEffect(`divNoAttrs.hasAttributeNS(namespace)`); |
| |
| await checkHasNoSideEffect(`div.hasAttributes()`); |
| |
| // Node |
| var testNodes = ['div', 'document', 'textNode']; |
| for (var node of testNodes) { |
| await checkHasNoSideEffect(`${node}.contains(textNode)`); |
| await checkHasNoSideEffect(`${node}.contains()`); |
| await checkHasNoSideEffect(`${node}.contains({})`); |
| await checkHasNoSideEffect(`${node}.querySelector('div')`); |
| await checkHasNoSideEffect(`${node}.querySelectorAll('div')`); |
| await checkHasNoSideEffect(`${node}.hasChildNodes()`); |
| } |
| |
| // Performance |
| await checkHasNoSideEffect(`performance.now()`); |
| |
| // Window |
| await checkHasNoSideEffect(`global_getSelection()`); |
| |
| // Collection getters (e.g. HTMLCollection, NodeList) |
| var indexedCollections = [ |
| 'htmlCollection', 'nodeList', '$$result', 'domTokenList', 'bodyStyle', 'namedNodeMap' |
| ]; |
| for (var collection of indexedCollections) { |
| await checkHasNoSideEffect(`${collection}[0]`); |
| await checkHasNoSideEffect(`${collection}.item(0)`); |
| await checkHasNoSideEffect(`${collection}.length`); |
| } |
| |
| // Named getters (e.g. CSSStyleDeclaration) |
| await checkHasNoSideEffect(`namedNodeMap.attr1`); |
| |
| // May update layout/scroll/style |
| await checkHasNoSideEffect(`div.getBoundingClientRect()`); |
| await checkHasNoSideEffect(`global_getComputedStyle(div)`); |
| |
| testRunner.completeTest(); |
| |
| |
| async function checkHasSideEffect(expression) { |
| return checkExpression(expression, true); |
| } |
| |
| async function checkHasNoSideEffect(expression) { |
| return checkExpression(expression, false); |
| } |
| |
| async function checkExpression(expression, expectSideEffect) { |
| var response = await dp.Runtime.evaluate({expression, throwOnSideEffect: true, includeCommandLineAPI: true}); |
| var hasSideEffect = false; |
| var exceptionDetails = response.result.exceptionDetails; |
| if (exceptionDetails && |
| exceptionDetails.exception.description.startsWith('EvalError: Possible side-effect in debug-evaluate')) |
| hasSideEffect = true; |
| const failed = (hasSideEffect !== expectSideEffect); |
| testRunner.log(`${failed ? 'FAIL: ' : ''}Expression \`${expression}\`\nhas side effect: ${hasSideEffect}, expected: ${expectSideEffect}`); |
| } |
| }) |