Skip to content

Commit

Permalink
feat: added methods handling in optional chaining (#2849)
Browse files Browse the repository at this point in the history
* test: added test in optional chaining for methods

* feat: added handling method in optional chaining

* fix: fixed condition

* refactor: reused function isFunction

* test: fixed test

* test: fixed test
  • Loading branch information
Aleksey28 authored Feb 3, 2023
1 parent 5abd99e commit b91644b
Show file tree
Hide file tree
Showing 21 changed files with 121 additions and 43 deletions.
3 changes: 2 additions & 1 deletion src/client/sandbox/code-instrumentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getProxyUrl, stringifyResourceType } from '../../utils/url';
import urlResolver from '../../utils/url-resolver';
import EventSandbox from '../event';
import MessageSandbox from '../event/message';
import { isFunction } from 'lodash';

export default class CodeInstrumentation extends SandboxBase {
static readonly WRAPPED_EVAL_FN = 'hammerhead|code-instrumentation|wrapped-eval-fn';
Expand Down Expand Up @@ -121,7 +122,7 @@ export default class CodeInstrumentation extends SandboxBase {
return target;

const shouldConvertToArray = !nativeMethods.isArray.call(nativeMethods.Array, target) &&
typeof target[Symbol.iterator] === 'function';
isFunction(target[Symbol.iterator]);

return shouldConvertToArray ? nativeMethods.arrayFrom.call(nativeMethods.Array, target) : target;
},
Expand Down
5 changes: 3 additions & 2 deletions src/client/sandbox/code-instrumentation/location/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import IntegerIdGenerator from '../../../utils/integer-id-generator';
import { createOverriddenDescriptor, overrideStringRepresentation } from '../../../utils/overriding';
import MessageSandbox from '../../event/message';
import { isIE11 } from '../../../utils/browser';
import { isFunction } from '../../../utils/types';

const GET_ORIGIN_CMD = 'hammerhead|command|get-origin';
const ORIGIN_RECEIVED_CMD = 'hammerhead|command|origin-received';
Expand Down Expand Up @@ -263,7 +264,7 @@ export default class LocationWrapper extends LocationInheritor {
const protoKeys = nativeMethods.objectKeys(Location.prototype);
const locWrapper = this;
const rewriteDescriptorFn = (descriptor, key: string) => {
if (typeof descriptor[key] !== 'function')
if (!isFunction(descriptor[key]))
return;

const nativeMethod = descriptor[key];
Expand Down Expand Up @@ -293,7 +294,7 @@ export default class LocationWrapper extends LocationInheritor {
}

// NOTE: window.Location in IE11 is object
if (typeof Location !== 'function')
if (!isFunction(Location))
LocationWrapper.toString = () => Location.toString();
else
overrideStringRepresentation(LocationWrapper, Location);
Expand Down
28 changes: 19 additions & 9 deletions src/client/sandbox/code-instrumentation/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import INSTRUCTION from '../../../processing/script/instruction';
import { shouldInstrumentMethod } from '../../../processing/script/instrumented';
import { isWindow, isLocation } from '../../utils/dom';
import fastApply from '../../utils/fast-apply';
import * as typeUtils from '../../utils/types';
import {
isNullOrUndefined,
inaccessibleTypeToStr,
isFunction,
} from '../../utils/types';
import { getProxyUrl, stringifyResourceType } from '../../utils/url';
import nativeMethods from '../native-methods';
import MessageSandbox from '../event/message';
Expand Down Expand Up @@ -59,7 +63,7 @@ export default class MethodCallInstrumentation extends SandboxBase {
if (win.postMessage === win.postMessage)
return win.postMessage === fn;

return fn && typeof fn.toString === 'function' && fn.toString() === win.postMessage.toString();
return fn && isFunction(fn.toString) && fn.toString() === win.postMessage.toString();
}

attach (window: Window & typeof globalThis) {
Expand All @@ -68,20 +72,26 @@ export default class MethodCallInstrumentation extends SandboxBase {
// NOTE: In Google Chrome, iframes whose src contains html code raise the 'load' event twice.
// So, we need to define code instrumentation functions as 'configurable' so that they can be redefined.
nativeMethods.objectDefineProperty(window, INSTRUCTION.callMethod, {
value: (owner: any, methName: any, args: any[]) => {
if (typeUtils.isNullOrUndefined(owner))
MethodCallInstrumentation._error(`Cannot call method '${methName}' of ${typeUtils.inaccessibleTypeToStr(owner)}`);
value: (owner: any, methName: any, args: any[], optional = false) => {
if (isNullOrUndefined(owner) && !optional)
MethodCallInstrumentation._error(`Cannot call method '${methName}' of ${inaccessibleTypeToStr(owner)}`);

if (typeof owner[methName] !== 'function')
if (!isFunction(owner[methName]) && !optional)
MethodCallInstrumentation._error(`'${methName}' is not a function`);

// OPTIMIZATION: previously we've performed the
// `this.methodWrappers.hasOwnProperty(methName)`
// check which is quite slow. Now we use the
// fast RegExp check instead.
if (typeof methName === 'string' && shouldInstrumentMethod(methName) &&
this.methodWrappers[methName].condition(owner))
return this.methodWrappers[methName].method(owner, args);
if (typeof methName === 'string' && shouldInstrumentMethod(methName)) {
if (optional && !isFunction(owner[methName]))
return void 0;
else if (this.methodWrappers[methName].condition(owner))
return this.methodWrappers[methName].method(owner, args);
}

if (optional && !isFunction(owner[methName]))
return void 0;

return fastApply(owner, methName, args);
},
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/event/drag-and-drop/data-transfer-item.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isFunction } from '../../../utils/types';
import nativeMethods from '../../native-methods';
import DATA_TRANSFER_ITEM_KIND from './data-transfer-item-kind';

Expand All @@ -23,7 +24,7 @@ export default class DataTransferItem {
if (!arguments.length)
throw new Error("Failed to execute 'getAsString' on 'DataTransferItem': 1 argument required, but only 0 present.");

if (typeof callback !== 'function')
if (!isFunction(callback))
return;

if (kind !== DATA_TRANSFER_ITEM_KIND.string)
Expand Down
7 changes: 4 additions & 3 deletions src/client/sandbox/event/focus-blur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import TimersSandbox from '../timers';
import ElementEditingWatcher from './element-editing-watcher';
import { ScrollState } from '../../../typings/client';
import nextTick from '../../utils/next-tick';
import { isFunction } from '../../utils/types';

const INTERNAL_FOCUS_BLUR_FLAG_PREFIX = 'hammerhead|event|internal-';

Expand Down Expand Up @@ -279,7 +280,7 @@ export default class FocusBlurSandbox extends SandboxBase {
if (browserUtils.isMSEdge && el && domUtils.isTextEditableElement(el))
this._eventSimulator.selectionchange(el);

if (typeof callback === 'function')
if (isFunction(callback))
callback();
}

Expand Down Expand Up @@ -403,7 +404,7 @@ export default class FocusBlurSandbox extends SandboxBase {
this.blur(domUtils.getIframeByElement(activeElement), raiseFocusEvent, true, isNativeFocus);
else if (!focusOnChange)
raiseFocusEvent();
else if (typeof callback === 'function')
else if (isFunction(callback))
callback();
}, silent, isNativeFocus, el);
}
Expand Down Expand Up @@ -450,7 +451,7 @@ export default class FocusBlurSandbox extends SandboxBase {
};

this._raiseEvent(el, 'blur', () => {
if (typeof callback === 'function')
if (isFunction(callback))
callback(focusedOnChange);
}, raiseEventParameters);
}
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/event/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
overrideFunction,
overrideStringRepresentation,
} from '../../utils/overriding';
import { isFunction } from '../../utils/types';

const LISTENED_EVENTS = [
'click', 'mousedown', 'mouseup', 'dblclick', 'contextmenu', 'mousemove', 'mouseover', 'mouseout',
Expand Down Expand Up @@ -85,7 +86,7 @@ export default class Listeners extends EventEmitter {
if (Listeners._isIEServiceHandler(listener) || eventCtx.cancelOuterHandlers)
return null;

if (typeof eventCtx.outerHandlersWrapper === 'function')
if (isFunction(eventCtx.outerHandlersWrapper))
return eventCtx.outerHandlersWrapper.call(this, e, listener);

return callEventListener(this, listener, e);
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/event/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { parse as parseJSON, stringify as stringifyJSON } from '../../../utils/j
import Listeners from './listeners';
import UnloadSandbox from './unload';
import settings from '../../settings';
import { isFunction } from '../../utils/types';

enum MessageType { // eslint-disable-line no-shadow
Service = 'hammerhead|service-msg',
Expand Down Expand Up @@ -202,7 +203,7 @@ export default class MessageSandbox extends SandboxBase {
overrideDescriptor(eventPropsOwner, 'onmessage', {
getter: () => this.storedOnMessageHandler,
setter: handler => {
this.storedOnMessageHandler = typeof handler === 'function' ? handler : null;
this.storedOnMessageHandler = isFunction(handler) ? handler : null;

nativeMethods.winOnMessageSetter.call(window, this.storedOnMessageHandler
? e => this._onWindowMessage(e, handler)
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/event/unload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import createPropertyDesc from '../../utils/create-property-desc.js';
import { isFirefox, isIOS } from '../../utils/browser';
import { overrideDescriptor } from '../../utils/overriding';
import Listeners from './listeners';
import { isFunction } from '../../utils/types';

interface EventProperties {
storedReturnValue: string;
Expand Down Expand Up @@ -163,7 +164,7 @@ export default class UnloadSandbox extends SandboxBase {
}

setOnEvent (eventProperties: EventProperties, window: Window, handler) {
if (typeof handler === 'function') {
if (isFunction(handler)) {
eventProperties.storedHandler = handler;

eventProperties.eventPropSetter.call(window, e => this._createEventHandler(eventProperties)(e, handler));
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
isAuthorizationHeader, removeAuthenticatePrefix,
removeAuthorizationPrefix,
} from '../../utils/headers';
import { isFunction } from '../utils/types';

function getCredentialsMode (credentialsOpt: any) {
credentialsOpt = String(credentialsOpt).toLowerCase();
Expand Down Expand Up @@ -184,7 +185,7 @@ export default class FetchSandbox extends SandboxBaseWithDelayedSettings {
overrideFunction(window.Headers.prototype, 'forEach', function (this: Headers, ...args: Parameters<Headers['forEach']>) {
const callback = args[0];

if (typeof callback === 'function') {
if (isFunction(callback)) {
args[0] = function (value, name, headers) {
value = FetchSandbox._removeAuthHeadersPrefix(name, value);

Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/ie-debug.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SandboxBase from './base';
import { isIE } from '../utils/browser';
import { isFunction } from '../utils/types';

const BROWSERTOOLS_CONSOLE_SAFEFUNC = '__BROWSERTOOLS_CONSOLE_SAFEFUNC';

Expand All @@ -13,7 +14,7 @@ export default class IEDebugSandbox extends SandboxBase {
}

_createFuncWrapper (func) {
if (typeof func === 'function') {
if (isFunction(func)) {
return (fn, safeAssert) => {
const ieDebugSandbox = this;

Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/native-methods.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*global Document, Window */
import globalContextInfo from '../utils/global-context-info';
import { isNativeFunction } from '../utils/overriding';
import { isFunction } from '../utils/types';

const NATIVE_CODE_RE = /\[native code]/;

Expand Down Expand Up @@ -1210,7 +1211,7 @@ class NativeMethods {
this.FileList = win.FileList;

// NOTE: non-IE11 case. window.File in IE11 is not constructable.
if (win.File && typeof win.File === 'function')
if (win.File && isFunction(win.File))
this.File = win.File;
}

Expand Down
8 changes: 4 additions & 4 deletions src/client/sandbox/node/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
isIframeWindow,
} from '../../utils/dom';

import { isPrimitiveType } from '../../utils/types';
import { isFunction, isPrimitiveType } from '../../utils/types';
import INTERNAL_ATTRS from '../../../processing/dom/internal-attributes';
import INTERNAL_PROPS from '../../../processing/dom/internal-properties';
import constructorIsCalledWithoutNewKeyword from '../../utils/constructor-is-called-without-new-keyword';
Expand Down Expand Up @@ -385,7 +385,7 @@ export default class WindowSandbox extends SandboxBase {
}

private static _patchFunctionPrototype (fn: Function, ctx: any): void {
if (!ctx || typeof ctx === 'function')
if (!ctx || isFunction(ctx))
return;

const inheritorProto = nativeMethods.objectGetPrototypeOf(ctx);
Expand Down Expand Up @@ -844,8 +844,8 @@ export default class WindowSandbox extends SandboxBase {
return nativeMethods.functionToString.call(this);
});

if (typeof window.history.pushState === 'function'
&& typeof window.history.replaceState === 'function'
if (isFunction(window.history.pushState)
&& isFunction(window.history.replaceState)
&& !this.proxyless) {
const createWrapperForHistoryStateManipulationFn = function (nativeFn) {
return function (this: History, ...args) {
Expand Down
3 changes: 2 additions & 1 deletion src/client/sandbox/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {

import styleProcessor from './../../processing/style';
import { getProxyUrl, parseProxyUrl } from './../utils/url';
import { isFunction } from '../utils/types';

const CSS_STYLE_IS_PROCESSED = 'hammerhead|style|is-processed';
const CSS_STYLE_PROXY_OBJECT = 'hammerhead|style|proxy-object';
Expand Down Expand Up @@ -187,7 +188,7 @@ export default class StyleSandbox extends SandboxBase {
const nativeFn = this.nativeMethods.objectGetOwnPropertyDescriptor.call(window.Object, styleDeclarationProto, prop).value;// eslint-disable-line no-restricted-properties

if (this.nativeMethods.objectHasOwnProperty.call(styleDeclarationProto, prop) &&
typeof nativeFn === 'function') {
isFunction(nativeFn)) {
(styleDeclarationProto[prop] as unknown as Function) = function (this: Window) {
return nativeFn.apply(this[CSS_STYLE_PROXY_TARGET] || this, arguments);
};
Expand Down
4 changes: 2 additions & 2 deletions src/client/utils/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {

import { getNativeQuerySelectorAll } from './query-selector';
import { instanceAndPrototypeToStringAreEqual } from './feature-detection';
import { isNumber } from './types';
import { isFunction, isNumber } from './types';

let scrollbarSize = 0;

Expand Down Expand Up @@ -862,7 +862,7 @@ export function findParent (node, includeSelf = false, predicate) {
node = getParent(node);

while (node) {
if (typeof predicate !== 'function' || predicate(node))
if (!isFunction(predicate) || predicate(node))
return node;

node = getParent(node);
Expand Down
5 changes: 3 additions & 2 deletions src/client/utils/event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import nativeMethods from '../sandbox/native-methods';
import { isFunction } from './types';

const COMPOSED_EVENTS = [
'blur',
Expand Down Expand Up @@ -77,11 +78,11 @@ export function stopPropagation (ev) {
}

export function isObjectEventListener (listener) {
return typeof listener === 'object' && listener && typeof listener.handleEvent === 'function';
return typeof listener === 'object' && listener && isFunction(listener.handleEvent);
}

export function isValidEventListener (listener) {
return typeof listener === 'function' || isObjectEventListener(listener);
return isFunction(listener) || isObjectEventListener(listener);
}

export function callEventListener (ctx, listener, e) {
Expand Down
3 changes: 2 additions & 1 deletion src/client/utils/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import nativeMethods from '../sandbox/native-methods';
import * as domUtils from './dom';
import * as styleUtils from './style';
import { isFirefox, isIE } from './browser';
import { isFunction } from './types';

function getAreaElementRectangle (el, mapContainer) {
const shape = nativeMethods.getAttribute.call(el, 'shape');
Expand Down Expand Up @@ -318,7 +319,7 @@ export function getOffsetPosition (el, roundFn = Math.round) {

let { left, top } = calcOffsetPositionFn(el, borders, offsetPosition, doc, currentIframe);

if (typeof roundFn === 'function') {
if (isFunction(roundFn)) {
left = roundFn(left);
top = roundFn(top);
}
Expand Down
4 changes: 4 additions & 0 deletions src/client/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ export function isNull (obj) {
export function isNumber (val) {
return typeof val === 'number';
}

export function isFunction (val) {
return typeof val === 'function';
}
10 changes: 7 additions & 3 deletions src/processing/script/node-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,15 @@ export function createPropertySetWrapper (propertyName: string, obj: Expression,
return createSimpleCallExpression(setPropertyIdentifier, [obj, createSimpleLiteral(propertyName), value]);
}

export function createMethodCallWrapper (owner: Expression, method: Literal, args: (Expression | SpreadElement)[]): CallExpression {
export function createMethodCallWrapper (owner: Expression, method: Literal, methodArgs: (Expression | SpreadElement)[], optional = false): CallExpression {
const callMethodIdentifier = createIdentifier(INSTRUCTION.callMethod);
const methodArgsArray = createArrayExpression(args);
const methodArgsArray = createArrayExpression(methodArgs);
const args = [owner, method, methodArgsArray];

return createSimpleCallExpression(callMethodIdentifier, [owner, method, methodArgsArray]);
if (optional)
args.push(createSimpleLiteral(optional));

return createSimpleCallExpression(callMethodIdentifier, args);
}

export function createPropertyGetWrapper (propertyName: string, owner: Expression, optional = false): CallExpression {
Expand Down
Loading

0 comments on commit b91644b

Please sign in to comment.