Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite JS/HTML using AST-based approach #5273

Merged
merged 103 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
ef94c8d
Add winPropAccessor to security.js, remove other replacers
flotwig Oct 2, 2019
ebedb98
Add start of Cypress.resolveWindowReference
flotwig Oct 2, 2019
53e2ee3
Add regexes for dot and bracket access
flotwig Oct 3, 2019
59494d1
Some security_spec tests pass with new injection
flotwig Oct 3, 2019
d37e02f
Add resolveWindowReference unit tests
flotwig Oct 3, 2019
5981e31
Old security_spec now passes with resolveWindowReference
flotwig Oct 3, 2019
50f3cb1
Inject stub resolveWindowReference so proxy still works outside of Cy…
flotwig Oct 3, 2019
99cc1ab
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Oct 4, 2019
a57aa0d
wip: rewrite HTML + JS with tokenizer
flotwig Oct 4, 2019
2ed5694
Move to using esprima + hyntax to rewrite JS + HTML
flotwig Oct 7, 2019
5bdab6f
remove comment; oneLine makes the whole thing commented
flotwig Oct 8, 2019
4d7cffc
Fix tests, apple.com edge case
flotwig Oct 8, 2019
a5c647c
wip: add getOrSet
flotwig Oct 23, 2019
dec3057
Revert "wip: add getOrSet"
flotwig Oct 23, 2019
0898715
release 3.5.0 [skip ci]
flotwig Oct 23, 2019
b0a6b9c
use recast to replace window property accesses
flotwig Oct 23, 2019
5177666
replace assignments to top properly
flotwig Oct 23, 2019
379e738
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Mar 25, 2020
0983814
fix yarn.lock
flotwig Mar 25, 2020
4d810ca
bump deps
flotwig Mar 31, 2020
fd22366
update integration tests
flotwig Mar 31, 2020
26474eb
remove old security ts?
flotwig Mar 31, 2020
bde5f18
fix integration spec
flotwig Apr 6, 2020
742f499
always ignore js interception failure
flotwig Apr 6, 2020
74c938e
use globalThis instead of window
flotwig Apr 6, 2020
faa6ea7
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Apr 6, 2020
eddce99
add experimentalSourceRewriting flag
flotwig Apr 6, 2020
c5d1e40
restore regex-writer spec
flotwig Apr 6, 2020
9326090
fix types
flotwig Apr 6, 2020
b02aa36
update config_spec
flotwig Apr 6, 2020
99a88c4
add source rewriting spec
flotwig Apr 6, 2020
9c2fa56
cleanup
flotwig Apr 7, 2020
963f327
simplify rewriting logic, move rules into rewriter package
flotwig Apr 7, 2020
b5aba0f
create threaded rewriting tool for non-streaming use
flotwig Apr 8, 2020
7383e72
update @packages/rewriter to use threads for async
flotwig Apr 8, 2020
f8ef782
use async rewriting where convenient
flotwig Apr 8, 2020
e03a318
add worker-shim.js
flotwig Apr 8, 2020
908c8be
add performance info to debug logs
flotwig Apr 8, 2020
8edd974
properly handle +=, -=, ...
flotwig Apr 8, 2020
0f61074
add proxy, rewriter to unit-tests stage
flotwig Apr 9, 2020
76d46aa
cleanup
flotwig Apr 9, 2020
2996280
use parse5 to rewrite HTML, strip SRI
flotwig Apr 9, 2020
b406368
update tests
flotwig Apr 9, 2020
d5e73e5
reorganization, cleanup
flotwig Apr 9, 2020
2fe3408
rewrite ALL parent, top identifiers except in a few cases
flotwig Apr 9, 2020
1c443f6
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Apr 9, 2020
7a8a138
handle many JS edge cases
flotwig Apr 9, 2020
f1cef5b
ensure parse5@5.1.1 is installed
flotwig Apr 10, 2020
1054385
update yarn.lock
flotwig Apr 10, 2020
2b9215f
update tests
flotwig Apr 10, 2020
0497331
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Apr 14, 2020
4282b89
add debugging, add tests
flotwig Apr 14, 2020
47b2c76
add attempted repro for .href issue
flotwig Apr 14, 2020
a7a6a2e
implement source maps + extending inline source maps
flotwig Apr 14, 2020
7bccfff
update opts passing in proxy layer
flotwig Apr 15, 2020
0a84d38
fix sourcemap naming structure
flotwig Apr 16, 2020
2a93793
update tests to account for sourcemaps
flotwig Apr 16, 2020
6d36dc9
sourcemap tests
flotwig Apr 17, 2020
732524d
remote source maps work
flotwig Apr 23, 2020
b8b16db
comment
flotwig Apr 23, 2020
29add0e
update rewriter tests
flotwig Apr 28, 2020
bdfb748
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig Apr 29, 2020
5eb6c3a
clean up TODOs in resolveWindowReference
flotwig Apr 29, 2020
d83fd70
remove @types/nock
flotwig Apr 29, 2020
97dfc3c
clean up todos in deferred-source-map-cache
flotwig Apr 29, 2020
7312d5d
fix rewriter build script
flotwig Apr 29, 2020
91d59da
fix concatStream import
flotwig Apr 29, 2020
3008707
bump expectedresultcount
flotwig Apr 30, 2020
498e381
clean up js-rules
flotwig Apr 30, 2020
0e29be3
threading improvements, workaround for Electron segfault
flotwig Apr 30, 2020
95d23fd
no visit_spec for now
flotwig Apr 30, 2020
6058671
fix 6_visit_spec
flotwig Apr 30, 2020
8b0d0d8
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig May 1, 2020
3cdbef4
update MAX_WORKER_THREADS
flotwig May 4, 2020
becab32
add repro for #3975
flotwig May 4, 2020
83d4365
cleanup
flotwig May 4, 2020
faeef79
cleanup
flotwig May 4, 2020
b3894ed
make better use of namedTypes and builders
flotwig May 4, 2020
ca6ceeb
get rid of the horrific closureDetectionTernary
flotwig May 5, 2020
6694f11
fix #3975, #3994
flotwig May 6, 2020
d1c4d08
add x-sourcemap, sourcemap header support
flotwig May 6, 2020
519d172
snap-shot-it 7.9.3
flotwig May 7, 2020
741a1ec
add deferred-source-map-cache-spec
flotwig May 7, 2020
13fcea1
add tests
flotwig May 7, 2020
a00fd7a
Throw error in driver if AST rewriting fails
flotwig May 7, 2020
4c8f1fb
Fix "location = 'relative-url'"
flotwig May 7, 2020
52cfd06
fix max recursion depth
flotwig May 7, 2020
9267c41
slim down some fixtures
flotwig May 7, 2020
f2137ee
fix window.location usage
flotwig May 7, 2020
5a0320a
don't mess with `frames` at all
flotwig May 7, 2020
53bd96e
no integration tests
flotwig May 7, 2020
bc5bb88
skip testing apple.com for now
flotwig May 7, 2020
d4a9c1c
update wording: regex-based vs. ast-based
flotwig May 7, 2020
7f00cc8
skip real-world tests for now
flotwig May 8, 2020
e772971
add some padding to process.exit workaround
flotwig May 8, 2020
edec2d3
fix resolvers_spec
flotwig May 8, 2020
c4d804e
fix html-spec
flotwig May 8, 2020
2d5d83b
Merge remote-tracking branch 'origin/develop' into issue-5271-fix-win…
flotwig May 8, 2020
43c3f09
cleanup
flotwig May 8, 2020
087becb
Update packages/rewriter/lib/js-rules.ts
flotwig May 8, 2020
d5b88ec
Update packages/driver/src/cypress/resolvers.ts
flotwig May 8, 2020
112869d
just import find by itself
flotwig May 11, 2020
5877e66
privatize typedefs for Cypress.state, remove .gitignore, remove dead …
kuceb May 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ jobs:
# run unit tests from each individual package
- run: yarn test
- verify-mocha-results:
expectedResultCount: 6
expectedResultCount: 8
- store_test_results:
path: /tmp/cypress
# CLI tests generate HTML files with sample CLI command output
Expand Down
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"resolve-pkg": "2.0.0",
"shelljs": "0.8.3",
"sinon": "7.2.2",
"snap-shot-it": "7.9.2",
"snap-shot-it": "7.9.3",
"spawn-mock": "1.0.0",
"strip-ansi": "4.0.0"
},
Expand Down
5 changes: 5 additions & 0 deletions cli/schema/cypress.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@
"type": "boolean",
"default": false,
"description": "If `true`, Cypress will add `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0."
},
"experimentalSourceRewriting": {
"type": "boolean",
"default": false,
"description": "Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm."
}
}
}
19 changes: 17 additions & 2 deletions cli/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ declare namespace Cypress {
*/
log(options: Partial<LogConfig>): Log

/**
* Access and set Cypress's internal state.
*/
state: State

/**
* @see https://on.cypress.io/api/commands
*/
Expand Down Expand Up @@ -478,11 +483,11 @@ declare namespace Cypress {
/**
* Returns a boolean indicating whether an object is a window object.
*/
isWindow(obj: any): boolean
isWindow(obj: any): obj is Window
/**
* Returns a boolean indicating whether an object is a jQuery object.
*/
isJquery(obj: any): boolean
isJquery(obj: any): obj is JQuery
isInputType(element: JQuery | HTMLElement, type: string | string[]): boolean
stringify(element: JQuery | HTMLElement, form: string): string
getElements(element: JQuery): JQuery | HTMLElement[]
Expand Down Expand Up @@ -2207,6 +2212,10 @@ declare namespace Cypress {
log: boolean
}

interface State {
(k: '$autIframe', v?: JQuery<HTMLIFrameElement>): JQuery<HTMLIFrameElement> | undefined
}

/**
* Options that control how long Test Runner is waiting for command to succeed
*/
Expand Down Expand Up @@ -2456,6 +2465,12 @@ declare namespace Cypress {
* @default false
*/
experimentalGetCookiesSameSite: boolean
/**
* Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement
* algorithm.
* @default false
*/
experimentalSourceRewriting: boolean
}

/**
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src --exclude e2e.coffee,e2e.js",
"stop-only-all": "yarn stop-only --folder packages",
"pretest": "yarn ensure-deps",
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{electron,extension,https-proxy,launcher,network,reporter,runner,socket}'\"",
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{electron,extension,https-proxy,launcher,network,proxy,rewriter,reporter,runner,socket}'\"",
"test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{coffee,desktop-gui,driver,root,static,web-config}'\"",
"pretest-e2e": "yarn ensure-deps",
"test-e2e": "lerna exec yarn test-e2e --ignore \"'@packages/{coffee,desktop-gui,driver,root,static,web-config}'\"",
Expand Down Expand Up @@ -184,7 +184,7 @@
"shelljs": "0.8.3",
"shx": "0.3.2",
"sinon": "7.3.2",
"snap-shot-it": "7.9.1",
"snap-shot-it": "7.9.3",
"start-server-and-test": "1.10.8",
"stop-only": "3.0.1",
"strip-ansi": "4.0.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/driver/src/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const $utils = require('./cypress/utils')
const $errUtils = require('./cypress/error_utils')
const $scriptUtils = require('./cypress/script_utils')
const browserInfo = require('./cypress/browser')
const resolvers = require('./cypress/resolvers')
const debug = require('debug')('cypress:driver:cypress')

const proxies = {
Expand Down Expand Up @@ -607,6 +608,8 @@ $Cypress.prototype.Location = $Location
$Cypress.prototype.Log = $Log
$Cypress.prototype.LocalStorage = $LocalStorage
$Cypress.prototype.Mocha = $Mocha
$Cypress.prototype.resolveWindowReference = resolvers.resolveWindowReference
$Cypress.prototype.resolveLocationReference = resolvers.resolveLocationReference
$Cypress.prototype.Mouse = $Mouse
$Cypress.prototype.Runner = $Runner
$Cypress.prototype.Server = $Server
Expand Down
12 changes: 12 additions & 0 deletions packages/driver/src/cypress/error_messages.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,18 @@ module.exports = {
ng:
no_global: "Angular global (`window.angular`) was not found in your window. You cannot use #{cmd('ng')} methods without angular."

proxy:
js_rewriting_failed: """
An error occurred in the Cypress proxy layer while rewriting your source code. This is a bug in Cypress.

JS URL: {{url}}

Original error:

{{errMessage}}
{{errStack}}
"""

reload:
invalid_arguments: {
message: "#{cmd('reload')} can only accept a boolean or `options` as its arguments."
Expand Down
159 changes: 159 additions & 0 deletions packages/driver/src/cypress/resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import _ from 'lodash'
import $Cypress from '../..'

/**
* Fix property reads and writes that could potentially help the AUT to break out of its iframe.
*
* @param currentWindow the value of `globalThis` from the scope of the window reference in question
* @param accessedObject a reference to the object being accessed
* @param accessedProp the property name being accessed (Symbol/number properties are not intercepted)
* @param value the right-hand side of an assignment operation (accessedObject.accessedProp = value)
*/
export function resolveWindowReference (this: typeof $Cypress, currentWindow: Window, accessedObject: Window | any, accessedProp: string, value?: any) {
const { dom, state } = this

const getTargetValue = () => {
const targetValue = accessedObject[accessedProp]

if (dom.isWindow(accessedObject) && accessedProp === 'location') {
const targetLocation = resolveLocationReference(accessedObject)

if (isValPassed) {
return targetLocation.href = value
}

return targetLocation
}

if (_.isFunction(targetValue)) {
return targetValue.bind(accessedObject)
}

return targetValue
}

const setTargetValue = () => {
if (dom.isWindow(accessedObject) && accessedProp === 'location') {
const targetLocation = resolveLocationReference(accessedObject)

return targetLocation.href = value
}

return (accessedObject[accessedProp] = value)
}

const isValPassed = arguments.length === 4

const $autIframe = state('$autIframe')

if (!$autIframe) {
// missing AUT iframe, resolve the property access normally
if (isValPassed) {
return setTargetValue()
}

return getTargetValue()
}

const contentWindow = $autIframe.prop('contentWindow')

if (accessedObject === currentWindow.top) {
// doing a property access on topmost window, adjust accessedObject
accessedObject = contentWindow
}

const targetValue = getTargetValue()

if (!dom.isWindow(targetValue) || dom.isJquery(targetValue)) {
if (isValPassed) {
return setTargetValue()
}

return targetValue
}

// targetValue is a reference to a Window object

if (accessedProp === 'top') {
// note: `isValPassed` is not considered here because `window.top` is readonly
return contentWindow
}

if (accessedProp === 'parent') {
// note: `isValPassed` is not considered here because `window.parent` is readonly
if (targetValue === currentWindow.top) {
return contentWindow
}

return targetValue
}

throw new Error('unhandled resolveWindowReference')
}

/**
* Fix `window.location` usages that would otherwise navigate to the wrong URL.
*
* @param currentWindow the value of `globalThis` from the scope of the location reference in question
flotwig marked this conversation as resolved.
Show resolved Hide resolved
*/
export function resolveLocationReference (currentWindow: Window) {
// @ts-ignore
if (currentWindow.__cypressFakeLocation) {
// @ts-ignore
return currentWindow.__cypressFakeLocation
}

function _resolveHref (href: string) {
const a = currentWindow.document.createElement('a')

a.href = href

// a.href will be resolved into the correct fully-qualified URL
return a.href
}

function assign (href: string) {
return currentWindow.location.assign(_resolveHref(href))
}

function replace (href: string) {
return currentWindow.location.replace(_resolveHref(href))
}

function setHref (href: string) {
return currentWindow.location.href = _resolveHref(href)
}

const locationKeys = Object.keys(currentWindow.location)

const fakeLocation = {}

_.reduce(locationKeys, (acc, cur) => {
// set a dummy value, the proxy will handle sets/gets
acc[cur] = Symbol.for('Proxied')

return acc
}, {})

// @ts-ignore
return currentWindow.__cypressFakeLocation = new Proxy(fakeLocation, {
get (_target, prop, _receiver) {
if (prop === 'assign') {
return assign
}

if (prop === 'replace') {
return replace
}

return currentWindow.location[prop]
},
set (_obj, prop, value) {
if (prop === 'href') {
return setHref(value)
}

return currentWindow.location[prop] = value
},
})
}
Loading