Revert "Consolidate pointer attribute testing"
diff --git a/pointerevents/pointerevent_attributes.html b/pointerevents/pointerevent_attributes.html
deleted file mode 100644
index 7675dd5..0000000
--- a/pointerevents/pointerevent_attributes.html
+++ /dev/null
@@ -1,295 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <title>Pointer Events properties tests</title>
-  <meta name="viewport" content="width=device-width">
-  <meta name="variant" content="?mouse">
-  <meta name="variant" content="?pen">
-  <meta name="variant" content="?mouse-right">
-  <meta name="variant" content="?pen-right">
-  <meta name="variant" content="?touch">
-  <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-  <style>
-    html {
-      touch-action: none;
-    }
-
-    div {
-      padding: 0;
-    }
-
-    #square1 {
-      background-color: green;
-      border: 1px solid black;
-      height: 50px;
-      width: 50px;
-      margin-bottom: 3px;
-      display: inline-block;
-    }
-
-    #innerFrame {
-      position: relative;
-      margin-bottom: 3px;
-      margin-left: 0;
-      top: 0;
-      left: 0;
-    }
-  </style>
-</head>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/testdriver.js"></script>
-<script src="/resources/testdriver-actions.js"></script>
-<script src="/resources/testdriver-vendor.js"></script>
-<!-- Additional helper script for common checks across event types -->
-<script type="text/javascript" src="pointerevent_support.js"></script>
-<script>
-let frameLoaded = undefined;
-const frameLoadedPromise = new Promise(resolve => {
-  frameLoaded = resolve;
-});
-</script>
-<body>
-  <div id="square1"></div>
-  <div>
-    <iframe onLoad = "frameLoaded()" id="innerFrame" srcdoc='
-      <style>
-        html {
-          touch-action: none;
-        }
-        #square2 {
-          background-color: green;
-          border: 1px solid black;
-          height: 50px;
-          width: 50px;
-          display: inline-block;
-        }
-      </style>
-      <body>
-        <div id="square2"></div>
-      </body>
-    '></iframe>
-  </div>
-  <!-- Used to detect a sentinel event. Once triggered, all other events must
-       have been processed. -->
-  <div>
-    <button id="done">done</button>
-  </div>
-</body>
-<script>
-  window.onload = runTests();
-
-  async function runTests() {
-
-    const queryStringFragments = location.search.substring(1).split('-');
-    const pointerType = queryStringFragments[0];
-    const button = queryStringFragments[1];
-
-    const eventList = [
-      'pointerover',
-      'pointerenter',
-      'pointerdown',
-      'pointerup',
-      'pointerout',
-      'pointerleave',
-      'pointermove'
-    ];
-
-    function injectScrubGesture(element) {
-      const doneButton = document.getElementById('done');
-      const actions = new test_driver.Actions();
-
-      let buttonArguments =
-        (button == 'right') ? { button: actions.ButtonType.RIGHT }
-                            : undefined;
-
-      // The following comments refer to the first event of each type since
-      // that is what is being validated in the test.
-      return actions
-        .addPointer('pointer1', pointerType)
-        // The pointermove, pointerover and pointerenter events will be
-        // triggered here with a hover pointer.
-        .pointerMove(0, -20, { origin: element })
-        // Pointerdown triggers pointerover, pointerenter with a non-hover
-        // pointer type.
-        .pointerDown(buttonArguments)
-        // This move triggers pointermove with a non-hover pointer-type.
-        .pointerMove(0, 20, { origin: element })
-        // The pointerout and pointerleave events are triggered here with a
-        // touch pointer.
-        .pointerUp(buttonArguments)
-        // An addition move outside of the target bounds is required to trigger
-        // pointerout & pointerleave events with a hover pointer.
-        .pointerMove(0, 0)
-        .send();
-    }
-
-    // Processing a click or tap on the done button is used to signal that all
-    // other events should have beem handled. This is used to catch unhandled
-    // events that would otherwise result in a timeout.
-    function clickOrTapDone() {
-      const doneButton = document.getElementById('done');
-      const pointerupPromise = getEvent('pointerup', doneButton);
-      const actionPromise = new test_driver.Actions()
-        .addPointer('pointer1', 'touch')
-        .pointerMove(0, 0, {origin: doneButton})
-        .pointerDown()
-        .pointerUp()
-        .send();
-      return actionPromise.then(pointerupPromise);
-    }
-
-    function verifyButtonAttributes(event) {
-      let downButton, upButton, downButtons, upButtons;
-      if (button == 'right') {
-         downButton = 2;
-         downButtons = 2;
-         upButton = 2;
-         upButtons = 0;
-      } else {
-        // defaults to left button click
-        downButton = 0;
-        downButtons = 1;
-        upButton = 0;
-        upButtons = 0;
-      }
-      const expectationsHover = {
-        // Pointer over, enter, and move are processed before the button press.
-        pointerover:  { button: -1, buttons: 0 },
-        pointerenter: { button:  -1, buttons: 0 },
-        pointermove:  { button:  -1, buttons: 0 },
-        // Button status changes on pointer down and up.
-        pointerdown:  { button:  downButton, buttons: downButtons },
-        pointerup:    { button:  upButton, buttons: upButtons },
-        // Pointer out and leave are processed after the button release.
-        pointerout:   { button:  -1, buttons: 0 },
-        pointerleave: { button:  -1, buttons: 0 }
-      };
-      const expectationsNoHover = {
-        // We don't see pointer events except during a touch gesture.
-        // Move is the only pointer event where the "button" click state is not
-        // changing. All other pointer events are associated with the start or
-        // end of a touch gesture.
-        pointerover:  { button:  0, buttons: 1 },
-        pointerenter: { button:  0, buttons: 1 },
-        pointerdown:  { button:  0, buttons: 1 },
-        pointermove:  { button: -1, buttons: 1 },
-        pointerup:    { button:  0, buttons: 0 },
-        pointerout:   { button:  0, buttons: 0 },
-        pointerleave: { button:  0, buttons: 0 }
-      };
-      const expectations =
-          (pointerType == 'touch') ? expectationsNoHover : expectationsHover;
-
-      assert_equals(event.button, expectations[event.type].button,
-                    `Button attribute on ${event.type}`);
-      assert_equals(event.buttons, expectations[event.type].buttons,
-                    `Buttons attribute on ${event.type}`);
-    }
-
-    function verifyPosition(event) {
-      const boundingRect = event.target.getBoundingClientRect();
-
-      // With a touch pointer type, the pointerout and pointerleave will trigger
-      // on pointerup while clientX and clientY are still within the target's
-      // bounds. With a hover pointer, these events will be triggered only after
-      // clientX or clientY are out of the target's bounds.
-      if (pointerType != 'touch' &&
-          (event.type == 'pointerout' || event.type == 'pointerleave')) {
-        assert_true(
-            boundingRect.left > event.clientX ||
-            boundingRect.right < event.clientX ||
-            boundingRect.top > event.clientY ||
-            boundingRect.bottom < event.clientY,
-            `clientX/clientY is outside the element bounds for ${event.type} event`);
-      } else {
-        assert_true(
-            boundingRect.left <= event.clientX &&
-            boundingRect.right >= event.clientX,
-            `clientX is within the expected range for ${event.type} event`);
-        assert_true(
-            boundingRect.top <= event.clientY &&
-            boundingRect.bottom >= event.clientY,
-            `clientY is within the expected range for ${event.type} event`);
-      }
-    }
-
-    function verifyEventAttributes(event, testNamePrefix) {
-       verifyButtonAttributes(event);
-       verifyPosition(event);
-       assert_true(event.isPrimary, 'isPrimary attribute is true');
-       check_PointerEvent(event, testNamePrefix);
-    }
-
-    function pointerPromise(test, testNamePrefix, type, target) {
-      let rejectCallback = undefined;
-      promise = new Promise((resolve, reject) => {
-        // Store a reference to the promise rejection functions, which would
-        // otherwise not be visible outside the promise object. If the callback
-        // remains set when the deadline is reached, it means that the promise
-        // will not get resolved and should be rejected.
-        rejectCallback = reject;
-        const pointerEventListener = event => {
-          rejectCallback = undefined;
-          assert_equals(event.type, type, `type attribute for ${type} event`);
-          event.preventDefault();
-          resolve(event);
-        };
-        target.addEventListener(type, pointerEventListener, { once: true });
-        test.add_cleanup(() => {
-          // Just in case of an assert prior to the events being triggered.
-          document.removeEventListener(type, pointerEventListener,
-                                       { once: true });
-        });
-      }).then(result => { verifyEventAttributes(result, testNamePrefix); },
-              error => { assert_unreached(error); });
-      promise.deadlineReached = () => {
-        // If the event has not been received, the promise will not be
-        // fulfilled, leading to a timeout. Reject the promise if still pending.
-        if (rejectCallback) {
-          rejectCallback(`missing ${type} event`);
-        }
-      }
-      return promise;
-    }
-
-    async function runPointerEventsTest(test, testNamePrefix, target) {
-      assert_true(['mouse', 'pen', 'touch'].indexOf(pointerType) >= 0,
-                  `Unexpected pointer type (${pointerType})`);
-
-      const promises = [];
-      eventList.forEach(type => {
-        // Create a promise for each event type. If clicking on the done button
-        // is detected before an event's promise is resolved, then the promise
-        // will be rejected. Otherwise, the attributes for the event are
-        // verified.
-        promises.push(pointerPromise(test, testNamePrefix, type, target));
-      });
-
-      await injectScrubGesture(target);
-
-      // The injected gestures consist of a shrub on a button followed by a
-      // click on the done button. The promise is only resolved after the
-      // done click is detected. At this stage all other events must have been
-      // processed. Any unresolved promises in the list will be rejected to
-      //  avoid a test timeout. The rejection will trigger a test failure.
-      await clickOrTapDone().then(promises.map(p => p.deadlineReached()));
-
-      // Once all promises are resolved, all event attributes have been
-      // successfully verified.
-      return Promise.all(promises);
-    }
-
-    promise_test(t => {
-      const square1 = document.getElementById('square1');
-      return runPointerEventsTest(t, '', square1);
-    }, 'Test pointer events in the main document');
-
-    promise_test(async t => {
-      const innerFrame = document.getElementById('innerFrame');
-      await frameLoadedPromise;
-      const square2 = innerFrame.contentDocument.getElementById('square2');
-      return runPointerEventsTest(t, 'Inner Frame', square2);
-    }, 'Test pointer events in an iframe');
-  }
-</script>
diff --git a/pointerevents/pointerevent_attributes_hoverable_pointers.html b/pointerevents/pointerevent_attributes_hoverable_pointers.html
new file mode 100644
index 0000000..c8443d9
--- /dev/null
+++ b/pointerevents/pointerevent_attributes_hoverable_pointers.html
@@ -0,0 +1,158 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Pointer Events properties tests</title>
+        <meta name="viewport" content="width=device-width">
+        <meta name="variant" content="?mouse">
+        <meta name="variant" content="?pen">
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
+        <!-- Additional helper script for common checks across event types -->
+        <script type="text/javascript" src="pointerevent_support.js"></script>
+        <script>
+            var input_pointertype = location.search.substring(1);
+            var detected_pointertypes = {};
+            var detected_eventTypes = {};
+            var eventList = ['pointerover', 'pointerenter', 'pointermove', 'pointerdown', 'pointerup', 'pointerout', 'pointerleave'];
+            var expectedPointerId = NaN;
+
+            function resetTestState() {
+                detected_eventTypes = {};
+                document.getElementById("square1").style.visibility = 'visible';
+                document.getElementById('innerFrame').contentDocument.getElementById("square2").style.visibility = 'hidden';
+                expectedPointerId = NaN;
+            }
+            function checkPointerEventAttributes(event, targetBoundingClientRect, testNamePrefix) {
+                if (detected_eventTypes[event.type])
+                    return;
+                var expectedEventType =  eventList[Object.keys(detected_eventTypes).length];
+                detected_eventTypes[event.type] = true;
+                var pointerTestName = (testNamePrefix ? testNamePrefix + ' ' : '')
+                    + expectedPointerType + ' ' + expectedEventType;
+
+                detected_pointertypes[event.pointerType] = true;
+
+                test(function() {
+                    assert_equals(event.type, expectedEventType);
+                }, pointerTestName + ".type should be " + expectedEventType);
+
+                // Test button and buttons
+                if (event.type == 'pointerdown') {
+                    test(function() {
+                        assert_equals(event.button, 0);
+                    }, pointerTestName + ".button attribute is 0 when left mouse button is pressed.");
+                    test(function() {
+                        assert_equals(event.buttons, 1);
+                    }, pointerTestName + ".buttons attribute is 1 when left mouse button is pressed.");
+                } else if (event.type == 'pointerup') {
+                    test(function() {
+                        assert_equals(event.button, 0);
+                    }, pointerTestName + ".button attribute is 0 when left mouse button is just released.");
+                    test(function() {
+                        assert_equals(event.buttons, 0);
+                    }, pointerTestName + ".buttons attribute is 0 when left mouse button is just released.");
+                } else {
+                    test(function() {
+                        assert_equals(event.button, -1);
+                    }, pointerTestName + ".button is -1 when mouse buttons are in released state.");
+                    test(function() {
+                        assert_equals(event.buttons, 0);
+                    }, pointerTestName + ".buttons is 0 when mouse buttons are in released state.");
+                }
+
+                // Test clientX and clientY
+                if (event.type != 'pointerout' && event.type != 'pointerleave' ) {
+                    test(function () {
+                        assert_greater_than_equal(event.clientX, targetBoundingClientRect.left, "clientX should be greater or equal than left of the box");
+                        assert_greater_than_equal(event.clientY, targetBoundingClientRect.top, "clientY should be greater or equal than top of the box");
+                        assert_less_than_equal(event.clientX, targetBoundingClientRect.right, "clientX should be less or equal than right of the box");
+                        assert_less_than_equal(event.clientY, targetBoundingClientRect.bottom, "clientY should be less or equal than bottom of the box");
+                    }, pointerTestName + ".clientX and .clientY attributes are correct.");
+                } else {
+                    test(function () {
+                        assert_true(event.clientX < targetBoundingClientRect.left || event.clientX >= targetBoundingClientRect.right || event.clientY < targetBoundingClientRect.top || event.clientY >= targetBoundingClientRect.bottom);
+                    }, pointerTestName + ".clientX and .clientY attributes are correct.");
+                }
+
+                check_PointerEvent(event, testNamePrefix);
+
+                // Test isPrimary
+                test(function () {
+                    assert_equals(event.isPrimary, true);
+                }, pointerTestName + ".isPrimary attribute is true.");
+
+                // Test pointerId value
+                if (isNaN(expectedPointerId)) {
+                    expectedPointerId = event.pointerId;
+                } else {
+                    test(function () {
+                        assert_equals(event.pointerId, expectedPointerId);
+                    }, pointerTestName + ".pointerId should be the same as previous pointer events for this active pointer.");
+                }
+            }
+
+            async function run() {
+                var test_pointerEvent = setup_pointerevent_test("pointerevent attributes", [input_pointertype]);
+                var square1 = document.getElementById("square1");
+                var rectSquare1 = square1.getBoundingClientRect();
+                var innerFrame = document.getElementById('innerFrame');
+                var square2 = innerFrame.contentDocument.getElementById('square2');
+                var rectSquare2 = square2.getBoundingClientRect();
+
+                eventList.forEach(function(eventName) {
+                    on_event(square1, eventName, function (event) {
+                        if (square1.style.visibility == 'hidden')
+                            return;
+                        checkPointerEventAttributes(event, rectSquare1, "");
+                        if (Object.keys(detected_eventTypes).length == eventList.length) {
+                            square1.style.visibility = 'hidden';
+                            detected_eventTypes = {};
+                            square2.style.visibility = 'visible';
+                            expectedPointerId = NaN;
+                        }
+                    });
+                    on_event(square2, eventName, function (event) {
+                        checkPointerEventAttributes(event, rectSquare2, "Inner frame ");
+                        if (Object.keys(detected_eventTypes).length == eventList.length) {
+                            square2.style.visibility = 'hidden';
+                            test_pointerEvent.done();
+                        }
+                    });
+                });
+
+                // Inject mouse or pen inputs.
+                await clickInTarget(input_pointertype, square1);
+                await moveToDocument(input_pointertype);
+                await clickInTarget(input_pointertype, square2);
+                await moveToDocument(input_pointertype);
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events hoverable pointer attributes test</h1>
+        <h2 id="pointerTypeDescription"></h2>
+        <h4>
+            Test Description: This test checks the properties of hoverable pointer events. If you are using hoverable pen don't leave the range of digitizer while doing the instructions.
+            <ol>
+                 <li>Move your pointer over the black square and click on it.</li>
+                 <li>Then move it off the black square so that it disappears.</li>
+                 <li>When red square appears move your pointer over the red square and click on it.</li>
+                 <li>Then move it off the red square.</li>
+            </ol>
+
+            Test passes if the proper behavior of the events is observed.
+        </h4>
+        <div id="square1" class="square"></div>
+        <iframe id="innerFrame" src="resources/pointerevent_attributes_hoverable_pointers-iframe.html"></iframe>
+        <div class="spacer"></div>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+            <p>Refresh the page to run the tests again with a different pointer type.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
diff --git a/pointerevents/pointerevent_attributes_hoverable_rightbutton.html b/pointerevents/pointerevent_attributes_hoverable_rightbutton.html
new file mode 100644
index 0000000..9a449ed
--- /dev/null
+++ b/pointerevents/pointerevent_attributes_hoverable_rightbutton.html
@@ -0,0 +1,168 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Pointer Events properties tests</title>
+    <meta name="viewport" content="width=device-width">
+    <meta name="variant" content="?mouse">
+    <meta name="variant" content="?pen">
+    <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
+    <!-- Additional helper script for common checks across event types -->
+    <script type="text/javascript" src="pointerevent_support.js"></script>
+    <script>
+      var input_pointertype = location.search.substring(1);
+      var detected_eventTypes = {};
+      var eventList = [
+          'pointerover', 'pointerenter', 'pointermove', 'pointerdown',
+          'pointerup', 'pointerout', 'pointerleave'
+      ];
+      var expectedPointerId = NaN;
+
+      function resetTestState() {
+        detected_eventTypes = {};
+        document.getElementById("square1").style.visibility = 'visible';
+        document.getElementById('innerFrame').contentDocument
+            .getElementById("square2").style.visibility = 'hidden';
+        expectedPointerId = NaN;
+      }
+      function checkPointerEventAttributes(
+          event, targetBoundingClientRect, testNamePrefix) {
+        if (detected_eventTypes[event.type])
+          return;
+        var expectedEventType =
+            eventList[Object.keys(detected_eventTypes).length];
+        detected_eventTypes[event.type] = true;
+        var pointerTestName = (testNamePrefix ? testNamePrefix + ' ' : '')
+          + expectedPointerType + ' ' + expectedEventType;
+
+        test(function() {
+          assert_equals(event.type, expectedEventType);
+        }, pointerTestName + "'s type should be " + expectedEventType);
+
+        // Test button and buttons
+        if (event.type == 'pointerdown') {
+          test(function() {
+            assert_equals(event.button, 2);
+          }, pointerTestName + "'s button attribute is 2 when right mouse "
+              + "button is pressed.");
+          test(function() {
+            assert_equals(event.buttons, 2);
+          }, pointerTestName + "'s buttons attribute is 2 when right mouse "
+              + "button is pressed.");
+        } else if (event.type == 'pointerup') {
+          test(function() {
+            assert_equals(event.button, 2);
+          }, pointerTestName + "'s button attribute is 0 when right mouse "
+              + "button is just released.");
+          test(function() {
+            assert_equals(event.buttons, 0);
+          }, pointerTestName + "'s buttons attribute is 0 when right mouse "
+              + "button is just released.");
+        } else {
+          test(function() {
+            assert_equals(event.button, -1);
+          }, pointerTestName + "'s button is -1 when mouse buttons are in "
+              + "released state.");
+          test(function() {
+            assert_equals(event.buttons, 0);
+          }, pointerTestName + "'s buttons is 0 when mouse buttons are in "
+              + "released state.");
+        }
+
+        // Test clientX and clientY
+        if (event.type != 'pointerout' && event.type != 'pointerleave' ) {
+          test(function () {
+            assert_greater_than_equal(
+                event.clientX, targetBoundingClientRect.left,
+                "clientX should be greater or equal than left of the box");
+            assert_greater_than_equal(
+                event.clientY, targetBoundingClientRect.top,
+                "clientY should be greater or equal than top of the box");
+            assert_less_than_equal(
+                event.clientX, targetBoundingClientRect.right,
+                "clientX should be less or equal than right of the box");
+            assert_less_than_equal(
+                event.clientY, targetBoundingClientRect.bottom,
+                "clientY should be less or equal than bottom of the box");
+          }, pointerTestName + "'s ClientX and ClientY attributes are correct.");
+        } else {
+          test(function () {
+            assert_true(
+                event.clientX < targetBoundingClientRect.left
+                    || event.clientX >= targetBoundingClientRect.right
+                    || event.clientY < targetBoundingClientRect.top
+                    || event.clientY >= targetBoundingClientRect.bottom,
+                "ClientX/Y should be out of the boundaries of the box");
+          }, pointerTestName + "'s ClientX and ClientY attributes are correct.");
+        }
+
+        check_PointerEvent(event, testNamePrefix);
+
+        // Test isPrimary
+        test(function () {
+          assert_equals(event.isPrimary, true);
+        }, pointerTestName + ".isPrimary attribute is correct.");
+
+        // Test pointerId value
+        if (isNaN(expectedPointerId)) {
+          expectedPointerId = event.pointerId;
+        } else {
+          test(function () {
+            assert_equals(event.pointerId, expectedPointerId);
+          }, pointerTestName + ".pointerId should be the same as previous "
+              + "pointer events for this active pointer.");
+        }
+      }
+
+      async function run() {
+        var test_pointerEvent = setup_pointerevent_test(
+            "pointerevent attributes", [input_pointertype]);
+        var square1 = document.getElementById("square1");
+        var rectSquare1 = square1.getBoundingClientRect();
+        var innerFrame = document.getElementById('innerFrame');
+        var square2 = innerFrame.contentDocument.getElementById('square2');
+        var rectSquare2 = square2.getBoundingClientRect();
+        var actions_promise;
+
+        eventList.forEach(function(eventName) {
+          on_event(square1, eventName, function (event) {
+            if (square1.style.visibility == 'hidden')
+              return;
+            checkPointerEventAttributes(event, rectSquare1, "");
+            if (Object.keys(detected_eventTypes).length == eventList.length) {
+              square1.style.visibility = 'hidden';
+              detected_eventTypes = {};
+              square2.style.visibility = 'visible';
+              expectedPointerId = NaN;
+            }
+          });
+          on_event(square2, eventName, function (event) {
+            checkPointerEventAttributes(event, rectSquare2, "Inner frame");
+            if (Object.keys(detected_eventTypes).length == eventList.length) {
+              square2.style.visibility = 'hidden';
+              test_pointerEvent.done();
+            }
+          });
+        });
+
+        // Inject mouse or pen inputs.
+        await rightClickInTarget(input_pointertype, square1);
+        await moveToDocument(input_pointertype);
+        await rightClickInTarget(input_pointertype, square2);
+        await moveToDocument(input_pointertype);
+      }
+    </script>
+  </head>
+  <body onload="run()">
+    <h1>Pointer Events hoverable pointer attributes test</h1>
+    <h2 id="pointerTypeDescription"></h2>
+    <div id="square1" class="square"></div>
+    <iframe id="innerFrame"
+        src="resources/pointerevent_attributes_hoverable_pointers-iframe.html">
+    </iframe>
+  </body>
+</html>
diff --git a/pointerevents/pointerevent_attributes_nohover_pointers.html b/pointerevents/pointerevent_attributes_nohover_pointers.html
new file mode 100644
index 0000000..3441417
--- /dev/null
+++ b/pointerevents/pointerevent_attributes_nohover_pointers.html
@@ -0,0 +1,200 @@
+<!doctype html>
+<html>
+<head>
+  <title>Pointer Events properties tests</title>
+  <meta name="viewport" content="width=device-width">
+  <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+  <style>
+    html {
+      touch-action: none;
+    }
+
+    div {
+      padding: 0;
+    }
+
+    #square1 {
+      background-color: green;
+      border: 1px solid black;
+      height: 50px;
+      width: 50px;
+      margin-bottom: 3px;
+      display: inline-block;
+    }
+
+    #innerFrame {
+      position: relative;
+      margin-bottom: 3px;
+      margin-left: 0;
+      top: 0;
+      left: 0;
+    }
+  </style>
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<!-- Additional helper script for common checks across event types -->
+<script type="text/javascript" src="pointerevent_support.js"></script>
+<script>
+
+  window.onload = async () => {
+     const event_list = [
+      'pointerover',
+      'pointerenter',
+      'pointerdown',
+      'pointerup',
+      'pointerout',
+      'pointerleave',
+      'pointermove'
+    ];
+
+    function checkPointerEventAttributes(testPrefix, event, expectations) {
+      const pointerTestName =
+          `${testPrefix} touch.${expectations.type}`;
+
+      test(function() {
+        assert_equals(event.type, expectations.type);
+      }, `${pointerTestName}.type should be ${expectations.type}`);
+
+      test(function() {
+        assert_equals(event.button, expectations.button);
+      }, `${pointerTestName}.button should be ${expectations.button}`);
+
+      test(function() {
+        assert_equals(event.buttons, expectations.buttons);
+      }, `${pointerTestName}.buttons should be ${expectations.buttons}`);
+
+      // Bounding rect of the event target must contain (clienX, clientY).
+      const boundingRect = event.target.getBoundingClientRect();
+      test(function() {
+        assert_true(
+            boundingRect.left <= event.clientX &&
+            boundingRect.right >= event.clientX);
+      }, `${pointerTestName}.clientX is within the expected range`);
+      test(function() {
+        assert_true(
+            boundingRect.top <= event.clientY &&
+            boundingRect.bottom >= event.clientY);
+      }, `${pointerTestName}.clientY is within the expected range`);
+
+      check_PointerEvent(event, testPrefix);
+
+      // Test isPrimary
+      test(function () {
+        assert_equals(event.isPrimary, true);
+      }, `${pointerTestName}: isPrimary attribute is true.`);
+    }
+
+    function injectScrub(element) {
+      return new test_driver.Actions()
+        .addPointer('pointer1', 'touch')
+        .pointerMove(0, -20, {origin: element})
+        .pointerDown()
+        .addTick()
+        .addTick()
+        .pointerMove(0, 20, {origin: element})
+        .addTick()
+        .addTick()
+        .pointerUp()
+        .send();
+    }
+
+    async function tapDone() {
+      const done_button = document.getElementById('done');
+      const pointerupPromise = getEvent('pointerup', done_button);
+      const actionPromise = new test_driver.Actions()
+        .addPointer('pointer1', 'touch')
+        .pointerMove(0, 0, {origin: done_button})
+        .pointerDown()
+        .addTick()
+        .addTick()
+        .pointerUp()
+        .send();
+      return actionPromise.then(pointerupPromise);
+    }
+
+    const test_fixture = async_test("All events handled");
+    const listeners = {};
+    const attachListener = (testPrefix, target, type, expectations,
+                            elements) => {
+      expectations.type = type;
+      const pointer_ids = {};
+      const key = `${testPrefix} ${type}`;
+      const listener = (event) => {
+        if (pointer_ids[testPrefix] == undefined) {
+          pointer_ids[testPrefix] == event.pointerId;
+        } else {
+          test(() => {
+            assert_equals(event.pointerId, pointer_ids[testPrefix]);
+          }, `${testPrefix} touch.pointerId matches expectation`);
+        }
+        // Don't let the browser handle the event to help guard against
+        // potential memory leaks.
+        event.preventDefault();
+        checkPointerEventAttributes(testPrefix, event, expectations);
+        target.removeEventListener(type, listener);
+        delete listeners[key];
+      };
+      target.addEventListener(type, listener);
+      listeners[key] = listener;
+    };
+
+    const square1 = document.getElementById("square1");
+    const innerFrame = document.getElementById('innerFrame');
+    const square2 = innerFrame.contentDocument.getElementById('square2');
+    const expectations = {
+      pointerover:  { button:  0, buttons: 1 },
+      pointerenter: { button:  0, buttons: 1 },
+      pointerdown:  { button:  0, buttons: 1 },
+      pointermove:  { button: -1, buttons: 1 },
+      pointerup:    { button:  0, buttons: 0 },
+      pointerout:   { button:  0, buttons: 0 },
+      pointerleave: { button:  0, buttons: 0 },
+    };
+    event_list.forEach(type => {
+      attachListener('', square1, type, expectations[type]);
+      attachListener('inner frame', square2, type, expectations[type]);
+    });
+    await injectScrub(square1);
+    await injectScrub(square2);
+    await tapDone();
+
+    test_fixture.step(() => {
+      assert_equals(
+          Object.keys(listeners).length, 0,
+          `Missing tests for ${Object.keys(listeners).join(', ')}`);
+      test_fixture.done();
+    });
+  };
+</script>
+<body>
+  <div id="square1"></div>
+  <div>
+    <iframe id="innerFrame" srcdoc='
+      <style>
+        html {
+          touch-action: none;
+        }
+        #square2 {
+          background-color: green;
+          border: 1px solid black;
+          height: 50px;
+          width: 50px;
+          display: inline-block;
+        }
+      </style>
+      <body>
+        <div id="square2"></div>
+      </body>
+    '></iframe>
+  </div>
+  <!-- Used to detect a sentinel event. Once triggered, all other events must
+       have been processed. -->
+  <div>
+    <button id="done">done</button>
+  </div>
+</body>
+</html>
diff --git a/pointerevents/resources/pointerevent_attributes_hoverable_pointers-iframe.html b/pointerevents/resources/pointerevent_attributes_hoverable_pointers-iframe.html
new file mode 100644
index 0000000..5e55868
--- /dev/null
+++ b/pointerevents/resources/pointerevent_attributes_hoverable_pointers-iframe.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+    </head>
+    <body>
+        <div id="square2" class="square"></div>
+    </body>
+</html>