Title: [278585] trunk
Revision
278585
Author
[email protected]
Date
2021-06-07 18:10:23 -0700 (Mon, 07 Jun 2021)

Log Message

Window should behave like a legacy platform object without indexed setter
https://bugs.webkit.org/show_bug.cgi?id=225894

Reviewed by Darin Adler.

LayoutTests/imported/w3c:

* web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache-expected.txt: Added.
* web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html: Added.
* web-platform-tests/html/browsers/the-window-object/window-indexed-properties-expected.txt:
* web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict-expected.txt:
* web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html:
* web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html:

Source/_javascript_Core:

* runtime/TypeError.h:
(JSC::typeError):

Source/WebCore:

This change fixes major interop issue by disallowing expando indexed properties on WindowProxy,
raising TypeError only when needed, which aligns WebKit with Blink and Gecko.

While [[DefineOwnProperty]] [1] and [[Delete]] [2] methods of WindowProxy are implemented
precisely per spec, current [[Set]] [3] algorithm seems to allow invoking setters from
the prototype chain. Blink and Gecko implement stricter semantics by failing early rather
than traversing the prototype chain, as does this patch.

To avoid breaking native apps that either add expando indexed properties to WindowProxy, or
more likely read / write indices of sloppy function's |this| value, which accidently happens
to be a WindowProxy, the new behavior is introduced only for web content and newly-built apps.

Since unlike putByIndex(), deletePropertyByIndex() might be invoked with UINT_MAX, which is
not an array index [4], isIndex() check is required. In future, JSC will be fixed to remove
such checks from all indexed overrides.

DeletePropertySlot::disableCaching() is not called because indexed deletes are not currently
repatched, and once they are, cacheability should be inferred from added type info flags.

Also, removes extra jsDOMWindowGetOwnPropertySlotRestrictedAccess() call for indices, which
is missing from the spec [5]; this is unobservable.

[1] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty (step 2.1)
[2] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete (step 2.1)
[3] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set (step 3)
[4] https://tc39.es/ecma262/#array-index
[5] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty (step 2.5.2)

Tests: imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html
       imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html
       imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html

* bindings/js/JSDOMExceptionHandling.cpp:
(WebCore::makeUnsupportedIndexedSetterErrorMessage):
* bindings/js/JSDOMExceptionHandling.h:
* bindings/js/JSDOMWindowCustom.cpp:
(WebCore::allowsLegacyExpandoIndexedProperties):
(WebCore::JSDOMWindow::getOwnPropertySlotByIndex): Remove outdated comments.
(WebCore::JSDOMWindow::put):
(WebCore::JSDOMWindow::putByIndex): Release scope when calling Base::putByIndex(), which could throw.
(WebCore::JSDOMWindow::deleteProperty):
(WebCore::JSDOMWindow::deletePropertyByIndex):
(WebCore::JSDOMWindow::defineOwnProperty):
* platform/cocoa/VersionChecks.h:

LayoutTests:

* fast/dom/Window/orphaned-frame-access.html:
* fast/frames/iframe-detached-window-still-writable-eval-expected.txt:
* fast/frames/iframe-detached-window-still-writable-eval.html:
* http/tests/security/cross-frame-access-delete-expected.txt:
* http/tests/security/resources/cross-frame-iframe-for-delete-test.html:
* js/dom/dfg-ensure-array-storage-on-window-expected.txt:
* js/dom/indexed-setter-on-global-object-expected.txt:
* js/dom/script-tests/dfg-ensure-array-storage-on-window.js:
* js/dom/script-tests/dfg-ensure-non-array-array-storage-on-window.js:
* js/dom/script-tests/indexed-setter-on-global-object.js:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (278584 => 278585)


