diff --git a/js/blob.ts b/js/blob.ts index c23b74afb57dc0..092bfaa083cd9f 100644 --- a/js/blob.ts +++ b/js/blob.ts @@ -1,6 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as domTypes from "./dom_types"; -import { containsOnlyASCII } from "./util"; +import { containsOnlyASCII, hasOwnProperty } from "./util"; import { TextEncoder } from "./text_encoding"; export const bytesSymbol = Symbol("bytes"); @@ -91,7 +91,7 @@ export class DenoBlob implements domTypes.Blob { options = options || {}; // Set ending property's default value to "transparent". - if (!options.hasOwnProperty("ending")) { + if (!hasOwnProperty(options, "ending")) { options.ending = "transparent"; } diff --git a/js/blob_test.ts b/js/blob_test.ts index 1e8ec8fbd15663..3c778a0e13a762 100644 --- a/js/blob_test.ts +++ b/js/blob_test.ts @@ -32,4 +32,22 @@ test(function blobSlice() { assertEquals(b4.size, blob.size); }); +test(function blobShouldNotThrowError() { + let hasThrown = false; + + try { + const options1: object = { + ending: "utf8", + hasOwnProperty: "hasOwnProperty" + }; + const options2: object = Object.create(null); + new Blob(["Hello World"], options1); + new Blob(["Hello World"], options2); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); + // TODO(qti3e) Test the stored data in a Blob after implementing FileReader API. diff --git a/js/console_table.ts b/js/console_table.ts index d7cae124c7163a..43819c5fe1a458 100644 --- a/js/console_table.ts +++ b/js/console_table.ts @@ -2,6 +2,7 @@ // Forked from Node's lib/internal/cli_table.js import { TextEncoder } from "./text_encoding"; +import { hasOwnProperty } from "./util"; const encoder = new TextEncoder(); @@ -64,7 +65,7 @@ export function cliTable(head: string[], columns: string[][]): string { if (rows[j] === undefined) { rows[j] = []; } - const value = (rows[j][i] = column.hasOwnProperty(j) ? column[j] : ""); + const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); const width = columnWidths[i] || 0; const counted = countBytes(value); columnWidths[i] = Math.max(width, counted); diff --git a/js/event_target.ts b/js/event_target.ts index cc436865ef6006..87268383e53538 100644 --- a/js/event_target.ts +++ b/js/event_target.ts @@ -1,6 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as domTypes from "./dom_types"; -import { requiredArguments } from "./util"; +import { requiredArguments, hasOwnProperty } from "./util"; /* TODO: This is an incomplete implementation to provide functionality * for Event. A proper spec is still required for a proper Web API. @@ -16,7 +16,7 @@ export class EventTarget implements domTypes.EventTarget { _options?: boolean | domTypes.AddEventListenerOptions ): void { requiredArguments("EventTarget.addEventListener", arguments.length, 2); - if (!this.listeners.hasOwnProperty(type)) { + if (!hasOwnProperty(this.listeners, type)) { this.listeners[type] = []; } if (listener !== null) { @@ -30,7 +30,7 @@ export class EventTarget implements domTypes.EventTarget { _options?: domTypes.EventListenerOptions | boolean ): void { requiredArguments("EventTarget.removeEventListener", arguments.length, 2); - if (this.listeners.hasOwnProperty(type) && callback !== null) { + if (hasOwnProperty(this.listeners, type) && callback !== null) { this.listeners[type] = this.listeners[type].filter( listener => listener !== callback ); @@ -39,7 +39,7 @@ export class EventTarget implements domTypes.EventTarget { public dispatchEvent(event: domTypes.Event): boolean { requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); - if (!this.listeners.hasOwnProperty(event.type)) { + if (!hasOwnProperty(this.listeners, event.type)) { return true; } const stack = this.listeners[event.type].slice(); diff --git a/js/event_target_test.ts b/js/event_target_test.ts index 71c872dabd9227..aedbbf72a5c674 100644 --- a/js/event_target_test.ts +++ b/js/event_target_test.ts @@ -92,3 +92,21 @@ test(function toStringShouldBeWebCompatibility() { const target = new EventTarget(); assertEquals(target.toString(), "[object EventTarget]"); }); + +test(function dispatchEventShouldNotThrowError() { + let hasThrown = false; + + try { + const target = new EventTarget(); + const event = new Event("hasOwnProperty", { + bubbles: true, + cancelable: false + }); + target.addEventListener("hasOwnProperty", () => {}); + target.dispatchEvent(event); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); diff --git a/js/util.ts b/js/util.ts index e6f38c8e17d9a0..b81b96aca265fe 100644 --- a/js/util.ts +++ b/js/util.ts @@ -142,3 +142,33 @@ export function getPrivateValue< } throw new TypeError("Illegal invocation"); } + +/** + * Determines whether an object has a property with the specified name. + * Avoid calling prototype builtin `hasOwnProperty` for two reasons: + * + * 1. `hasOwnProperty` is defined on the object as something else: + * + * const options = { + * ending: 'utf8', + * hasOwnProperty: 'foo' + * }; + * options.hasOwnProperty('ending') // throws a TypeError + * + * 2. The object doesn't inherit from `Object.prototype`: + * + * const options = Object.create(null); + * options.ending = 'utf8'; + * options.hasOwnProperty('ending'); // throws a TypeError + * + * @param obj A Object. + * @param v A property name. + * @see https://eslint.org/docs/rules/no-prototype-builtins + * @internal + */ +export function hasOwnProperty(obj: T, v: PropertyKey): boolean { + if (obj == null) { + return false; + } + return Object.prototype.hasOwnProperty.call(obj, v); +}