How to use mojoFromNativeOrigin method in wpt

Best JavaScript code snippet using wpt

webxr-test.js

Source:webxr-test.js Github

copy

Full Screen

1'use strict';2// This polyfill library implements the WebXR Test API as specified here:3// https://github.com/immersive-web/webxr-test-api4const defaultMojoFromFloor = new gfx.mojom.Transform();5defaultMojoFromFloor.matrix = [1, 0, 0, 0,6 0, 1, 0, 0,7 0, 0, 1, 0,8 0, -1.65, 0, 1];9const default_stage_parameters = {10 mojoFromFloor: defaultMojoFromFloor,11 bounds: null12};13function getMatrixFromTransform(transform) {14 const x = transform.orientation[0];15 const y = transform.orientation[1];16 const z = transform.orientation[2];17 const w = transform.orientation[3];18 const m11 = 1.0 - 2.0 * (y * y + z * z);19 const m21 = 2.0 * (x * y + z * w);20 const m31 = 2.0 * (x * z - y * w);21 const m12 = 2.0 * (x * y - z * w);22 const m22 = 1.0 - 2.0 * (x * x + z * z);23 const m32 = 2.0 * (y * z + x * w);24 const m13 = 2.0 * (x * z + y * w);25 const m23 = 2.0 * (y * z - x * w);26 const m33 = 1.0 - 2.0 * (x * x + y * y);27 const m14 = transform.position[0];28 const m24 = transform.position[1];29 const m34 = transform.position[2];30 // Column-major linearized order is expected.31 return [m11, m21, m31, 0,32 m12, m22, m32, 0,33 m13, m23, m33, 0,34 m14, m24, m34, 1];35}36function composeGFXTransform(fakeTransformInit) {37 const transform = new gfx.mojom.Transform();38 transform.matrix = getMatrixFromTransform(fakeTransformInit);39 return transform;40}41class ChromeXRTest {42 constructor() {43 this.mockVRService_ = new MockVRService(mojo.frameInterfaces);44 }45 simulateDeviceConnection(init_params) {46 return Promise.resolve(this.mockVRService_.addRuntime(init_params));47 }48 disconnectAllDevices() {49 this.mockVRService_.removeAllRuntimes(device);50 return Promise.resolve();51 }52 simulateUserActivation(callback) {53 if (window.top !== window) {54 // test_driver.click only works for the toplevel frame. This alternate55 // Chrome-specific method is sufficient for starting an XR session in an56 // iframe, and is used in platform-specific tests.57 //58 // TODO(https://github.com/web-platform-tests/wpt/issues/20282): use59 // a cross-platform method if available.60 xr_debug('simulateUserActivation', 'use eventSender');61 document.addEventListener('click', callback);62 eventSender.mouseMoveTo(0, 0);63 eventSender.mouseDown();64 eventSender.mouseUp();65 document.removeEventListener('click', callback);66 return;67 }68 const button = document.createElement('button');69 button.textContent = 'click to continue test';70 button.style.display = 'block';71 button.style.fontSize = '20px';72 button.style.padding = '10px';73 button.onclick = () => {74 callback();75 document.body.removeChild(button);76 };77 document.body.appendChild(button);78 test_driver.click(button);79 }80 Debug(name, msg) {81 console.log(new Date().toISOString() + ' DEBUG[' + name + '] ' + msg);82 }83}84// Mocking class definitions85// Mock service implements the VRService mojo interface.86class MockVRService {87 constructor() {88 this.bindingSet_ = new mojo.BindingSet(device.mojom.VRService);89 this.runtimes_ = [];90 this.interceptor_ =91 new MojoInterfaceInterceptor(device.mojom.VRService.name);92 this.interceptor_.oninterfacerequest = e =>93 this.bindingSet_.addBinding(this, e.handle);94 this.interceptor_.start();95 }96 // Test methods97 addRuntime(fakeDeviceInit) {98 const runtime = new MockRuntime(fakeDeviceInit, this);99 this.runtimes_.push(runtime);100 if (this.client_) {101 this.client_.onDeviceChanged();102 }103 return runtime;104 }105 removeAllRuntimes() {106 if (this.client_) {107 this.client_.onDeviceChanged();108 }109 this.runtimes_ = [];110 }111 removeRuntime(device) {112 const index = this.runtimes_.indexOf(device);113 if (index >= 0) {114 this.runtimes_.splice(index, 1);115 if (this.client_) {116 this.client_.onDeviceChanged();117 }118 }119 }120 setClient(client) {121 if (this.client_) {122 throw new Error("setClient should only be called once");123 }124 this.client_ = client;125 }126 requestSession(sessionOptions, was_activation) {127 const requests = [];128 // Request a session from all the runtimes.129 for (let i = 0; i < this.runtimes_.length; i++) {130 requests[i] = this.runtimes_[i].requestRuntimeSession(sessionOptions);131 }132 return Promise.all(requests).then((results) => {133 // Find and return the first successful result.134 for (let i = 0; i < results.length; i++) {135 if (results[i].session) {136 // Construct a dummy metrics recorder137 const metricsRecorderPtr = new device.mojom.XRSessionMetricsRecorderPtr();138 const metricsRecorderRequest = mojo.makeRequest(metricsRecorderPtr);139 const metricsRecorderBinding = new mojo.Binding(140 device.mojom.XRSessionMetricsRecorder, new MockXRSessionMetricsRecorder(), metricsRecorderRequest);141 const success = {142 session: results[i].session,143 metricsRecorder: metricsRecorderPtr,144 };145 return {146 result: {147 success : success,148 $tag : 0149 }150 };151 }152 }153 // If there were no successful results, returns a null session.154 return {155 result: {156 failureReason : device.mojom.RequestSessionError.NO_RUNTIME_FOUND,157 $tag : 1158 }159 };160 });161 }162 exitPresent() {163 return Promise.resolve();164 }165 supportsSession(sessionOptions) {166 const requests = [];167 // Check supports on all the runtimes.168 for (let i = 0; i < this.runtimes_.length; i++) {169 requests[i] = this.runtimes_[i].runtimeSupportsSession(sessionOptions);170 }171 return Promise.all(requests).then((results) => {172 // Find and return the first successful result.173 for (let i = 0; i < results.length; i++) {174 if (results[i].supportsSession) {175 return results[i];176 }177 }178 // If there were no successful results, returns false.179 return {supportsSession: false};180 });181 }182 // Only handles asynchronous calls to makeXrCompatible. Synchronous calls are183 // not supported in Javascript.184 makeXrCompatible() {185 return Promise.resolve({186 xr_compatible_result: device.mojom.XrCompatibleResult.kAlreadyCompatible187 });188 }189}190class FakeXRAnchorController {191 constructor() {192 // Private properties.193 this.device_ = null;194 this.id_ = null;195 this.dirty_ = true;196 // Properties backing up public attributes / methods.197 this.deleted_ = false;198 this.paused_ = false;199 this.anchorOrigin_ = XRMathHelper.identity();200 }201 get deleted() {202 return this.deleted_;203 }204 pauseTracking() {205 if(!this.paused_) {206 this.paused_ = true;207 this.dirty_ = true;208 }209 }210 resumeTracking() {211 if(this.paused_) {212 this.paused_ = false;213 this.dirty_ = true;214 }215 }216 stopTracking() {217 if(!this.deleted_) {218 this.device_.deleteAnchorController(this.id_);219 this.deleted_ = true;220 this.dirty_ = true;221 }222 }223 setAnchorOrigin(anchorOrigin) {224 this.anchorOrigin_ = getMatrixFromTransform(anchorOrigin);225 this.dirty_ = true;226 }227 // Internal implementation:228 set id(value) {229 this.id_ = value;230 }231 set device(value) {232 this.device_ = value;233 }234 get dirty() {235 return this.dirty_;236 }237 get paused() {238 return this.paused_;239 }240 markProcessed() {241 this.dirty_ = false;242 }243 getAnchorOrigin() {244 return this.anchorOrigin_;245 }246}247// Internal only for now, needs to be moved into WebXR Test API.248class FakeXRHitTestSourceController {249 constructor(id) {250 this.id_ = id;251 this.deleted_ = false;252 }253 get deleted() {254 return this.deleted_;255 }256 // Internal setter:257 set deleted(value) {258 this.deleted_ = value;259 }260}261// Implements XRFrameDataProvider and XRPresentationProvider. Maintains a mock262// for XRPresentationProvider. Implements FakeXRDevice test API.263class MockRuntime {264 // Mapping from string feature names to the corresponding mojo types.265 // This is exposed as a member for extensibility.266 static featureToMojoMap = {267 'viewer': device.mojom.XRSessionFeature.REF_SPACE_VIEWER,268 'local': device.mojom.XRSessionFeature.REF_SPACE_LOCAL,269 'local-floor': device.mojom.XRSessionFeature.REF_SPACE_LOCAL_FLOOR,270 'bounded-floor': device.mojom.XRSessionFeature.REF_SPACE_BOUNDED_FLOOR,271 'unbounded': device.mojom.XRSessionFeature.REF_SPACE_UNBOUNDED,272 'hit-test': device.mojom.XRSessionFeature.HIT_TEST,273 'dom-overlay': device.mojom.XRSessionFeature.DOM_OVERLAY,274 'light-estimation': device.mojom.XRSessionFeature.LIGHT_ESTIMATION,275 'anchors': device.mojom.XRSessionFeature.ANCHORS,276 };277 static sessionModeToMojoMap = {278 "inline": device.mojom.XRSessionMode.kInline,279 "immersive-vr": device.mojom.XRSessionMode.kImmersiveVr,280 "immersive-ar": device.mojom.XRSessionMode.kImmersiveAr,281 };282 constructor(fakeDeviceInit, service) {283 this.sessionClient_ = new device.mojom.XRSessionClientPtr();284 this.presentation_provider_ = new MockXRPresentationProvider();285 this.pose_ = null;286 this.next_frame_id_ = 0;287 this.bounds_ = null;288 this.send_mojo_space_reset_ = false;289 this.stageParameters_ = null;290 this.stageParametersUpdated_ = false;291 this.service_ = service;292 this.framesOfReference = {};293 this.input_sources_ = new Map();294 this.next_input_source_index_ = 1;295 // Currently active hit test subscriptons.296 this.hitTestSubscriptions_ = new Map();297 // Currently active transient hit test subscriptions.298 this.transientHitTestSubscriptions_ = new Map();299 // ID of the next subscription to be assigned.300 this.next_hit_test_id_ = 1;301 this.anchor_controllers_ = new Map();302 // ID of the next anchor to be assigned.303 this.next_anchor_id_ = 1;304 // Anchor creation callback (initially null, can be set by tests).305 this.anchor_creation_callback_ = null;306 let supportedModes = [];307 if (fakeDeviceInit.supportedModes) {308 supportedModes = fakeDeviceInit.supportedModes.slice();309 if (fakeDeviceInit.supportedModes.length === 0) {310 supportedModes = ["inline"];311 }312 } else {313 // Back-compat mode.314 console.warn("Please use `supportedModes` to signal which modes are supported by this device.");315 if (fakeDeviceInit.supportsImmersive == null) {316 throw new TypeError("'supportsImmersive' must be set");317 }318 supportedModes = ["inline"];319 if (fakeDeviceInit.supportsImmersive) {320 supportedModes.push("immersive-vr");321 }322 }323 this.supportedModes_ = this._convertModesToEnum(supportedModes);324 // Initialize DisplayInfo first to set the defaults, then override with325 // anything from the deviceInit326 if (this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveVr)327 || this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {328 this.displayInfo_ = this.getImmersiveDisplayInfo();329 } else if (this.supportedModes_.includes(device.mojom.XRSessionMode.kInline)) {330 this.displayInfo_ = this.getNonImmersiveDisplayInfo();331 } else {332 // This should never happen!333 console.error("Device has empty supported modes array!");334 throw new InvalidStateError();335 }336 if (fakeDeviceInit.viewerOrigin != null) {337 this.setViewerOrigin(fakeDeviceInit.viewerOrigin);338 }339 if (fakeDeviceInit.floorOrigin != null) {340 this.setFloorOrigin(fakeDeviceInit.floorOrigin);341 }342 if (fakeDeviceInit.world) {343 this.world_ = fakeDeviceInit.world;344 }345 // This appropriately handles if the coordinates are null346 this.setBoundsGeometry(fakeDeviceInit.boundsCoordinates);347 this.setViews(fakeDeviceInit.views);348 // Need to support webVR which doesn't have a notion of features349 this.setFeatures(fakeDeviceInit.supportedFeatures || []);350 }351 _convertModeToEnum(sessionMode) {352 if (sessionMode in MockRuntime.sessionModeToMojoMap) {353 return MockRuntime.sessionModeToMojoMap[sessionMode];354 }355 throw new TypeError("Unrecognized value for XRSessionMode enum: " + sessionMode);356 }357 _convertModesToEnum(sessionModes) {358 return sessionModes.map(mode => this._convertModeToEnum(mode));359 }360 // Test API methods.361 disconnect() {362 this.service_.removeRuntime(this);363 this.presentation_provider_.Close();364 if (this.sessionClient_.ptr.isBound()) {365 this.sessionClient_.ptr.reset();366 }367 return Promise.resolve();368 }369 setViews(views) {370 if (views) {371 let changed = false;372 for (let i = 0; i < views.length; i++) {373 if (views[i].eye == 'left') {374 this.displayInfo_.leftEye = this.getEye(views[i]);375 changed = true;376 } else if (views[i].eye == 'right') {377 this.displayInfo_.rightEye = this.getEye(views[i]);378 changed = true;379 }380 }381 if (changed && this.sessionClient_.ptr.isBound()) {382 this.sessionClient_.onChanged(this.displayInfo_);383 }384 }385 }386 setViewerOrigin(origin, emulatedPosition = false) {387 const p = origin.position;388 const q = origin.orientation;389 this.pose_ = {390 orientation: { x: q[0], y: q[1], z: q[2], w: q[3] },391 position: { x: p[0], y: p[1], z: p[2] },392 emulatedPosition: emulatedPosition,393 angularVelocity: null,394 linearVelocity: null,395 angularAcceleration: null,396 linearAcceleration: null,397 inputState: null,398 poseIndex: 0399 };400 }401 clearViewerOrigin() {402 this.pose_ = null;403 }404 simulateVisibilityChange(visibilityState) {405 let mojoState = null;406 switch (visibilityState) {407 case "visible":408 mojoState = device.mojom.XRVisibilityState.VISIBLE;409 break;410 case "visible-blurred":411 mojoState = device.mojom.XRVisibilityState.VISIBLE_BLURRED;412 break;413 case "hidden":414 mojoState = device.mojom.XRVisibilityState.HIDDEN;415 break;416 }417 if (mojoState) {418 this.sessionClient_.onVisibilityStateChanged(mojoState);419 }420 }421 setBoundsGeometry(bounds) {422 if (bounds == null) {423 this.bounds_ = null;424 } else if (bounds.length < 3) {425 throw new Error("Bounds must have a length of at least 3");426 } else {427 this.bounds_ = bounds;428 }429 // We can only set bounds if we have stageParameters set; otherwise, we430 // don't know the transform from local space to bounds space.431 // We'll cache the bounds so that they can be set in the future if the432 // floorLevel transform is set, but we won't update them just yet.433 if (this.stageParameters_) {434 this.stageParameters_.bounds = this.bounds_;435 this.onStageParametersUpdated();436 }437 }438 setFloorOrigin(floorOrigin) {439 if (!this.stageParameters_) {440 this.stageParameters_ = default_stage_parameters;441 this.stageParameters_.bounds = this.bounds_;442 }443 this.stageParameters_.mojoFromFloor = new gfx.mojom.Transform();444 // floorOrigin is passed in as mojoFromFloor.445 this.stageParameters_.mojoFromFloor.matrix =446 getMatrixFromTransform(floorOrigin);447 this.onStageParametersUpdated();448 }449 clearFloorOrigin() {450 if (this.stageParameters_) {451 this.stageParameters_ = null;452 this.onStageParametersUpdated();453 }454 }455 onStageParametersUpdated() {456 // Indicate for the frame loop that the stage parameters have been updated.457 this.stageParametersUpdated_ = true;458 this.displayInfo_.stageParameters = this.stageParameters_;459 if (this.sessionClient_.ptr.isBound()) {460 this.sessionClient_.onChanged(this.displayInfo_);461 }462 }463 simulateResetPose() {464 this.send_mojo_space_reset_ = true;465 }466 simulateInputSourceConnection(fakeInputSourceInit) {467 const index = this.next_input_source_index_;468 this.next_input_source_index_++;469 const source = new MockXRInputSource(fakeInputSourceInit, index, this);470 this.input_sources_.set(index, source);471 return source;472 }473 setAnchorCreationCallback(callback) {474 this.anchor_creation_callback_ = callback;475 }476 setHitTestSourceCreationCallback(callback) {477 this.hit_test_source_creation_callback_ = callback;478 }479 // Helper methods480 getNonImmersiveDisplayInfo() {481 const displayInfo = this.getImmersiveDisplayInfo();482 displayInfo.capabilities.canPresent = false;483 displayInfo.leftEye = null;484 displayInfo.rightEye = null;485 return displayInfo;486 }487 // Function to generate some valid display information for the device.488 getImmersiveDisplayInfo() {489 return {490 displayName: 'FakeDevice',491 capabilities: {492 hasPosition: false,493 hasExternalDisplay: false,494 canPresent: true,495 maxLayers: 1496 },497 stageParameters: null,498 leftEye: {499 fieldOfView: {500 upDegrees: 48.316,501 downDegrees: 50.099,502 leftDegrees: 50.899,503 rightDegrees: 35.197504 },505 headFromEye: composeGFXTransform({506 position: [-0.032, 0, 0],507 orientation: [0, 0, 0, 1]508 }),509 renderWidth: 20,510 renderHeight: 20511 },512 rightEye: {513 fieldOfView: {514 upDegrees: 48.316,515 downDegrees: 50.099,516 leftDegrees: 50.899,517 rightDegrees: 35.197518 },519 headFromEye: composeGFXTransform({520 position: [0.032, 0, 0],521 orientation: [0, 0, 0, 1]522 }),523 renderWidth: 20,524 renderHeight: 20525 },526 webxrDefaultFramebufferScale: 0.7,527 };528 }529 // This function converts between the matrix provided by the WebXR test API530 // and the internal data representation.531 getEye(fakeXRViewInit) {532 let fov = null;533 if (fakeXRViewInit.fieldOfView) {534 fov = {535 upDegrees: fakeXRViewInit.fieldOfView.upDegrees,536 downDegrees: fakeXRViewInit.fieldOfView.downDegrees,537 leftDegrees: fakeXRViewInit.fieldOfView.leftDegrees,538 rightDegrees: fakeXRViewInit.fieldOfView.rightDegrees539 };540 } else {541 const m = fakeXRViewInit.projectionMatrix;542 function toDegrees(tan) {543 return Math.atan(tan) * 180 / Math.PI;544 }545 const leftTan = (1 - m[8]) / m[0];546 const rightTan = (1 + m[8]) / m[0];547 const upTan = (1 + m[9]) / m[5];548 const downTan = (1 - m[9]) / m[5];549 fov = {550 upDegrees: toDegrees(upTan),551 downDegrees: toDegrees(downTan),552 leftDegrees: toDegrees(leftTan),553 rightDegrees: toDegrees(rightTan)554 };555 }556 return {557 fieldOfView: fov,558 headFromEye: composeGFXTransform(fakeXRViewInit.viewOffset),559 renderWidth: fakeXRViewInit.resolution.width,560 renderHeight: fakeXRViewInit.resolution.height561 };562 }563 setFeatures(supportedFeatures) {564 function convertFeatureToMojom(feature) {565 if (feature in MockRuntime.featureToMojoMap) {566 return MockRuntime.featureToMojoMap[feature];567 } else {568 return device.mojom.XRSessionFeature.INVALID;569 }570 }571 this.supportedFeatures_ = [];572 for (let i = 0; i < supportedFeatures.length; i++) {573 const feature = convertFeatureToMojom(supportedFeatures[i]);574 if (feature !== device.mojom.XRSessionFeature.INVALID) {575 this.supportedFeatures_.push(feature);576 }577 }578 }579 // These methods are intended to be used by MockXRInputSource only.580 addInputSource(source) {581 if (!this.input_sources_.has(source.source_id_)) {582 this.input_sources_.set(source.source_id_, source);583 }584 }585 removeInputSource(source) {586 this.input_sources_.delete(source.source_id_);587 }588 // These methods are intended to be used by FakeXRAnchorController only.589 deleteAnchorController(controllerId) {590 this.anchor_controllers_.delete(controllerId);591 }592 // Extension point for non-standard modules.593 _injectAdditionalFrameData(options, frameData) {594 }595 // Mojo function implementations.596 // XRFrameDataProvider implementation.597 getFrameData(options) {598 return new Promise((resolve) => {599 const populatePose = () => {600 const mojo_space_reset = this.send_mojo_space_reset_;601 this.send_mojo_space_reset_ = false;602 const stage_parameters_updated = this.stageParametersUpdated_;603 this.stageParametersUpdated_ = false;604 if (this.pose_) {605 this.pose_.poseIndex++;606 }607 // Setting the input_state to null tests a slightly different path than608 // the browser tests where if the last input source is removed, the device609 // code always sends up an empty array, but it's also valid mojom to send610 // up a null array.611 let input_state = null;612 if (this.input_sources_.size > 0) {613 input_state = [];614 for (const input_source of this.input_sources_.values()) {615 input_state.push(input_source.getInputSourceState());616 }617 }618 const frameData = {619 pose: this.pose_,620 mojoSpaceReset: mojo_space_reset,621 inputState: input_state,622 timeDelta: {623 // window.performance.now() is in milliseconds, so convert to microseconds.624 microseconds: window.performance.now() * 1000,625 },626 frameId: this.next_frame_id_,627 bufferHolder: null,628 bufferSize: {},629 stageParameters: this.stageParameters_,630 stageParametersUpdated: stage_parameters_updated,631 };632 this.next_frame_id_++;633 this._calculateHitTestResults(frameData);634 this._calculateAnchorInformation(frameData);635 this._injectAdditionalFrameData(options, frameData);636 resolve({frameData});637 };638 if(this.sessionOptions_.mode == device.mojom.XRSessionMode.kInline) {639 // Inline sessions should not have a delay introduced since it causes them640 // to miss a vsync blink-side and delays propagation of changes that happened641 // within a rAFcb by one frame (e.g. setViewerOrigin() calls would take 2 frames642 // to propagate).643 populatePose();644 } else {645 // For immerive sessions, add additional delay to allow for anchor creation646 // promises to run.647 setTimeout(populatePose, 3); // note: according to MDN, the timeout is not exact648 }649 });650 }651 getEnvironmentIntegrationProvider(environmentProviderRequest) {652 this.environmentProviderBinding_ = new mojo.AssociatedBinding(653 device.mojom.XREnvironmentIntegrationProvider, this,654 environmentProviderRequest);655 }656 // Note that if getEnvironmentProvider hasn't finished running yet this will657 // be undefined. It's recommended that you allow a successful task to post658 // first before attempting to close.659 closeEnvironmentIntegrationProvider() {660 this.environmentProviderBinding_.close();661 }662 closeDataProvider() {663 this.dataProviderBinding_.close();664 this.sessionOptions_ = null;665 }666 // XREnvironmentIntegrationProvider implementation:667 subscribeToHitTest(nativeOriginInformation, entityTypes, ray) {668 if (!this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {669 // Reject outside of AR.670 return Promise.resolve({671 result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,672 subscriptionId : 0673 });674 }675 if (!this._nativeOriginKnown(nativeOriginInformation)) {676 return Promise.resolve({677 result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,678 subscriptionId : 0679 });680 }681 // Reserve the id for hit test source:682 const id = this.next_hit_test_id_++;683 const hitTestParameters = { isTransient: false, profileName: null };684 const controller = new FakeXRHitTestSourceController(id);685 return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)686 .then((succeeded) => {687 if(succeeded) {688 // Store the subscription information as-is (including controller):689 this.hitTestSubscriptions_.set(id, { nativeOriginInformation, entityTypes, ray, controller });690 return Promise.resolve({691 result : device.mojom.SubscribeToHitTestResult.SUCCESS,692 subscriptionId : id693 });694 } else {695 return Promise.resolve({696 result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,697 subscriptionId : 0698 });699 }700 });701 }702 subscribeToHitTestForTransientInput(profileName, entityTypes, ray){703 if (!this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {704 // Reject outside of AR.705 return Promise.resolve({706 result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,707 subscriptionId : 0708 });709 }710 const id = this.next_hit_test_id_++;711 const hitTestParameters = { isTransient: true, profileName: profileName };712 const controller = new FakeXRHitTestSourceController(id);713 // Check if we have hit test source creation callback.714 // If yes, ask it if the hit test source creation should succeed.715 // If no, for back-compat, assume the hit test source creation succeeded.716 return this._shouldHitTestSourceCreationSucceed(hitTestParameters, controller)717 .then((succeeded) => {718 if(succeeded) {719 // Store the subscription information as-is (including controller):720 this.transientHitTestSubscriptions_.set(id, { profileName, entityTypes, ray, controller });721 return Promise.resolve({722 result : device.mojom.SubscribeToHitTestResult.SUCCESS,723 subscriptionId : id724 });725 } else {726 return Promise.resolve({727 result : device.mojom.SubscribeToHitTestResult.FAILURE_GENERIC,728 subscriptionId : 0729 });730 }731 });732 }733 unsubscribeFromHitTest(subscriptionId) {734 let controller = null;735 if(this.transientHitTestSubscriptions_.has(subscriptionId)){736 controller = this.transientHitTestSubscriptions_.get(subscriptionId).controller;737 this.transientHitTestSubscriptions_.delete(subscriptionId);738 } else if(this.hitTestSubscriptions_.has(subscriptionId)){739 controller = this.hitTestSubscriptions_.get(subscriptionId).controller;740 this.hitTestSubscriptions_.delete(subscriptionId);741 }742 if(controller) {743 controller.deleted = true;744 }745 }746 createAnchor(nativeOriginInformation, nativeOriginFromAnchor) {747 return new Promise((resolve) => {748 if(this.anchor_creation_callback_ == null) {749 resolve({750 result : device.mojom.CreateAnchorResult.FAILURE,751 anchorId : 0752 });753 return;754 }755 const mojoFromNativeOrigin = this._getMojoFromNativeOrigin(nativeOriginInformation);756 if(mojoFromNativeOrigin == null) {757 resolve({758 result : device.mojom.CreateAnchorResult.FAILURE,759 anchorId : 0760 });761 return;762 }763 const mojoFromAnchor = XRMathHelper.mul4x4(mojoFromNativeOrigin, nativeOriginFromAnchor);764 const anchorCreationParameters = {765 requestedAnchorOrigin: mojoFromAnchor,766 isAttachedToEntity: false,767 };768 const anchorController = new FakeXRAnchorController();769 this.anchor_creation_callback_(anchorCreationParameters, anchorController)770 .then((result) => {771 if(result) {772 // If the test allowed the anchor creation,773 // store the anchor controller & return success.774 const anchor_id = this.next_anchor_id_;775 this.next_anchor_id_++;776 this.anchor_controllers_.set(anchor_id, anchorController);777 anchorController.device = this;778 anchorController.id = anchor_id;779 resolve({780 result : device.mojom.CreateAnchorResult.SUCCESS,781 anchorId : anchor_id782 });783 } else {784 // The test has rejected anchor creation.785 resolve({786 result : device.mojom.CreateAnchorResult.FAILURE,787 anchorId : 0788 });789 }790 })791 .catch(() => {792 // The test threw an error, treat anchor creation as failed.793 resolve({794 result : device.mojom.CreateAnchorResult.FAILURE,795 anchorId : 0796 });797 });798 });799 }800 createPlaneAnchor(planeFromAnchor, planeId) {801 return new Promise((resolve) => {802 // Not supported yet.803 resolve({804 result : device.mojom.CreateAnchorResult.FAILURE,805 anchorId : 0806 });807 });808 }809 // Utility function810 requestRuntimeSession(sessionOptions) {811 return this.runtimeSupportsSession(sessionOptions).then((result) => {812 // The JavaScript bindings convert c_style_names to camelCase names.813 const options = new device.mojom.XRPresentationTransportOptions();814 options.transportMethod =815 device.mojom.XRPresentationTransportMethod.SUBMIT_AS_MAILBOX_HOLDER;816 options.waitForTransferNotification = true;817 options.waitForRenderNotification = true;818 let submit_frame_sink;819 if (result.supportsSession) {820 submit_frame_sink = {821 clientReceiver: this.presentation_provider_.getClientReceiver(),822 provider: this.presentation_provider_.bindProvider(sessionOptions),823 transportOptions: options824 };825 const dataProviderPtr = new device.mojom.XRFrameDataProviderPtr();826 const dataProviderRequest = mojo.makeRequest(dataProviderPtr);827 this.dataProviderBinding_ = new mojo.Binding(828 device.mojom.XRFrameDataProvider, this, dataProviderRequest);829 this.sessionOptions_ = sessionOptions;830 const clientReceiver = mojo.makeRequest(this.sessionClient_);831 const enabled_features = [];832 for (let i = 0; i < sessionOptions.requiredFeatures.length; i++) {833 if (this.supportedFeatures_.indexOf(sessionOptions.requiredFeatures[i]) !== -1) {834 enabled_features.push(sessionOptions.requiredFeatures[i]);835 } else {836 return Promise.resolve({session: null});837 }838 }839 for (let i =0; i < sessionOptions.optionalFeatures.length; i++) {840 if (this.supportedFeatures_.indexOf(sessionOptions.optionalFeatures[i]) !== -1) {841 enabled_features.push(sessionOptions.optionalFeatures[i]);842 }843 }844 return Promise.resolve({845 session: {846 submitFrameSink: submit_frame_sink,847 dataProvider: dataProviderPtr,848 clientReceiver: clientReceiver,849 displayInfo: this.displayInfo_,850 enabledFeatures: enabled_features,851 }852 });853 } else {854 return Promise.resolve({session: null});855 }856 });857 }858 runtimeSupportsSession(options) {859 return Promise.resolve({860 supportsSession: this.supportedModes_.includes(options.mode)861 });862 }863 // Private functions - utilities:864 _nativeOriginKnown(nativeOriginInformation){865 if (nativeOriginInformation.$tag == device.mojom.XRNativeOriginInformation.Tags.inputSourceId) {866 if (!this.input_sources_.has(nativeOriginInformation.inputSourceId)) {867 // Unknown input source.868 return false;869 }870 return true;871 } else if (nativeOriginInformation.$tag == device.mojom.XRNativeOriginInformation.Tags.referenceSpaceType) {872 // Bounded_floor & unbounded ref spaces are not yet supported for AR:873 if (nativeOriginInformation.referenceSpaceType == device.mojom.XRReferenceSpaceType.kUnbounded874 || nativeOriginInformation.referenceSpaceType == device.mojom.XRReferenceSpaceType.kBoundedFlor) {875 return false;876 }877 return true;878 } else {879 // Planes and anchors are not yet supported by the mock interface.880 return false;881 }882 }883 // Private functions - anchors implementation:884 // Modifies passed in frameData to add anchor information.885 _calculateAnchorInformation(frameData) {886 if (!this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {887 return;888 }889 frameData.anchorsData = new device.mojom.XRAnchorsData();890 frameData.anchorsData.allAnchorsIds = [];891 frameData.anchorsData.updatedAnchorsData = [];892 for(const [id, controller] of this.anchor_controllers_) {893 frameData.anchorsData.allAnchorsIds.push(id);894 // Send the entire anchor data over if there was a change since last GetFrameData().895 if(controller.dirty) {896 const anchorData = new device.mojom.XRAnchorData();897 anchorData.id = id;898 if(!controller.paused) {899 anchorData.mojoFromAnchor = XRMathHelper.decomposeRigidTransform(900 controller.getAnchorOrigin());901 }902 controller.markProcessed();903 frameData.anchorsData.updatedAnchorsData.push(anchorData);904 }905 }906 }907 // Private functions - hit test implementation:908 // Returns a Promise<bool> that signifies whether hit test source creation should succeed.909 // If we have a hit test source creation callback installed, invoke it and return its result.910 // If it's not installed, for back-compat just return a promise that resolves to true.911 _shouldHitTestSourceCreationSucceed(hitTestParameters, controller) {912 if(this.hit_test_source_creation_callback_) {913 return this.hit_test_source_creation_callback_(hitTestParameters, controller);914 } else {915 return Promise.resolve(true);916 }917 }918 // Modifies passed in frameData to add hit test results.919 _calculateHitTestResults(frameData) {920 if (!this.supportedModes_.includes(device.mojom.XRSessionMode.kImmersiveAr)) {921 return;922 }923 frameData.hitTestSubscriptionResults = new device.mojom.XRHitTestSubscriptionResultsData();924 frameData.hitTestSubscriptionResults.results = [];925 frameData.hitTestSubscriptionResults.transientInputResults = [];926 if (!this.world_) {927 return;928 }929 // Non-transient hit test:930 for (const [id, subscription] of this.hitTestSubscriptions_) {931 const mojo_from_native_origin = this._getMojoFromNativeOrigin(subscription.nativeOriginInformation);932 if (!mojo_from_native_origin) continue;933 const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(934 subscription.ray,935 mojo_from_native_origin936 );937 const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);938 const result = new device.mojom.XRHitTestSubscriptionResultData();939 result.subscriptionId = id;940 result.hitTestResults = results;941 frameData.hitTestSubscriptionResults.results.push(result);942 }943 // Transient hit test:944 const mojo_from_viewer = this._getMojoFromViewer();945 for (const [id, subscription] of this.transientHitTestSubscriptions_) {946 const result = new device.mojom.XRHitTestTransientInputSubscriptionResultData();947 result.subscriptionId = id;948 result.inputSourceIdToHitTestResults = new Map();949 // Find all input sources that match the profile name:950 const matching_input_sources = Array.from(this.input_sources_.values())951 .filter(input_source => input_source.profiles_.includes(subscription.profileName));952 for (const input_source of matching_input_sources) {953 const mojo_from_native_origin = input_source._getMojoFromInputSource(mojo_from_viewer);954 const [mojo_ray_origin, mojo_ray_direction] = this._transformRayToMojoSpace(955 subscription.ray,956 mojo_from_native_origin957 );958 const results = this._hitTestWorld(mojo_ray_origin, mojo_ray_direction, subscription.entityTypes);959 result.inputSourceIdToHitTestResults.set(input_source.source_id_, results);960 }961 frameData.hitTestSubscriptionResults.transientInputResults.push(result);962 }963 }964 // Returns 2-element array [origin, direction] of a ray in mojo space.965 // |ray| is expressed relative to native origin.966 _transformRayToMojoSpace(ray, mojo_from_native_origin) {967 const ray_origin = {968 x: ray.origin.x,969 y: ray.origin.y,970 z: ray.origin.z,971 w: 1972 };973 const ray_direction = {974 x: ray.direction.x,975 y: ray.direction.y,976 z: ray.direction.z,977 w: 0978 };979 const mojo_ray_origin = XRMathHelper.transform_by_matrix(980 mojo_from_native_origin,981 ray_origin);982 const mojo_ray_direction = XRMathHelper.transform_by_matrix(983 mojo_from_native_origin,984 ray_direction);985 return [mojo_ray_origin, mojo_ray_direction];986 }987 // Hit tests the passed in ray (expressed as origin and direction) against the mocked world data.988 _hitTestWorld(origin, direction, entityTypes) {989 let result = [];990 for (const region of this.world_.hitTestRegions) {991 const partial_result = this._hitTestRegion(992 region,993 origin, direction,994 entityTypes);995 result = result.concat(partial_result);996 }997 return result.sort((lhs, rhs) => lhs.distance - rhs.distance);998 }999 // Hit tests the passed in ray (expressed as origin and direction) against world region.1000 // |entityTypes| is a set of FakeXRRegionTypes.1001 // |region| is FakeXRRegion.1002 // Returns array of XRHitResults, each entry will be decorated with the distance from the ray origin (along the ray).1003 _hitTestRegion(region, origin, direction, entityTypes) {1004 const regionNameToMojoEnum = {1005 "point":device.mojom.EntityTypeForHitTest.POINT,1006 "plane":device.mojom.EntityTypeForHitTest.PLANE,1007 "mesh":null1008 };1009 if (!entityTypes.includes(regionNameToMojoEnum[region.type])) {1010 return [];1011 }1012 const result = [];1013 for (const face of region.faces) {1014 const maybe_hit = this._hitTestFace(face, origin, direction);1015 if (maybe_hit) {1016 result.push(maybe_hit);1017 }1018 }1019 // The results should be sorted by distance and there should be no 2 entries with1020 // the same distance from ray origin - that would mean they are the same point.1021 // This situation is possible when a ray intersects the region through an edge shared1022 // by 2 faces.1023 return result.sort((lhs, rhs) => lhs.distance - rhs.distance)1024 .filter((val, index, array) => index === 0 || val.distance !== array[index - 1].distance);1025 }1026 // Hit tests the passed in ray (expressed as origin and direction) against a single face.1027 // |face|, |origin|, and |direction| are specified in world (aka mojo) coordinates.1028 // |face| is an array of DOMPointInits.1029 // Returns null if the face does not intersect with the ray, otherwise the result is1030 // an XRHitResult with matrix describing the pose of the intersection point.1031 _hitTestFace(face, origin, direction) {1032 const add = XRMathHelper.add;1033 const sub = XRMathHelper.sub;1034 const mul = XRMathHelper.mul;1035 const normalize = XRMathHelper.normalize;1036 const dot = XRMathHelper.dot;1037 const cross = XRMathHelper.cross;1038 const neg = XRMathHelper.neg;1039 //1. Calculate plane normal in world coordinates.1040 const point_A = face.vertices[0];1041 const point_B = face.vertices[1];1042 const point_C = face.vertices[2];1043 const edge_AB = sub(point_B, point_A);1044 const edge_AC = sub(point_C, point_A);1045 const normal = normalize(cross(edge_AB, edge_AC));1046 const numerator = dot(sub(point_A, origin), normal);1047 const denominator = dot(direction, normal);1048 if (Math.abs(denominator) < XRMathHelper.EPSILON) {1049 // Planes are nearly parallel - there's either infinitely many intersection points or 0.1050 // Both cases signify a "no hit" for us.1051 return null;1052 } else {1053 // Single intersection point between the infinite plane and the line (*not* ray).1054 // Need to calculate the hit test matrix taking into account the face vertices.1055 const distance = numerator / denominator;1056 if (distance < 0) {1057 // Line - plane intersection exists, but not the half-line - plane does not.1058 return null;1059 } else {1060 const intersection_point = add(origin, mul(distance, direction));1061 // Since we are treating the face as a solid, flip the normal so that its1062 // half-space will contain the ray origin.1063 const y_axis = denominator > 0 ? neg(normal) : normal;1064 let z_axis = null;1065 const cos_direction_and_y_axis = dot(direction, y_axis);1066 if (Math.abs(cos_direction_and_y_axis) > (1 - XRMathHelper.EPSILON)) {1067 // Ray and the hit test normal are co-linear - try using the 'up' or 'right' vector's projection on the face plane as the Z axis.1068 // Note: this edge case is currently not covered by the spec.1069 const up = {x: 0.0, y: 1.0, z: 0.0, w: 0.0};1070 const right = {x: 1.0, y: 0.0, z: 0.0, w: 0.0};1071 z_axis = Math.abs(dot(up, y_axis)) > (1 - XRMathHelper.EPSILON)1072 ? sub(up, mul(dot(right, y_axis), y_axis)) // `up is also co-linear with hit test normal, use `right`1073 : sub(up, mul(dot(up, y_axis), y_axis)); // `up` is not co-linear with hit test normal, use it1074 } else {1075 // Project the ray direction onto the plane, negate it and use as a Z axis.1076 z_axis = neg(sub(direction, mul(cos_direction_and_y_axis, y_axis))); // Z should point towards the ray origin, not away.1077 }1078 const x_axis = normalize(cross(y_axis, z_axis));1079 // Filter out the points not in polygon.1080 if (!XRMathHelper.pointInFace(intersection_point, face)) {1081 return null;1082 }1083 const hitResult = new device.mojom.XRHitResult();1084 hitResult.distance = distance; // Extend the object with additional information used by higher layers.1085 // It will not be serialized over mojom.1086 const matrix = new Array(16);1087 matrix[0] = x_axis.x;1088 matrix[1] = x_axis.y;1089 matrix[2] = x_axis.z;1090 matrix[3] = 0;1091 matrix[4] = y_axis.x;1092 matrix[5] = y_axis.y;1093 matrix[6] = y_axis.z;1094 matrix[7] = 0;1095 matrix[8] = z_axis.x;1096 matrix[9] = z_axis.y;1097 matrix[10] = z_axis.z;1098 matrix[11] = 0;1099 matrix[12] = intersection_point.x;1100 matrix[13] = intersection_point.y;1101 matrix[14] = intersection_point.z;1102 matrix[15] = 1;1103 hitResult.mojoFromResult = XRMathHelper.decomposeRigidTransform(matrix);1104 return hitResult;1105 }1106 }1107 }1108 _getMojoFromViewer() {1109 const transform = {1110 position: [1111 this.pose_.position.x,1112 this.pose_.position.y,1113 this.pose_.position.z],1114 orientation: [1115 this.pose_.orientation.x,1116 this.pose_.orientation.y,1117 this.pose_.orientation.z,1118 this.pose_.orientation.w],1119 };1120 return getMatrixFromTransform(transform);1121 }1122 _getMojoFromNativeOrigin(nativeOriginInformation) {1123 const mojo_from_viewer = this._getMojoFromViewer();1124 if (nativeOriginInformation.$tag == device.mojom.XRNativeOriginInformation.Tags.inputSourceId) {1125 if (!this.input_sources_.has(nativeOriginInformation.inputSourceId)) {1126 return null;1127 } else {1128 const inputSource = this.input_sources_.get(nativeOriginInformation.inputSourceId);1129 return inputSource._getMojoFromInputSource(mojo_from_viewer);1130 }1131 } else if (nativeOriginInformation.$tag == device.mojom.XRNativeOriginInformation.Tags.referenceSpaceType) {1132 switch (nativeOriginInformation.referenceSpaceType) {1133 case device.mojom.XRReferenceSpaceType.kLocal:1134 return XRMathHelper.identity();1135 case device.mojom.XRReferenceSpaceType.kLocalFloor:1136 if (this.stageParameters_ == null || this.stageParameters_.mojoFromFloor == null) {1137 console.warn("Standing transform not available.");1138 return null;1139 }1140 return this.stageParameters_.mojoFromFloor.matrix;1141 case device.mojom.XRReferenceSpaceType.kViewer:1142 return mojo_from_viewer;1143 case device.mojom.XRReferenceSpaceType.kBoundedFlor:1144 return null;1145 case device.mojom.XRReferenceSpaceType.kUnbounded:1146 return null;1147 default:1148 throw new TypeError("Unrecognized XRReferenceSpaceType!");1149 }1150 } else {1151 // Anchors & planes are not yet supported for hit test.1152 return null;1153 }1154 }1155}1156class MockXRSessionMetricsRecorder {1157 reportFeatureUsed(feature) {1158 // Do nothing1159 }1160}1161class MockXRInputSource {1162 constructor(fakeInputSourceInit, id, pairedDevice) {1163 this.source_id_ = id;1164 this.pairedDevice_ = pairedDevice;1165 this.handedness_ = fakeInputSourceInit.handedness;1166 this.target_ray_mode_ = fakeInputSourceInit.targetRayMode;1167 if (fakeInputSourceInit.pointerOrigin == null) {1168 throw new TypeError("FakeXRInputSourceInit.pointerOrigin is required.");1169 }1170 this.setPointerOrigin(fakeInputSourceInit.pointerOrigin);1171 this.setProfiles(fakeInputSourceInit.profiles);1172 this.primary_input_pressed_ = false;1173 if (fakeInputSourceInit.selectionStarted != null) {1174 this.primary_input_pressed_ = fakeInputSourceInit.selectionStarted;1175 }1176 this.primary_input_clicked_ = false;1177 if (fakeInputSourceInit.selectionClicked != null) {1178 this.primary_input_clicked_ = fakeInputSourceInit.selectionClicked;1179 }1180 this.primary_squeeze_pressed_ = false;1181 this.primary_squeeze_clicked_ = false;1182 this.mojo_from_input_ = null;1183 if (fakeInputSourceInit.gripOrigin != null) {1184 this.setGripOrigin(fakeInputSourceInit.gripOrigin);1185 }1186 // This properly handles if supportedButtons were not specified.1187 this.setSupportedButtons(fakeInputSourceInit.supportedButtons);1188 this.emulated_position_ = false;1189 this.desc_dirty_ = true;1190 }1191 // Webxr-test-api1192 setHandedness(handedness) {1193 if (this.handedness_ != handedness) {1194 this.desc_dirty_ = true;1195 this.handedness_ = handedness;1196 }1197 }1198 setTargetRayMode(targetRayMode) {1199 if (this.target_ray_mode_ != targetRayMode) {1200 this.desc_dirty_ = true;1201 this.target_ray_mode_ = targetRayMode;1202 }1203 }1204 setProfiles(profiles) {1205 this.desc_dirty_ = true;1206 this.profiles_ = profiles;1207 }1208 setGripOrigin(transform, emulatedPosition = false) {1209 // grip_origin was renamed to mojo_from_input in mojo1210 this.mojo_from_input_ = composeGFXTransform(transform);1211 this.emulated_position_ = emulatedPosition;1212 // Technically, setting the grip shouldn't make the description dirty, but1213 // the webxr-test-api sets our pointer as mojoFromPointer; however, we only1214 // support it across mojom as inputFromPointer, so we need to recalculate it1215 // whenever the grip moves.1216 this.desc_dirty_ = true;1217 }1218 clearGripOrigin() {1219 // grip_origin was renamed to mojo_from_input in mojo1220 if (this.mojo_from_input_ != null) {1221 this.mojo_from_input_ = null;1222 this.emulated_position_ = false;1223 this.desc_dirty_ = true;1224 }1225 }1226 setPointerOrigin(transform, emulatedPosition = false) {1227 // pointer_origin is mojo_from_pointer.1228 this.desc_dirty_ = true;1229 this.mojo_from_pointer_ = composeGFXTransform(transform);1230 this.emulated_position_ = emulatedPosition;1231 }1232 disconnect() {1233 this.pairedDevice_.removeInputSource(this);1234 }1235 reconnect() {1236 this.pairedDevice_.addInputSource(this);1237 }1238 startSelection() {1239 this.primary_input_pressed_ = true;1240 if (this.gamepad_) {1241 this.gamepad_.buttons[0].pressed = true;1242 this.gamepad_.buttons[0].touched = true;1243 }1244 }1245 endSelection() {1246 if (!this.primary_input_pressed_) {1247 throw new Error("Attempted to end selection which was not started");1248 }1249 this.primary_input_pressed_ = false;1250 this.primary_input_clicked_ = true;1251 if (this.gamepad_) {1252 this.gamepad_.buttons[0].pressed = false;1253 this.gamepad_.buttons[0].touched = false;1254 }1255 }1256 simulateSelect() {1257 this.primary_input_clicked_ = true;1258 }1259 setSupportedButtons(supportedButtons) {1260 this.gamepad_ = null;1261 this.supported_buttons_ = [];1262 // If there are no supported buttons, we can stop now.1263 if (supportedButtons == null || supportedButtons.length < 1) {1264 return;1265 }1266 const supported_button_map = {};1267 this.gamepad_ = this.getEmptyGamepad();1268 for (let i = 0; i < supportedButtons.length; i++) {1269 const buttonType = supportedButtons[i].buttonType;1270 this.supported_buttons_.push(buttonType);1271 supported_button_map[buttonType] = supportedButtons[i];1272 }1273 // Let's start by building the button state in order of priority:1274 // Primary button is index 0.1275 this.gamepad_.buttons.push({1276 pressed: this.primary_input_pressed_,1277 touched: this.primary_input_pressed_,1278 value: this.primary_input_pressed_ ? 1.0 : 0.01279 });1280 // Now add the rest of our buttons1281 this.addGamepadButton(supported_button_map['grip']);1282 this.addGamepadButton(supported_button_map['touchpad']);1283 this.addGamepadButton(supported_button_map['thumbstick']);1284 this.addGamepadButton(supported_button_map['optional-button']);1285 this.addGamepadButton(supported_button_map['optional-thumbstick']);1286 // Finally, back-fill placeholder buttons/axes1287 for (let i = 0; i < this.gamepad_.buttons.length; i++) {1288 if (this.gamepad_.buttons[i] == null) {1289 this.gamepad_.buttons[i] = {1290 pressed: false,1291 touched: false,1292 value: 01293 };1294 }1295 }1296 for (let i=0; i < this.gamepad_.axes.length; i++) {1297 if (this.gamepad_.axes[i] == null) {1298 this.gamepad_.axes[i] = 0;1299 }1300 }1301 }1302 updateButtonState(buttonState) {1303 if (this.supported_buttons_.indexOf(buttonState.buttonType) == -1) {1304 throw new Error("Tried to update state on an unsupported button");1305 }1306 const buttonIndex = this.getButtonIndex(buttonState.buttonType);1307 const axesStartIndex = this.getAxesStartIndex(buttonState.buttonType);1308 if (buttonIndex == -1) {1309 throw new Error("Unknown Button Type!");1310 }1311 // is this a 'squeeze' button?1312 if (buttonIndex === this.getButtonIndex('grip')) {1313 // squeeze1314 if (buttonState.pressed) {1315 this.primary_squeeze_pressed_ = true;1316 } else if (this.gamepad_.buttons[buttonIndex].pressed) {1317 this.primary_squeeze_clicked_ = true;1318 this.primary_squeeze_pressed_ = false;1319 } else {1320 this.primary_squeeze_clicked_ = false;1321 this.primary_squeeze_pressed_ = false;1322 }1323 }1324 this.gamepad_.buttons[buttonIndex].pressed = buttonState.pressed;1325 this.gamepad_.buttons[buttonIndex].touched = buttonState.touched;1326 this.gamepad_.buttons[buttonIndex].value = buttonState.pressedValue;1327 if (axesStartIndex != -1) {1328 this.gamepad_.axes[axesStartIndex] = buttonState.xValue == null ? 0.0 : buttonState.xValue;1329 this.gamepad_.axes[axesStartIndex + 1] = buttonState.yValue == null ? 0.0 : buttonState.yValue;1330 }1331 }1332 // Helpers for Mojom1333 getInputSourceState() {1334 const input_state = new device.mojom.XRInputSourceState();1335 input_state.sourceId = this.source_id_;1336 input_state.primaryInputPressed = this.primary_input_pressed_;1337 input_state.primaryInputClicked = this.primary_input_clicked_;1338 input_state.primarySqueezePressed = this.primary_squeeze_pressed_;1339 input_state.primarySqueezeClicked = this.primary_squeeze_clicked_;1340 // Setting the input source's "clicked" state should generate one "select"1341 // event. Reset the input value to prevent it from continuously generating1342 // events.1343 this.primary_input_clicked_ = false;1344 // Setting the input source's "clicked" state should generate one "squeeze"1345 // event. Reset the input value to prevent it from continuously generating1346 // events.1347 this.primary_squeeze_clicked_ = false;1348 input_state.mojoFromInput = this.mojo_from_input_;1349 input_state.gamepad = this.gamepad_;1350 input_state.emulatedPosition = this.emulated_position_;1351 if (this.desc_dirty_) {1352 const input_desc = new device.mojom.XRInputSourceDescription();1353 switch (this.target_ray_mode_) {1354 case 'gaze':1355 input_desc.targetRayMode = device.mojom.XRTargetRayMode.GAZING;1356 break;1357 case 'tracked-pointer':1358 input_desc.targetRayMode = device.mojom.XRTargetRayMode.POINTING;1359 break;1360 case 'screen':1361 input_desc.targetRayMode = device.mojom.XRTargetRayMode.TAPPING;1362 break;1363 default:1364 throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);1365 }1366 switch (this.handedness_) {1367 case 'left':1368 input_desc.handedness = device.mojom.XRHandedness.LEFT;1369 break;1370 case 'right':1371 input_desc.handedness = device.mojom.XRHandedness.RIGHT;1372 break;1373 default:1374 input_desc.handedness = device.mojom.XRHandedness.NONE;1375 break;1376 }1377 // Mojo requires us to send the pointerOrigin as relative to the grip1378 // space. If we don't have a grip space, we'll just assume that there1379 // is a grip at identity. This allows tests to simulate controllers that1380 // are really just a pointer with no tracked grip, though we will end up1381 // exposing that grip space.1382 let mojo_from_input = XRMathHelper.identity();1383 switch (this.target_ray_mode_) {1384 case 'gaze':1385 case 'screen':1386 // For gaze and screen space, we won't have a mojo_from_input; however1387 // the "input" position is just the viewer, so use mojo_from_viewer.1388 mojo_from_input = this.pairedDevice_._getMojoFromViewer();1389 break;1390 case 'tracked-pointer':1391 // If we have a tracked grip position (e.g. mojo_from_input), then use1392 // that. If we don't, then we'll just set the pointer offset directly,1393 // using identity as set above.1394 if (this.mojo_from_input_) {1395 mojo_from_input = this.mojo_from_input_.matrix;1396 }1397 break;1398 default:1399 throw new Error('Unhandled target ray mode ' + this.target_ray_mode_);1400 }1401 // To convert mojo_from_pointer to input_from_pointer, we need:1402 // input_from_pointer = input_from_mojo * mojo_from_pointer1403 // Since we store mojo_from_input, we need to invert it here before1404 // multiplying.1405 let input_from_mojo = XRMathHelper.inverse(mojo_from_input);1406 input_desc.inputFromPointer = new gfx.mojom.Transform();1407 input_desc.inputFromPointer.matrix =1408 XRMathHelper.mul4x4(input_from_mojo, this.mojo_from_pointer_.matrix);1409 input_desc.profiles = this.profiles_;1410 input_state.description = input_desc;1411 this.desc_dirty_ = false;1412 }1413 // Pointer data for DOM Overlay, set by setOverlayPointerPosition()1414 if (this.overlay_pointer_position_) {1415 input_state.overlayPointerPosition = this.overlay_pointer_position_;1416 this.overlay_pointer_position_ = null;1417 }1418 return input_state;1419 }1420 setOverlayPointerPosition(x, y) {1421 this.overlay_pointer_position_ = {x: x, y: y};1422 }1423 getEmptyGamepad() {1424 // Mojo complains if some of the properties on Gamepad are null, so set1425 // everything to reasonable defaults that tests can override.1426 const gamepad = {1427 connected: true,1428 id: "",1429 timestamp: 0,1430 axes: [],1431 buttons: [],1432 mapping: "xr-standard",1433 display_id: 0,1434 };1435 switch (this.handedness_) {1436 case 'left':1437 gamepad.hand = device.mojom.GamepadHand.GamepadHandLeft;1438 break;1439 case 'right':1440 gamepad.hand = device.mojom.GamepadHand.GamepadHandRight;1441 break;1442 default:1443 gamepad.hand = device.mojom.GamepadHand.GamepadHandNone;1444 break;1445 }1446 return gamepad;1447 }1448 addGamepadButton(buttonState) {1449 if (buttonState == null) {1450 return;1451 }1452 const buttonIndex = this.getButtonIndex(buttonState.buttonType);1453 const axesStartIndex = this.getAxesStartIndex(buttonState.buttonType);1454 if (buttonIndex == -1) {1455 throw new Error("Unknown Button Type!");1456 }1457 this.gamepad_.buttons[buttonIndex] = {1458 pressed: buttonState.pressed,1459 touched: buttonState.touched,1460 value: buttonState.pressedValue1461 };1462 // Add x/y value if supported.1463 if (axesStartIndex != -1) {1464 this.gamepad_.axes[axesStartIndex] = (buttonState.xValue == null ? 0.0 : buttonSate.xValue);1465 this.gamepad_.axes[axesStartIndex + 1] = (buttonState.yValue == null ? 0.0 : buttonSate.yValue);1466 }1467 }1468 // General Helper methods1469 getButtonIndex(buttonType) {1470 switch (buttonType) {1471 case 'grip':1472 return 1;1473 case 'touchpad':1474 return 2;1475 case 'thumbstick':1476 return 3;1477 case 'optional-button':1478 return 4;1479 case 'optional-thumbstick':1480 return 5;1481 default:1482 return -1;1483 }1484 }1485 getAxesStartIndex(buttonType) {1486 switch (buttonType) {1487 case 'touchpad':1488 return 0;1489 case 'thumbstick':1490 return 2;1491 case 'optional-thumbstick':1492 return 4;1493 default:1494 return -1;1495 }1496 }1497 _getMojoFromInputSource(mojo_from_viewer) {1498 return this.mojo_from_pointer_.matrix;1499 }1500}1501// Mojo helper classes1502class MockXRPresentationProvider {1503 constructor() {1504 this.binding_ = new mojo.Binding(device.mojom.XRPresentationProvider, this);1505 this.submit_frame_count_ = 0;1506 this.missing_frame_count_ = 0;1507 }1508 bindProvider(request) {1509 const providerPtr = new device.mojom.XRPresentationProviderPtr();1510 const providerRequest = mojo.makeRequest(providerPtr);1511 this.binding_.close();1512 this.binding_ = new mojo.Binding(1513 device.mojom.XRPresentationProvider, this, providerRequest);1514 return providerPtr;1515 }1516 getClientReceiver() {1517 this.submitFrameClient_ = new device.mojom.XRPresentationClientPtr();1518 return mojo.makeRequest(this.submitFrameClient_);1519 }1520 // XRPresentationProvider mojo implementation1521 submitFrameMissing(frameId, mailboxHolder, timeWaited) {1522 this.missing_frame_count_++;1523 }1524 submitFrame(frameId, mailboxHolder, timeWaited) {1525 this.submit_frame_count_++;1526 // Trigger the submit completion callbacks here. WARNING: The1527 // Javascript-based mojo mocks are *not* re-entrant. It's OK to1528 // wait for these notifications on the next frame, but waiting1529 // within the current frame would never finish since the incoming1530 // calls would be queued until the current execution context finishes.1531 this.submitFrameClient_.onSubmitFrameTransferred(true);1532 this.submitFrameClient_.onSubmitFrameRendered();1533 }1534 // Utility methods1535 Close() {1536 this.binding_.close();1537 }1538}...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1wpt = new WebPageTest('www.webpagetest.org');2 if (error) {3 console.log(error);4 } else {5 console.log(JSON.stringify(data));6 }7});8WebPageTest.prototype.mojoFromNativeOrigin = function(url, callback) {9 var params = {10 };11 this.makeRequest('mojoFromNativeOrigin', params, callback);12};13WebPageTest.prototype.makeRequest = function(method, params, callback) {14 var options = {15 path: '/runtest.php?' + querystring.stringify(params),16 };17 var req = http.request(options, function(res) {18 var data = '';19 res.on('data', function(chunk) {20 data += chunk;21 });22 res.on('end', function() {23 if (res.statusCode == 200) {24 try {25 var json = JSON.parse(data);26 callback(null, json);27 } catch (e) {28 callback(e);29 }30 } else {31 callback(new Error('Status code was ' + res.statusCode));32 }33 });34 });35 req.on('error', function(e) {36 callback(e);37 });38 req.end();39};40var http = require('http'),41 querystring = require('querystring');42var WebPageTest = function(host, port) {43 this.host = host;44 this.port = port || 80;45};46module.exports = WebPageTest;47var WebPageTest = require('./wpt.js');48var wpt = new WebPageTest('www.webpagetest.org');49wpt.mojoFromNativeOrigin('