--- trunk/LayoutTests/ChangeLog	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/ChangeLog	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,3 +1,21 @@
+2021-06-07  Alexey Shvayka  <[email protected]>
+
+        Window should behave like a legacy platform object without indexed setter
+        https://bugs.webkit.org/show_bug.cgi?id=225894
+
+        Reviewed by Darin Adler.
+
+        * fast/dom/Window/orphaned-frame-access.html:
+        * fast/frames/iframe-detached-window-still-writable-eval-expected.txt:
+        * fast/frames/iframe-detached-window-still-writable-eval.html:
+        * http/tests/security/cross-frame-access-delete-expected.txt:
+        * http/tests/security/resources/cross-frame-iframe-for-delete-test.html:
+        * js/dom/dfg-ensure-array-storage-on-window-expected.txt:
+        * js/dom/indexed-setter-on-global-object-expected.txt:
+        * js/dom/script-tests/dfg-ensure-array-storage-on-window.js:
+        * js/dom/script-tests/dfg-ensure-non-array-array-storage-on-window.js:
+        * js/dom/script-tests/indexed-setter-on-global-object.js:
+
 2021-06-07  Wenson Hsieh  <[email protected]>
 
         Upstream WebKit support for Live Text

Modified: trunk/LayoutTests/fast/dom/Window/orphaned-frame-access.html (278584 => 278585)


--- trunk/LayoutTests/fast/dom/Window/orphaned-frame-access.html	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/fast/dom/Window/orphaned-frame-access.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -17,7 +17,7 @@
     // schedule to run after the frame is null
     setTimeout(function() {
         document.body.appendChild(document.createTextNode(win.test || 'property: FAIL ... '));
-        document.body.appendChild(document.createTextNode(win[20] || 'array: FAIL ... '));
+        document.body.appendChild(document.createTextNode(win[20] === undefined ? 'array: PASS ... ' : 'array: FAIL ... '));
         document.body.appendChild(document.createTextNode(win.Comment ? 'constructor: PASS .... ' : 'constructor: FAIL ... '));
         document.body.appendChild(document.createTextNode(win.postMessage ? 'operation: PASS.' : 'operation: FAIL.'));
         if (window.testRunner)

Modified: trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval-expected.txt (278584 => 278585)


--- trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -4,9 +4,9 @@
 
 
 PASS () => foo is 2
-PASS () => globalThis[0] is 2
+PASS () => globalThis[0] is undefined
 PASS iframeContentWindow.foo is 2
-PASS iframeContentWindow[0] is 2
+PASS iframeContentWindow[0] is undefined
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval.html (278584 => 278585)


--- trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval.html	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/fast/frames/iframe-detached-window-still-writable-eval.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -15,10 +15,10 @@
         foo++;
         globalThis[0]++;
         shouldBe(() => foo, "2");
-        shouldBe(() => globalThis[0], "2");
+        shouldBe(() => globalThis[0], "undefined");
     `);
     shouldBe("iframeContentWindow.foo", "2");
-    shouldBe("iframeContentWindow[0]", "2");
+    shouldBe("iframeContentWindow[0]", "undefined");
 </script>
 </body>
 </html>
\ No newline at end of file

Modified: trunk/LayoutTests/http/tests/security/cross-frame-access-delete-expected.txt (278584 => 278585)


--- trunk/LayoutTests/http/tests/security/cross-frame-access-delete-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/http/tests/security/cross-frame-access-delete-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -22,7 +22,7 @@
 ----- tests for deleting the properties of window, window.history, and window.location cross-domain -----
 
 PASS: window.existingProperty should be 'test value' and is.
-PASS: window[1] should be 'test value' and is.
+PASS: window[1] should be 'undefined' and is.
 PASS: window.history.existingProperty should be 'test value' and is.
 PASS: window.history[1] should be 'test value' and is.
 PASS: window.location.existingProperty should be 'test value' and is.

Modified: trunk/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-delete-test.html (278584 => 278585)


--- trunk/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-delete-test.html	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-delete-test.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -26,7 +26,7 @@
             log("\n----- tests for deleting the properties of window, window.history, and window.location cross-domain -----\n");
 
             shouldBe("window.existingProperty", "'test value'");
-            shouldBe("window[1]", "'test value'");
+            shouldBe("window[1]", "undefined");
             shouldBe("window.history.existingProperty", "'test value'");
             shouldBe("window.history[1]", "'test value'");
             shouldBe("window.location.existingProperty", "'test value'");

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (278584 => 278585)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,3 +1,17 @@
+2021-06-07  Alexey Shvayka  <[email protected]>
+
+        Window should behave like a legacy platform object without indexed setter
+        https://bugs.webkit.org/show_bug.cgi?id=225894
+
+        Reviewed by Darin Adler.
+
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache-expected.txt: Added.
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html: Added.
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties-expected.txt:
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict-expected.txt:
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html:
+        * web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html:
+
 2021-06-07  Imanol Fernandez  <[email protected]>
 
         Use 3D points to map the WebXRBoundedReferenceSpace bounds transform

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache-expected.txt (0 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -0,0 +1,4 @@
+
+PASS Absence of index '0' is not cached
+PASS Presence of index '0' is not cached
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html (0 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Deletion of WindowProxy's indexed properties is not cached</title>
+<link rel="help" href=""
+<script src=""
+<script src=""
+<div id=log></div>
+<script>
+const iframe = document.createElement("iframe");
+iframe.srcdoc = "";
+
+test(() => {
+  assert_equals(window.length, 0);
+  for (let i = 0; i < 1e5; i++) {
+    assert_true(delete window[0]);
+  }
+
+  document.body.append(iframe);
+  assert_false(delete window[0]);
+}, "Absence of index '0' is not cached");
+
+test(() => {
+  assert_equals(window.length, 1);
+  for (let i = 0; i < 1e5; i++) {
+    assert_false(delete window[0]);
+  }
+
+  iframe.remove();
+  assert_true(delete window[0]);
+}, "Presence of index '0' is not cached");
+</script>

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-expected.txt (278584 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,7 +1,9 @@
 
 PASS Indexed properties of the window object (non-strict mode)
 PASS Ensure indexed properties have the correct configuration
-FAIL Indexed properties of the window object (non-strict mode) 1 assert_throws_js: function "() => Object.defineProperty(window, 0, { value: "bar" })" did not throw
-FAIL Indexed properties of the window object (non-strict mode) 2 assert_throws_js: function "() => Object.defineProperty(window, 1, { value: "bar" })" did not throw
+PASS Indexed properties of the window object (non-strict mode) 1
+PASS Indexed properties of the window object (non-strict mode) 2
+PASS Borderline numeric key: 2 ** 32 - 2 is an index
+PASS Borderline numeric key: 2 ** 32 - 1 is not an index
 PASS Indexed properties of the window object (non-strict mode) 3
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict-expected.txt (278584 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,10 +1,8 @@
 
 PASS Indexed properties of the window object (strict mode)
-FAIL Indexed properties of the window object (strict mode) 1 assert_throws_js: function "function () {
-    window[0] = "foo";
-  }" did not throw
-FAIL Indexed properties of the window object (strict mode) 2 assert_throws_js: function "function () {
-    window[1] = "foo";
-  }" did not throw
+PASS Indexed properties of the window object (strict mode) 1
+PASS Indexed properties of the window object (strict mode) 2
+PASS Borderline numeric key: 2 ** 32 - 2 is an index (strict mode)
+PASS Borderline numeric key: 2 ** 32 - 1 is not an index (strict mode)
 PASS Indexed properties of the window object (strict mode) 3
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html (278584 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -44,6 +44,29 @@
 });
 test(function() {
   "use strict";
+  assert_throws_js(TypeError, () => { window[4294967294] = 1; });
+  assert_false(Reflect.set(window, 4294967294, 2));
+  assert_false(Reflect.defineProperty(window, 4294967294, { value: 3 }));
+  assert_throws_js(TypeError, () => Object.defineProperty(window, 4294967294, { get: () => 4 }));
+  assert_equals(window[4294967294], undefined);
+  assert_false(4294967294 in window);
+  assert_true(delete window[4294967294]);
+}, "Borderline numeric key: 2 ** 32 - 2 is an index (strict mode)");
+test(function() {
+  "use strict";
+  window[4294967295] = 1;
+  assert_equals(window[4294967295], 1);
+  assert_true(Reflect.set(window, 4294967295, 2));
+  assert_equals(window[4294967295], 2);
+  assert_true(Reflect.defineProperty(window, 4294967295, { value: 3 }));
+  assert_equals(window[4294967295], 3);
+  Object.defineProperty(window, 4294967295, { get: () => 4 });
+  assert_equals(window[4294967295], 4);
+  assert_true(delete window[4294967295]);
+  assert_false(4294967295 in window);
+}, "Borderline numeric key: 2 ** 32 - 1 is not an index (strict mode)");
+test(function() {
+  "use strict";
   var proto = Window.prototype;
   [-1, 0, 1].forEach(function(idx) {
     assert_false(idx in proto, idx + " in proto");

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html (278584 => 278585)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html	2021-06-08 01:10:23 UTC (rev 278585)
@@ -42,6 +42,27 @@
   assert_equals(delete window[1], true);
 });
 test(function() {
+  window[4294967294] = 1;
+  assert_false(Reflect.set(window, 4294967294, 2));
+  assert_false(Reflect.defineProperty(window, 4294967294, { value: 3 }));
+  assert_throws_js(TypeError, () => Object.defineProperty(window, 4294967294, { get: () => 4 }));
+  assert_equals(window[4294967294], undefined);
+  assert_false(4294967294 in window);
+  assert_true(delete window[4294967294]);
+}, "Borderline numeric key: 2 ** 32 - 2 is an index");
+test(function() {
+  window[4294967295] = 1;
+  assert_equals(window[4294967295], 1);
+  assert_true(Reflect.set(window, 4294967295, 2));
+  assert_equals(window[4294967295], 2);
+  assert_true(Reflect.defineProperty(window, 4294967295, { value: 3 }));
+  assert_equals(window[4294967295], 3);
+  Object.defineProperty(window, 4294967295, { get: () => 4 });
+  assert_equals(window[4294967295], 4);
+  assert_true(delete window[4294967295]);
+  assert_false(4294967295 in window);
+}, "Borderline numeric key: 2 ** 32 - 1 is not an index");
+test(function() {
   var proto = Window.prototype;
   [-1, 0, 1].forEach(function(idx) {
     assert_false(idx in proto, idx + " in proto");

Modified: trunk/LayoutTests/js/dom/dfg-ensure-array-storage-on-window-expected.txt (278584 => 278585)


--- trunk/LayoutTests/js/dom/dfg-ensure-array-storage-on-window-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/js/dom/dfg-ensure-array-storage-on-window-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -3,7 +3,7 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS foo(w) is 1
+PASS foo(w) is NaN
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/js/dom/indexed-setter-on-global-object-expected.txt (278584 => 278585)


--- trunk/LayoutTests/js/dom/indexed-setter-on-global-object-expected.txt	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/js/dom/indexed-setter-on-global-object-expected.txt	2021-06-08 01:10:23 UTC (rev 278585)
@@ -3,7 +3,8 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS thingy is "foo"
+PASS this.__defineSetter__(42, function() {}) threw exception TypeError: Failed to set an indexed property on Window: Indexed property setter is not supported..
+PASS this[42] is undefined
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/js/dom/script-tests/dfg-ensure-array-storage-on-window.js (278584 => 278585)


--- trunk/LayoutTests/js/dom/script-tests/dfg-ensure-array-storage-on-window.js	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/js/dom/script-tests/dfg-ensure-array-storage-on-window.js	2021-06-08 01:10:23 UTC (rev 278585)
@@ -23,4 +23,4 @@
 var w = this;
 w[0] = 1;
 w.length = 1;
-shouldBe("foo(w)", "1");
+shouldBe("foo(w)", "NaN");

Modified: trunk/LayoutTests/js/dom/script-tests/dfg-ensure-non-array-array-storage-on-window.js (278584 => 278585)


--- trunk/LayoutTests/js/dom/script-tests/dfg-ensure-non-array-array-storage-on-window.js	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/js/dom/script-tests/dfg-ensure-non-array-array-storage-on-window.js	2021-06-08 01:10:23 UTC (rev 278585)
@@ -51,10 +51,8 @@
 var w = this;
 w[0] = 1;
 w.length = 1;
-var thingy = false;
-w.__defineSetter__(1, function(value) { thingy = value; });
-shouldBe("foo(w)", "1");
-shouldBe("thingy", "false");
+shouldThrowErrorName("w.__defineSetter__(1, function() {})", "TypeError");
+shouldBe("foo(w)", "NaN");
 
 // At this point we check to make sure that bar doesn't end up either creating array storage for
 // the window proxy, or equally badly, storing to the already created array storage on the proxy
@@ -62,8 +60,7 @@
 // thingy, to detect that for index 1 we fall through the proxy to the real window object.
 bar(w);
 
-shouldBe("thingy", "42");
-shouldBe("foo(w)", "1");
 w.length = 2;
-shouldBe("foo(w)", "0/0");
+shouldBe("w[1]", "");
+shouldBe("foo(w)", "NaN");
 

Modified: trunk/LayoutTests/js/dom/script-tests/indexed-setter-on-global-object.js (278584 => 278585)


--- trunk/LayoutTests/js/dom/script-tests/indexed-setter-on-global-object.js	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/LayoutTests/js/dom/script-tests/indexed-setter-on-global-object.js	2021-06-08 01:10:23 UTC (rev 278585)
@@ -2,13 +2,9 @@
 "Tests that creating an indexed setter on the global object doesn't break things."
 );
 
-var thingy;
+shouldThrowErrorName("this.__defineSetter__(42, function() {})", "TypeError");
 
-this.__defineSetter__(42, function(value) {
-    thingy = value;
-});
-
 this[42] = "foo";
 
-shouldBe("thingy", "\"foo\"");
+shouldBe("this[42]", "undefined");
 

Modified: trunk/Source/_javascript_Core/ChangeLog (278584 => 278585)


--- trunk/Source/_javascript_Core/ChangeLog	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,3 +1,13 @@
+2021-06-07  Alexey Shvayka  <[email protected]>
+
+        Window should behave like a legacy platform object without indexed setter
+        https://bugs.webkit.org/show_bug.cgi?id=225894
+
+        Reviewed by Darin Adler.
+
+        * runtime/TypeError.h:
+        (JSC::typeError):
+
 2021-06-07  Saam Barati  <[email protected]>
 
         Short circuit read modify write nodes emit byte code that uses the wrong locals

Modified: trunk/Source/_javascript_Core/runtime/TypeError.h (278584 => 278585)


--- trunk/Source/_javascript_Core/runtime/TypeError.h	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/_javascript_Core/runtime/TypeError.h	2021-06-08 01:10:23 UTC (rev 278585)
@@ -30,7 +30,8 @@
 
 namespace JSC {
 
-inline bool typeError(JSGlobalObject* globalObject, ThrowScope& scope, bool throwException, ASCIILiteral message)
+template<typename Message>
+inline bool typeError(JSGlobalObject* globalObject, ThrowScope& scope, bool throwException, Message message)
 {
     if (throwException)
         throwTypeError(globalObject, scope, message);

Modified: trunk/Source/WebCore/ChangeLog (278584 => 278585)


--- trunk/Source/WebCore/ChangeLog	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/WebCore/ChangeLog	2021-06-08 01:10:23 UTC (rev 278585)
@@ -1,3 +1,55 @@
+2021-06-07  Alexey Shvayka  <[email protected]>
+
+        Window should behave like a legacy platform object without indexed setter
+        https://bugs.webkit.org/show_bug.cgi?id=225894
+
+        Reviewed by Darin Adler.
+
+        This change fixes major interop issue by disallowing expando indexed properties on WindowProxy,
+        raising TypeError only when needed, which aligns WebKit with Blink and Gecko.
+
+        While [[DefineOwnProperty]] [1] and [[Delete]] [2] methods of WindowProxy are implemented
+        precisely per spec, current [[Set]] [3] algorithm seems to allow invoking setters from
+        the prototype chain. Blink and Gecko implement stricter semantics by failing early rather
+        than traversing the prototype chain, as does this patch.
+
+        To avoid breaking native apps that either add expando indexed properties to WindowProxy, or
+        more likely read / write indices of sloppy function's |this| value, which accidently happens
+        to be a WindowProxy, the new behavior is introduced only for web content and newly-built apps.
+
+        Since unlike putByIndex(), deletePropertyByIndex() might be invoked with UINT_MAX, which is
+        not an array index [4], isIndex() check is required. In future, JSC will be fixed to remove
+        such checks from all indexed overrides.
+
+        DeletePropertySlot::disableCaching() is not called because indexed deletes are not currently
+        repatched, and once they are, cacheability should be inferred from added type info flags.
+
+        Also, removes extra jsDOMWindowGetOwnPropertySlotRestrictedAccess() call for indices, which
+        is missing from the spec [5]; this is unobservable.
+
+        [1] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty (step 2.1)
+        [2] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete (step 2.1)
+        [3] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set (step 3)
+        [4] https://tc39.es/ecma262/#array-index
+        [5] https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty (step 2.5.2)
+
+        Tests: imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-delete-no-cache.html
+               imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties.html
+               imported/w3c/web-platform-tests/html/browsers/the-window-object/window-indexed-properties-strict.html
+
+        * bindings/js/JSDOMExceptionHandling.cpp:
+        (WebCore::makeUnsupportedIndexedSetterErrorMessage):
+        * bindings/js/JSDOMExceptionHandling.h:
+        * bindings/js/JSDOMWindowCustom.cpp:
+        (WebCore::allowsLegacyExpandoIndexedProperties):
+        (WebCore::JSDOMWindow::getOwnPropertySlotByIndex): Remove outdated comments.
+        (WebCore::JSDOMWindow::put):
+        (WebCore::JSDOMWindow::putByIndex): Release scope when calling Base::putByIndex(), which could throw.
+        (WebCore::JSDOMWindow::deleteProperty):
+        (WebCore::JSDOMWindow::deletePropertyByIndex):
+        (WebCore::JSDOMWindow::defineOwnProperty):
+        * platform/cocoa/VersionChecks.h:
+
 2021-06-07  Chris Dumez  <[email protected]>
 
         Drop legacy MainThreadTaskQueue & EventLoopTaskQueue classes

Modified: trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp (278584 => 278585)


--- trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.cpp	2021-06-08 01:10:23 UTC (rev 278585)
@@ -280,6 +280,11 @@
     return makeString("Can only call ", interfaceName, '.', functionName, " on instances of ", interfaceName);
 }
 
+String makeUnsupportedIndexedSetterErrorMessage(const char* interfaceName)
+{
+    return makeString("Failed to set an indexed property on ", interfaceName, ": Indexed property setter is not supported.");
+}
+
 EncodedJSValue throwThisTypeError(JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope, const char* interfaceName, const char* functionName)
 {
     return throwTypeError(lexicalGlobalObject, scope, makeThisTypeErrorMessage(interfaceName, functionName));

Modified: trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.h (278584 => 278585)


--- trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.h	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/WebCore/bindings/js/JSDOMExceptionHandling.h	2021-06-08 01:10:23 UTC (rev 278585)
@@ -55,6 +55,7 @@
 JSC::EncodedJSValue throwConstructorScriptExecutionContextUnavailableError(JSC::JSGlobalObject&, JSC::ThrowScope&, const char* interfaceName);
 
 String makeThisTypeErrorMessage(const char* interfaceName, const char* attributeName);
+String makeUnsupportedIndexedSetterErrorMessage(const char* interfaceName);
 
 WEBCORE_EXPORT JSC::EncodedJSValue throwThisTypeError(JSC::JSGlobalObject&, JSC::ThrowScope&, const char* interfaceName, const char* functionName);
 

Modified: trunk/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp (278584 => 278585)


--- trunk/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp	2021-06-08 01:10:23 UTC (rev 278585)
@@ -59,6 +59,10 @@
 #include "JSWebKitNamespace.h"
 #endif
 
+#if PLATFORM(COCOA)
+#include "VersionChecks.h"
+#endif
+
 namespace WebCore {
 using namespace JSC;
 
@@ -91,6 +95,18 @@
 }
 #endif
 
+static ALWAYS_INLINE bool allowsLegacyExpandoIndexedProperties()
+{
+#if PLATFORM(COCOA)
+    // Given that WindowProxy is the default |this| value for sloppy mode functions, it's hard to prove
+    // that older iOS and macOS apps don't accidentally depend on this behavior, so we keep it for them.
+    static bool requiresQuirk = !linkedOnOrAfter(SDKVersion::FirstWithoutExpandoIndexedPropertiesOnWindow);
+    return requiresQuirk;
+#else
+    return false;
+#endif
+}
+
 template <DOMWindowType windowType>
 bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject, AbstractDOMWindow& window, JSGlobalObject& lexicalGlobalObject, PropertyName propertyName, PropertySlot& slot, const String& errorMessage)
 {
@@ -218,13 +234,8 @@
     return false;
 }
 
-// Property access sequence is:
-// (1) indexed properties,
-// (2) regular own properties,
-// (3) named properties (in fact, these shouldn't be on the window, should be on the NPO).
 bool JSDOMWindow::getOwnPropertySlotByIndex(JSObject* object, JSGlobalObject* lexicalGlobalObject, unsigned index, PropertySlot& slot)
 {
-    VM& vm = lexicalGlobalObject->vm();
     auto* thisObject = jsCast<JSDOMWindow*>(object);
     auto& window = thisObject->wrapped();
     auto* frame = window.frame();
@@ -232,7 +243,6 @@
     // Indexed getters take precendence over regular properties, so caching would be invalid.
     slot.disableCaching();
 
-    // (1) First, indexed properties.
     // These are also allowed cross-origin, so come before the access check.
     if (frame && index < frame->tree().scopedChildCount()) {
         slot.setValue(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly), toJS(lexicalGlobalObject, frame->tree().scopedChild(index)->document()->domWindow()));
@@ -239,13 +249,11 @@
         return true;
     }
 
-    // Hand off all cross-domain/frameless access to jsDOMWindowGetOwnPropertySlotRestrictedAccess.
-    String errorMessage;
-    if (!BindingSecurity::shouldAllowAccessToDOMWindow(*lexicalGlobalObject, window, errorMessage))
-        return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, window, *lexicalGlobalObject, Identifier::from(vm, index), slot, errorMessage);
-
-    // (2) Regular own properties.
-    return Base::getOwnPropertySlotByIndex(thisObject, lexicalGlobalObject, index, slot);
+    if (!BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, window, ThrowSecurityError))
+        return false;
+    if (allowsLegacyExpandoIndexedProperties())
+        return Base::getOwnPropertySlotByIndex(thisObject, lexicalGlobalObject, index, slot);
+    return false;
 }
 
 void JSDOMWindow::doPutPropertySecurityCheck(JSObject* cell, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, PutPropertySlot&)
@@ -285,6 +293,8 @@
         return false;
     }
 
+    if (parseIndex(propertyName) && !allowsLegacyExpandoIndexedProperties())
+        return typeError(lexicalGlobalObject, scope, slot.isStrictMode(), makeUnsupportedIndexedSetterErrorMessage("Window"));
     RELEASE_AND_RETURN(scope, Base::put(thisObject, lexicalGlobalObject, propertyName, value, slot));
 }
 
@@ -300,7 +310,12 @@
         return false;
     }
     
-    return Base::putByIndex(thisObject, lexicalGlobalObject, index, value, shouldThrow);
+    if (allowsLegacyExpandoIndexedProperties()) {
+        if (auto* document = thisObject->wrapped().document())
+            document->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, "Adding expando indexed properties to 'window' was a non-standard behavior that is now removed."_s);
+        RELEASE_AND_RETURN(scope, Base::putByIndex(thisObject, lexicalGlobalObject, index, value, shouldThrow));
+    }
+    return typeError(lexicalGlobalObject, scope, shouldThrow, makeUnsupportedIndexedSetterErrorMessage("Window"));
 }
 
 bool JSDOMWindow::deleteProperty(JSCell* cell, JSGlobalObject* lexicalGlobalObject, PropertyName propertyName, DeletePropertySlot& slot)
@@ -309,6 +324,10 @@
     // Only allow deleting properties by frames in the same origin.
     if (!BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, thisObject->wrapped(), ThrowSecurityError))
         return false;
+    if (std::optional<uint32_t> index = parseIndex(propertyName)) {
+        if (!allowsLegacyExpandoIndexedProperties())
+            return index.value() >= thisObject->wrapped().length();
+    }
     return Base::deleteProperty(thisObject, lexicalGlobalObject, propertyName, slot);
 }
 
@@ -318,6 +337,8 @@
     // Only allow deleting properties by frames in the same origin.
     if (!BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, thisObject->wrapped(), ThrowSecurityError))
         return false;
+    if (isIndex(propertyName) && !allowsLegacyExpandoIndexedProperties())
+        return propertyName >= thisObject->wrapped().length();
     return Base::deletePropertyByIndex(thisObject, lexicalGlobalObject, propertyName);
 }
 
@@ -409,12 +430,18 @@
 
 bool JSDOMWindow::defineOwnProperty(JSC::JSObject* object, JSC::JSGlobalObject* lexicalGlobalObject, JSC::PropertyName propertyName, const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
 {
+    VM& vm = lexicalGlobalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
     // Only allow defining properties in this way by frames in the same origin, as it allows setters to be introduced.
     if (!BindingSecurity::shouldAllowAccessToDOMWindow(lexicalGlobalObject, thisObject->wrapped(), ThrowSecurityError))
         return false;
+    if (parseIndex(propertyName) && !allowsLegacyExpandoIndexedProperties())
+        return typeError(lexicalGlobalObject, scope, shouldThrow, makeUnsupportedIndexedSetterErrorMessage("Window"));
+    scope.release();
 
-    auto& builtinNames = static_cast<JSVMClientData*>(lexicalGlobalObject->vm().clientData)->builtinNames();
+    auto& builtinNames = static_cast<JSVMClientData*>(vm.clientData)->builtinNames();
     if (propertyName == builtinNames.documentPublicName() || propertyName == builtinNames.windowPublicName())
         return JSObject::defineOwnProperty(thisObject, lexicalGlobalObject, propertyName, descriptor, shouldThrow);
 

Modified: trunk/Source/WebCore/platform/cocoa/VersionChecks.h (278584 => 278585)


--- trunk/Source/WebCore/platform/cocoa/VersionChecks.h	2021-06-08 01:04:17 UTC (rev 278584)
+++ trunk/Source/WebCore/platform/cocoa/VersionChecks.h	2021-06-08 01:10:23 UTC (rev 278585)
@@ -73,6 +73,7 @@
     FirstWithSharedNetworkProcess = DYLD_IOS_VERSION_14_5,
     FirstWithBlankViewOnJSPrompt = DYLD_IOS_VERSION_14_5,
     FirstWithApplicationCacheDisabledByDefault = DYLD_IOS_VERSION_15_0,
+    FirstWithoutExpandoIndexedPropertiesOnWindow = DYLD_IOS_VERSION_15_0,
 #elif PLATFORM(MAC)
     FirstWithNetworkCache = DYLD_MACOSX_VERSION_10_11,
     FirstWithExceptionsForDuplicateCompletionHandlerCalls = DYLD_MACOSX_VERSION_10_13,
@@ -96,6 +97,7 @@
     FirstWithDOMWindowReuseRestriction = DYLD_MACOSX_VERSION_11_3,
     FirstWithBlankViewOnJSPrompt = DYLD_MACOSX_VERSION_11_3,
     FirstWithApplicationCacheDisabledByDefault = DYLD_MACOSX_VERSION_12_00,
+    FirstWithoutExpandoIndexedPropertiesOnWindow = DYLD_MACOSX_VERSION_12_00,
 #endif
 };
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to