- BBC UK वेबसाइट पर More बटन कुछ खास work-from-home सेटअप में ही क्लिक को सही तरह प्रोसेस नहीं कर रहा था; दिखने में साधारण UI bug असल में multi-monitor coordinate system की समस्या निकला
- जब बाहरी मॉनिटर मुख्य मॉनिटर के ऊपर या बाईं ओर रखा हो, तो Chrome और Firefox के
click event में screenX, screenY नकारात्मक हो सकते थे
- मौजूदा code pointer click पहचानने के लिए
event.screenX > 0 || event.screenY > 0 का उपयोग कर रहा था, इसलिए negative coordinate click को mouse click नहीं माना गया
- fix सरल था:
screenX, screenY 0 से बड़े हैं या नहीं, यह देखने के बजाय 0 नहीं हैं या नहीं, यह जांचना; यानी event.type === 'click' && (event.screenX!== 0 || event.screenY!== 0)
- unit test, Puppeteer, manual test और assistive technology test से गुजरने के बावजूद, UI Events spec की अस्पष्टता और multi-monitor coordinate assumptions की वजह से ऐसा bug बचा रह सकता है
सिर्फ़ खास वातावरण में दोहराया गया BBC navigation bug
- BBC UK वेबसाइट का navigation bar तब menu खोलता है जब उपयोगकर्ता More बटन को activate करता है
- यह बटन
click event का उपयोग करता है, और यह event सिर्फ़ mouse से नहीं बल्कि touch, keyboard के Enter, Space से भी हो सकता है
- टीम के एक सदस्य को यह समस्या सिर्फ़ घर से काम करते समय अपने work laptop पर हुई, जबकि वही laptop ऑफिस में ठीक काम करता था
- घर पर भी यह सिर्फ़ तब fail होता था जब browser window बाहरी मॉनिटर पर होती थी; laptop स्क्रीन पर बटन सामान्य रूप से काम करता था
- समस्या होने पर JavaScript handler menu खोलने के बजाय no-JavaScript fallback व्यवहार के जरिए menu खोलता था
- Safari में यही समस्या नहीं दिखी
दोहराव की शर्त: मॉनिटर की स्थिति
- टीम ने घर के सेटअप में कौन-सा factor समस्या पैदा कर रहा है, यह पता करते हुए reproduction condition को संकीर्ण किया
- बाहरी मॉनिटर laptop स्क्रीन के ऊपर रखा गया था, और OS settings में यह व्यवस्था बदलने पर समस्या रुक गई
- दूसरे टीम सदस्य ने भी OS में मॉनिटर की वही व्यवस्था सेट की तो bug दोहराया जा सका
- जांच की शुरुआती अवस्था में दो बातें सामने आईं
- Safari में समस्या नहीं होती थी
- जब बाहरी मॉनिटर मुख्य मॉनिटर के ऊपर और बाईं ओर हो, तब समस्या होती थी
screenX, screenY के नकारात्मक coordinate
More बटन के click event को console.log से देखने पर Chrome और Firefox में screenX, screenY की value negative आई
click event, चाहे किसी भी input से आया हो, PointerEvent का एक प्रकार है, इसलिए event object में click पैदा करने वाले mouse या touch pointer की जानकारी शामिल होती है
screenX, screenY स्क्रीन पर क्लिक हुए बिंदु के coordinate को pixel में दिखाते हैं
- DOM UI Events spec में यह स्पष्ट जानकारी नहीं मिली कि ये properties negative हो सकती हैं या नहीं
- Safari और Chrome/Firefox के बीच का अंतर दिखाता है कि multi-monitor configuration में browser के screen coordinates दिखाने का तरीका अलग हो सकता है
- इस interoperability समस्या की रिपोर्ट WebKit टीम को दी गई
browser के अनुसार multi-monitor coordinate तरीका अलग
- multi-monitor setup में browser का screen coordinate system कई मॉनिटर को एक बड़ी स्क्रीन की तरह मानता है
- अगर 800px के 2 मॉनिटर क्षैतिज रूप से लगे हों, तो x coordinate range 0 से 1600 तक हो सकती है
- Safari में coordinate range हमेशा सबसे ऊपर-बाएँ मॉनिटर से शुरू होने वाली positive range जैसी दिखती है
- Chrome और Firefox में coordinates मुख्य मॉनिटर के आधार पर calculate होते दिखते हैं, इसलिए मुख्य मॉनिटर से ऊपर या बाईं ओर मौजूद स्क्रीन पर negative coordinates आ सकते हैं
- इस bug में समस्या सिर्फ़ तब हुई जब
screenX, screenY negative थे
असली समस्या वाला code और fix
- समस्या वाले code में
isInvokedByMouse, click event mouse या touch pointer से आया है या नहीं, यह जानने के लिए screenX, screenY positive हैं या नहीं, यह जांच रहा था
const isInvokedByMouse = event => event.screenX > 0 || event.screenY > 0;
const isInvokedByKeyboard = event => isEnterKey(event) || isSpaceKey(event);
// ...
const toggleMenu = event => {
// ...
if (isInvokedByMouse(event) || isInvokedByKeyboard(event)) {
event.preventDefault();
// Do stuff to open the menu and move the focus...
}
};
- यह code मानकर चल रहा था कि pointer से आए
click event के screenX, screenY positive होंगे
- जब उपयोगकर्ता ऐसे मॉनिटर पर
More बटन क्लिक करता था जिसका screen coordinate negative था, तो event handler उस click को मान्य नहीं मानता था और More link की default action पर गिर जाता था
- fix यह था कि
screenX, screenY 0 से बड़े हैं या नहीं, यह देखने के बजाय यह देखा जाए कि वे 0 नहीं हैं
const isInvokedByMouse = event =>
event.type === 'click' && (event.screenX !== 0 || event.screenY !== 0);
- इस बदलाव से अब असामान्य multi-monitor layout इस्तेमाल करने वाले उपयोगकर्ता भी BBC वेबसाइट का navigation bar इस्तेमाल कर सकते हैं
बाकी design issues और बाद की refactoring
- fix अपने आप में सरल था, लेकिन code में कुछ अजीब हिस्से अब भी बचे थे
- यह जांचने की ज़रूरत नहीं थी कि
click mouse से आया या keyboard से, और event handler keydown event तक संभाल रहा था, जिससे complexity बढ़ गई थी
- API के behavior के बारे में कौन-सी assumptions रखी जा रही हैं, इस पर सावधानी चाहिए;
screenX, screenY negative हो सकते हैं या नहीं, इस बारे में spec का अस्पष्ट होना भी समस्या को छिपाने वाला कारक था
- यह code unit test, Puppeteer test, कई browsers/devices और assistive technology tools के साथ manual testing से गुजर चुका था, फिर भी bug पकड़ा नहीं गया
- 19 नवंबर 2024 के update के अनुसार, navigation component बाद में refactor किया गया और
menu button event handler भी काफी बदल गया
- अगला लेख refactoring के तरीके और बार-बार पूछे गए सवालों के जवाब देता है: How I refactored the BBC navigation bar and a follow-up FAQ
1 टिप्पणियां
Hacker News टिप्पणियाँ
जिन लोगों ने WebKit bug report तक क्लिक करके नहीं देखा, उनके लिए संदर्भ: WebKit developer ने BBC से पूछा कि keyboard से आया event detect कर पाना क्यों उपयोगी होगा, और लेखक ने जवाब दिया कि accessibility से जुड़े use case के कारण interoperability ज़रूरी है
BBC UK website के navigation bar menu button का व्यवहार pointer से खोलने और keyboard से खोलने पर थोड़ा अलग है। click event हमेशा menu खोलता है, लेकिन pointer से खोलने पर focus menu container पर चला जाता है, और keyboard से खोलने पर menu open animation के बिना focus menu के पहले link पर चला जाता है।
clickevent device-independent होता है, इसलिए keyboard user experience बनाते समय अच्छा है, और keyboard पर यह सिर्फ Space या Enter से ही invoke होता है।keydownइस्तेमाल करने पर खुद check करना पड़ता है कि यह Space/Enter है या नहींस्रोत: https://bugs.webkit.org/show_bug.cgi?id=281430
const isInvokedByMouse = event => event.screenX > 0 || event.screenY > 0;औरconst isInvokedByKeyboard = event => isEnterKey(event) || isSpaceKey(event);, जो सतह पर ऐसा लगता है मानो event को mouse या keyboard, इनमें से किसी एक में classify करने की कोशिश हो रही हैअसल में चार categories बनती हैं: mouse है और keyboard नहीं, keyboard है और mouse नहीं, दोनों है, दोनों नहीं। मूल bug की तरह “दोनों नहीं” को गलत तरीके से handle किया जाता है, और “दोनों है” भी सही से काम करता है या नहीं, इस पर शक होता है। code को यह बात जानबूझकर handle करनी चाहिए कि keyboard-ness और mouse-ness अलग-अलग boolean हैं, या फिर
eventSourceको"keyboard","mouse","not sure"जैसी mutually exclusive categories return करने के लिए बनाया जाना चाहिएdefault behavior के साथ align करके ऐसा component design करना बेहतर है जो दोनों use cases में काम करे। accessibility में over-smart बनने की कोशिश नहीं करनी चाहिए। अंत में समाधान लगभग hack जैसा बन गया, और ऐसे तरीके अनिवार्य रूप से टूटते हैं या side effects पैदा करते हैं। accessibility context में अलग तरह से handle करने के अच्छे handles कम मिलने की वजह यही है कि यह क्षेत्र शुरुआत से ही अलग तरह से handle करने के लिए intended नहीं था
साथ ही वह केवल एक event पर bind करने की सुविधा भी चाहता है।
clickयह संभव बनाता है, लेकिन event mouse click से हुआ या keyboard input से, यह जानने का तरीका नहीं है, इसलिए Chrome में mouse positionscreenX=0,screenY=0हो तो उसे origin click या keyboard trigger मानने वाला unstable heuristic इस्तेमाल किया जा रहा है। accessibility projects पर काम कर चुके व्यक्ति के तौर पर यह काफी खराब idea है, और PR में देखा होता तो फिर से लिखने को कहता। browsers का same behavior रखना ideal तो है, लेकिन असली समस्या यह लगती है कि keyboard से हुएclickमेंscreenXऔरscreenYका अर्थ लगभग नहीं होताIdeally,
MouseEventemit करने के बजाय keyboard और mouse दोनों पर लागू होने वाला कोई ज्यादा general event होना चाहिए, जैसे"trigger", और trigger source की जानकारी देनी चाहिए। चूंकि यह अभी spec में नहीं है और अभी समाधान चाहिए, तोkeydownपर भी bind करके, उसी element परkeydownके साथclickआए तो उसे keyboard input मानना कहीं ज्यादा stable और कम hacky होगाscreenXऔरscreenYकी ज़रूरत क्यों है, लेकिन यह अब भी सवाल है किscreenXको renderer के अंदर की position या rendered page position जैसेlayerX,layerYके बजाय actual screen coordinates क्यों return करने चाहिएलेखक की ज़रूरत renderer position से भी पूरी हो सकती है, और browser window की position हर visit की गई website को leak करने की ज़रूरत नहीं पड़ेगी
don’tशायद typo है, जो intended meaning का उल्टा बना रहा है“
isInvokedByMouseपहले check करता था किscreenXऔरscreenY0 से बड़े हैं या नहीं, बस इसे 0 न होने की check में बदलना था” वाले हिस्से पर, यह बहुत rare होगा, लेकिन अगर user सचमुच 0,0 position पर mouse click करे तो क्या होगा, यह जानने की उत्सुकता हैमैं JS से बहुत परिचित नहीं हूँ, क्या
!= 0check सच में सबसे अच्छा या इकलौता तरीका है? दोबारा पढ़ने पर लगता है कि event handlerkeydownभी handle करता है इसलिए complexity है और बाद में और refactoring करनी होगी, लेकिन अभी के लिए यह fix काफी है—यह वाक्य इस हिस्से को कुछ हद तक address करता हैinstanceof MouseEventइस्तेमाल करने की सोचता, लेकिन वह भी risky या hack जैसा लगता हैसवाल है कि ऐसे heuristic पर निर्भर क्यों हैं। शायद इसलिए कि
toggleMenuकई event handlers में इस्तेमाल होता है, या codebase की अपनी कोई और वजह हो सकती है। पूरी तस्वीर जाने बिना judgement देना मुश्किल है। जवाब शायद यहाँ है: https://news.ycombinator.com/item?id=42174177event.name == 'click'check किया जा रहा है। तो समझ नहीं आता कि कुछ valid click events को filter out क्यों करना हैपहले मैंने इसका इस्तेमाल यह चुनने के लिए किया था कि कौन सा layout दिखाना है। अगर आप सिर्फ touch input सुनना चाहते हैं, तो वैसा करके event में
preventDefaultcall कर सकते हैं ताकि browser आगेclickevent न बनाए। या फिर बस मेहनत कम करके click handler लिख देंBBC ने accessibility में निवेश करते हुए एक अप्रिय bug खोजा, यह सराहनीय है। लेकिन industry अब तक ऐसा dropdown ठीक से क्यों नहीं बना पाई जो सभी users के लिए consistently खुले?
क्या accessibility सच में इतनी कठिन है? क्या BBC को पहले से ये सब handle करने वाला कोई web framework या web component इस्तेमाल करना चाहिए था? Backend-केंद्रित full-stack developer होने के नाते browser components में हाथ डालना मुझे जोखिम भरा लगता है। Behaviour में बहुत सारी बारीकियां होती हैं और implementations लंबे समय से tested होती हैं। उदाहरण के लिए, custom text box बनाते समय platform-specific text box behaviour की गहराई से जांच न करना आसानी से fail हो सकता है। बड़ी companies की sites पर भी copy/paste टूटना और characters गायब होना अक्सर दिखता है। 2024 में text box क्यों टूटते हैं, समझ नहीं आता, और अब React arrogant-सा लगने लगा है
निजी तौर पर मैं server-side templates, Bulma जैसे CSS framework, और न्यूनतम JS से काम चलाने की कोशिश करता। Smooth custom branding मांगने वाली sites के लिए यह सही नहीं है, लेकिन text boxes ठीक चलते हैं और development cost भी बहुत ज्यादा नहीं होती। यह BBC के accessibility standards को satisfy करेगा या नहीं, इसका भरोसा नहीं है
एक वास्तविक उदाहरण modal है। अगर कोई visual impairment नहीं है, तो आप gray “छूना नहीं” area के ऊपर एक white box और उसके अंदर floating UI components देख सकते हैं। Screen reader इस्तेमाल करने पर यह guarantee नहीं कि आपको वही जानकारी मिलेगी। Tab से UI elements में घूमते हुए जब आप box के top पर वापस आते हैं, तो क्या कोई specific screen reader यह बताएगा? क्या वह available interactive elements की list देगा? क्या वह उन्हें दूसरे screen reader जैसी ही order में list करेगा? Phone पर क्या होगा, Mac पर क्या होगा? क्या screen reader और browser input elements को ठीक से report करेंगे, या user को चुपचाप modal से बाहर निकलकर site के बाकी हिस्से में लौटने देंगे?
Accessibility में आप भरोसा नहीं कर सकते कि operating system, browser और screen reader cooperate करेंगे या सही context में reasonable तरीके से behave करेंगे। 2019 में VoiceOver + Safari में negative CSS margin के कारण RTL text block को screen reader द्वारा उल्टे order में पढ़े जाने वाला bug report करना पड़ा था। Visually यह
9/10/2019दिखता था, लेकिन screen reader में “ten slash nine slash two-thousand-and-nineteen” जैसा सुनाई देता था, और workaround के तौर पर text कोaria-hiddenकरना पड़ा और सही order वाला invisibleptag डालना पड़ा। इसलिए accessibility से जुड़ा अजीब code दिखे तो कई बार सच में उससे बेहतर तरीका नहीं होता। Codebase को पूरी तरह पलटकर accessibility को top priority बना दें, तब भी JAWS या VoiceOver update आते ही यह समझ से बाहर तरीके से टूट सकता हैआम तौर पर सब ठीक रहता है, लेकिन
reset.cssfiles मौजूद होने की वजह है, और यहां लगता है कि ऐसी problems को पूरी तरह bypass करने के लिए शायद और extreme approach अपनाया गया। मैं उनके decision का अनुमान लगाने की कोशिश कर रहा हूंयह गलत heuristic से पैदा हुआ खुद बुलाया हुआ bug लगता है। Positive
screenX/Yvalues को mouse event मान लिया गया, और tracking/logging की कमी ने investigation को और complicated बना दियादूसरे comments ने जो ज्यादा appropriate property
pointerTypecheck करने का सुझाव दिया, उसके बजाय author का solution डगमगाते heuristic पर और patches चढ़ाना है, यह थोड़ा हैरान करता है। जैसे अंतिम दो clues से यह conclusion निकाला गया किscreenXऔरscreenYcoordinates check करते समय सिर्फ positive नहीं, negative भी check करने चाहिएpointerId === -1इस्तेमाल होगा, और फिरscreenX === 0पर fallback होगाकरीब 4 साल पहले जब यह code पहली बार लिखा गया था, तब सभी browsers
clickके लिए PointerEvent इस्तेमाल नहीं करते थेमुझे समझ नहीं आता कि website को शुरू से ही screen coordinate system में mouse position क्यों मिल सकती है
window.screenX/window.screenYजान सकती है और click position भी उसी coordinate system में report हो सकती है—desktop पर यह बेतुका लगता हैTOR Browser fingerprinting से बचने के लिए शायद
screenXऔरscreenYको mask करता है। सोच रहा हूं कि किसी ने इस feature का अच्छा use case देखा है क्या। मन में बस interacting dual-window applications, या virtual screen में position के आधार पर अलग behave करने वाली sites आती हैंउदाहरण: https://youtu.be/3al8prbfK5o?si=loNtyqIfMFkppm5V
event.typecheck करने के बजाय coordinates क्यों check किए जा रहे हैं, यह समझ नहीं आता। फिर भी लेख अपने आप में अच्छा puzzle है, और किसी और के लिखे code को देखते हुए “click coordinates का 0 न होना क्यों important है?”, “बसevent.targetcheck नहीं कर सकते कि वही button activate करना है?”, “details/summarytags से वही काम हो सकता है तो JavaScript क्यों?” जैसे सवाल पूछने वाली situation relatable हैशुरुआत में ही screen coordinates से filter क्यों किया जा रहा है? अगर user बिना screen वाले alternative input device का इस्तेमाल कर रहा हो तो क्या होगा?
सिर्फ
clickevent ही यह बताने के लिए काफी signal है कि user menu activate करना चाहता था। Wheel को फिर से invent क्यों किया जा रहा है, समझ नहीं आताisInvokedByMouseयह जांचने के लिएscreenXयाscreenYcoordinates positive हैं या नहीं देखता था किclickevent keyboard से नहीं, बल्कि mouse या touch pointer से invoke हुआ हैयानी keyboard activation और mouse activation detect करने की कोशिश थी, और author ने मान लिया था कि mouse event के screen coordinates हमेशा positive होंगे
लोगों की जिज्ञासा वाला context समझाने और सवालों के जवाब देने के लिए एक और blog post डाली है। इसमें बताया है कि मैंने शुरुआत में ही
screenX === 0क्यों check किया था, keyboard और mouse input के हिसाब से अलग behavior क्यों चाहिए था, और आगे की गड़बड़ियों को रोकने के लिए कैसे refactor कियाउम्मीद है मदद मिलेगी: https://www.joshtumath.uk/posts/2024-11-18-how-i-refactored-...
यह पता करने का सही तरीका क्या है कि यह mouse click है या keyboard click? मुझे लगता है कि सबसे हाल में हुई event के आधार पर module-level flag set करना चाहेंगे: अगर
mousedownज्यादा recent है तोisKeyboard=false,isMouse=true, और अगरkeydownज्यादा recent है तो इसका उल्टातब
isInvokedByMouseऔरisInvokedByKeyboardfunctions की जरूरत नहीं रहेगी। क्या कोई बेहतर तरीका है? इसके लिए screen coordinates पर निर्भर रहना बहुत संदिग्ध और hack जैसा लगता हैevent.detail[1] keyboard “click” में 0 होता है और pointer click में 11: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/det...
बहुत दिलचस्प है, लेकिन समझ नहीं आता कि browser monitor के हिसाब से अलग coordinates क्यों report करता है। मुझे लगा था browser webpage को ऐसा treat करता है जैसे वह full screen हो, चाहे वह किसी भी display पर हो
Web API के पास ऐसी जानकारी होने की कोई वजह है क्या? यह security, information leakage और tracking risk जैसा लगता है
क्या यह development competency की समस्या नहीं है? screen coordinates नहीं, viewport coordinates इस्तेमाल करने चाहिए थे, और उन्हें
.clientXऔर.clientYसे पढ़ना चाहिए था। screen space में negative value आना bug क्यों है, यह समझ नहीं आताhttps://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_view/...