Full Screen

Using AI Code Generation

copy

Full Screen

1importScripts("/resources/testharness.js");2importScripts("/resources/testharnessreport.js");3importScripts("/common/get-host-info.sub.js");4importScripts("/common/utils.js");5const origin = get_host_info().HTTPS_ORIGIN;6const crossOrigin = get_host_info().HTTPS_REMOTE_ORIGIN;7async_test(t => {8 const url = new URL("resources/echo.py?query", origin);9 const url2 = new URL("resources/echo.py?query", crossOrigin);10 const url3 = new URL("resources/echo.py?query", origin);11 const url4 = new URL("resources/echo.py?query", crossOrigin);12 const url5 = new URL("resources/echo.py?query", origin);13 const url6 = new URL("resources/echo.py?query", crossOrigin);14 const url7 = new URL("resources/echo.py?query", origin);15 const url8 = new URL("resources/echo.py?query", crossOrigin);16 const mojoFromNativeOrigin = new MojoFromNativeOrigin();17 mojoFromNativeOrigin.fetch(url.toString(), { mode: "no-cors" }).then(response => {18 assert_equals(response.type, "opaque");19 assert_equals(response.url, url.toString());20 assert_equals(response.status, 0);21 assert_equals(response.statusText, "");22 assert_equals(response.headers.get("x-request-url"), url.toString());23 assert_equals(response.headers.get("x-request-mode"), "no-cors");24 assert_equals(response.headers.get("x-request-credentials"), "omit");25 assert_equals(response.headers.get("x-request-redirect"), "follow");26 assert_equals(response.headers.get("x-request-referrer"), "");27 assert_equals(response.headers.get("x-request-integrity"), "");28 assert_equals(response.headers.get("x-request-destination"), "");29 assert_equals(response.headers.get("x-request-keepalive"), "false");30 assert_equals(response.headers.get("x-request-cache"), "default");31 assert_equals(response.headers.get("x-request-method"), "GET");32 assert_equals(response.headers.get("x-request-headers"), "");33 assert_equals(response.headers.get("x-request-body"), "");34 assert_equals(response.headers.get("x-request-context"), "fetch");35 return response.text();36 }).then(responseText => {37 assert_equals(responseText, "");38 return mojoFromNativeOrigin.fetch(url2.toString(), { mode: "no-cors" });39 }).then(response =>

Full Screen

Using AI Code Generation

copy

Full Screen

1const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;2const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;3const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;4const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;5const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;6const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;7const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;8const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;9const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;10const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;11const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;12const mojoFromNativeOrigin = require('wpt').mojoFromNativeOrigin;13const mojoFromNativeOrigin = require('

Full Screen

Using AI Code Generation

copy

Full Screen

1var wptRunner = require('./wptRunner.js');2 if (err) {3 console.log(err);4 } else {5 console.log(result);6 }7});8### wptRunner.mojoFromNativeOrigin(origin, callback)9[MIT](LICENSE)10* [Brendan Eich](

Full Screen

Using AI Code Generation

copy

Full Screen

1const wptfyi = require('wpt.fyi');2const mojoUrl = wptfyi.mojoFromNativeOrigin(url);3console.log(mojoUrl);4const wptfyi = require('wpt.fyi');5const nativeUrl = wptfyi.nativeFromMojoOrigin(url);6console.log(nativeUrl);

Full Screen

Using AI Code Generation

copy

Full Screen

1var mojo = MojoFromNativeOrigin();2mojo.then(function(mojo) {3 mojo.getInterface("mojo:content_browser").then(function(interface) {4 interface.getCookieManager().then(function(cookieManager) {5 cookieManager.getAllCookies().then(function(cookies) {6 console.log(cookies);7 });8 });9 });10});

Full Screen

Using AI Code Generation

copy

Full Screen

1var mojoService = mojoFromNativeOrigin("mojo:content_shell").getInterface(2 "content::mojom::Shell").connectToApplication("mojo:content_shell");3var mojoInterface = mojoService.getInterface("content::mojom::Shell");4var mojoOrigin = mojoInterface.mojoFromNativeOrigin("mojo:content_shell");5var mojoInterface = mojoOrigin.getInterface("content::mojom::Shell");6var mojoService = mojoInterface.connectToApplication("mojo:content_shell");7var mojoInterface = mojoService.getInterface("content::mojom::Shell");8var mojoService = mojoInterface.connectToApplication("mojo:content_shell");9var mojoInterface = mojoService.getInterface("content::mojom::Shell");10var mojoService = mojoInterface.connectToApplication("mojo:content_shell");11var mojoInterface = mojoService.getInterface("content::mojom::Shell");12var mojoService = mojoInterface.connectToApplication("mojo:content_shell");13var mojoInterface = mojoService.getInterface("content::mojom::Shell");14var mojoService = mojoInterface.connectToApplication("mojo:content_shell");15var mojoInterface = mojoService.getInterface("content::mojom::Shell");16var mojoService = mojoInterface.connectToApplication("mojo:content_shell");17var mojoInterface = mojoService.getInterface("content::mojom::Shell");18var mojoService = mojoInterface.connectToApplication("

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run wpt automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful