Một chỗ trong giao diện không hiện đúng, hoặc hiện rồi mà bấm không phản ứng. Bạn console.log khắp nơi trong code, build lại, tải lại trang, mò từng bước. Có cách nhanh hơn nhiều: OWL — engine giao diện của Odoo — để lộ toàn bộ cây component đang chạy ra cho bạn xem thẳng trong console trình duyệt. Không cần cài gì, không cần sửa code. Bài này chỉ cách nhìn vào cây đó, đọc state/props của bất kỳ component nào, và giải thích một chi tiết kiến trúc lộ ra khi soi: vì sao mỗi menu trên thanh điều hướng được bọc trong một ErrorHandler.

Cửa sau: window.OWL_DEVTOOLS

Khi OWL khởi động, nó gắn một hook toàn cục vào window. Đây là dòng thật trong lõi owl.js của Odoo 19:

window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, reactive });

Chính cái object này là thứ tiện ích Owl Devtools (extension trình duyệt) đọc để vẽ ra bảng component. Nhưng bạn không cần extension — mở console là dùng được ngay. apps là một Set chứa mọi "app" OWL trên trang; backend Odoo chỉ có một, và component gốc của nó là WebClient.

Ảnh chụp đoạn mã JavaScript nền tối hướng dẫn mở cửa sau của OWL trong console. Phần đầu gọi window OWL_DEVTOOLS trả về object gồm apps Fiber RootFiber toRaw reactive, rồi lấy app đầu tiên từ apps và root là ComponentNode gốc, in constructor name ra WebClient. Phần sau khai hàm duyet đệ quy nhận node và độ sâu, in tên component rồi lặp qua node children gọi đệ quy để in cả cây; cùng các dòng đọc node component state là state phản ứng, node props là props từ cha, và node parent component constructor name là component cha. Cuối cùng nhắc bật chế độ dev bằng debug bằng 1 trên URL

Hình 1: Vài câu lệnh mở "cửa sau" của OWL. __OWL_DEVTOOLS__.apps cho bạn app gốc; từ app.root (một ComponentNode) đi xuống theo .children là duyệt được cả cây. Mỗi node có .component (instance thật), .props (nhận từ cha) và .parent.

this.owl: mỗi component mang node của chính nó

Điểm mấu chốt để hiểu cách OWL tự soi: mỗi instance component đều có một thuộc tính this.__owl__ trỏ tới ComponentNode của nó. Lõi gán đúng một dòng:

this.__owl__ = node;

ComponentNode là "hồ sơ" của component trong cây: nó giữ component (instance), props, app, parent, và children. Nắm được một node là bạn đi được lên (.parent) hay xuống (.children) cả cây, và đọc được mọi thứ component đang giữ.

Đọc cả cây từ gốc

Chạy hàm duyệt đệ quy ở Hình 1 trên backend Odoo, đây là cây thật in ra:

Ảnh chụp console nền tối in cây component thật trên Odoo 19 backend action-918. Gốc là WebClient màu vàng, con là NavBar, dưới NavBar có Dropdown, DropdownItem, DropdownGroup chứa bốn DropdownItem. Tiếp theo là bốn khối ErrorHandler màu hồng, mỗi khối bọc một menu systray: ErrorHandler bọc ActivityMenu, ErrorHandler bọc CallMenu, ErrorHandler bọc MessagingMenu có Dropdown con, ErrorHandler bọc SwitchCompanyMenu có DropdownGroup con, kèm chú thích mỗi menu systray bọc trong một error boundary. Cuối cùng là dấu ba chấm

Hình 2: Cây component thật của backend Odoo. Gốc là WebClient, dưới nó là NavBar, rồi các Dropdown. Để ý bốn khối ErrorHandler bọc lần lượt ActivityMenu, CallMenu, MessagingMenu, SwitchCompanyMenu — mục sau giải thích vì sao.

Chỉ nhìn cây này bạn đã hiểu cấu trúc giao diện Odoo mà không cần đọc một dòng template nào: menu trên cùng là NavBar, các biểu tượng góc phải (chuông thông báo, tin nhắn, chuyển công ty) là những component systray riêng.

Xem state và props của từng component

Có node rồi, đọc trạng thái sống của nó. node.component.state là state phản ứng (reactive), node.props là props cha truyền vào. Đây là dữ liệu thật lấy từ ba component đầu cây:

Ảnh chụp console nền tối đọc state và props thật của các component đang chạy trên Odoo 19. Dòng đầu OWL_DEVTOOLS apps size trả về 1, root component constructor name trả về WebClient. Tiếp theo WebClient gốc có props rỗng và state gồm fullscreen. NavBar con của WebClient có state gồm isAllAppsMenuOpened và isAppMenuSidebarOpened. Dropdown có props gồm slots và state gồm isOpen, gạch dưới onOpen, gạch dưới onClose. Cuối cùng chú thích state là reactive, sửa nó trong console thì giao diện vẽ lại

Hình 3: state/props thật. WebClient giữ fullscreen; NavBar giữ hai cờ đóng/mở menu; Dropdown nhận prop slots và giữ isOpen. Vì state là reactive, sửa thẳng nó trong console (ví dụ node.component.state.isOpen = true) sẽ khiến OWL vẽ lại component đó — cách nhanh để thử một trạng thái mà không cần thao tác chuột.

Đây là điểm khiến việc debug nhanh hẳn: thay vì đoán "state lúc này là gì", bạn đọc thẳng nó; thay vì tìm cách tái hiện một trạng thái, bạn gán thẳng vào state reactive và xem giao diện phản ứng.

Vì sao mỗi menu bọc trong ErrorHandler

Quay lại bốn khối ErrorHandler ở Hình 2. ErrorHandler là hiện thân của error boundary trong OWL: một component cha bắt lỗi render/hook của con qua hook onError, để lỗi của con không kéo sập cả cây phía trên. Odoo bọc mỗi menu systray trong một ErrorHandler riêng vì lý do rất thực dụng: các menu này do nhiều module khác nhau đóng góp (chat, hoạt động, chuyển công ty...). Nếu MessagingMenu của module chat gặp lỗi mà không có lưới, nó sẽ làm hỏng luôn cả NavBar — người dùng mất sạch thanh điều hướng chỉ vì một biểu tượng lỗi. Có ErrorHandler bao quanh, menu lỗi tự gục một mình, phần còn lại vẫn chạy.

Nhìn cây component, bạn thấy quyết định kiến trúc đó hiện ra thành cấu trúc — điều mà đọc code rời rạc khó nhận ra.

Vài mẹo debug thực dụng

  • Bật ?debug=1 trên URL để vào chế độ nhà phát triển: OWL kiểm tra props chặt hơn và log nhiều hơn, dễ bắt lỗi kiểu "truyền thiếu prop".
  • Chọn phần tử trong tab Elements rồi dùng $0 trong Console — nhưng nhớ __owl__ nằm trên instance component, không phải phần tử DOM; từ $0 hãy đi ngược lên qua app.root hoặc dùng extension Owl Devtools để ánh xạ DOM ↔ component.
  • __OWL_DEVTOOLS__.toRaw gỡ lớp proxy reactive khỏi một state để in ra giá trị "trần", dễ đọc hơn khi state lồng nhau nhiều tầng.
  • Đọc node.parent khi một component nhận sai prop: đi lên cha xem chính xác nó truyền gì xuống.

Ba ý mang về

  1. OWL để lộ cả cây component qua window.__OWL_DEVTOOLS__ ({ apps, Fiber, RootFiber, toRaw, reactive }) và qua this.__owl__ trên mỗi instance — không cần cài gì, mở console là soi được từ gốc WebClient xuống.
  2. Mỗi ComponentNode cho bạn component, state, props, parent, children; state là reactive nên đọc thẳng để debug, gán thẳng để thử trạng thái và xem giao diện vẽ lại.
  3. Cây component tiết lộ kiến trúc — bốn ErrorHandler bọc bốn menu systray chính là các error boundary giữ cho một menu lỗi không kéo sập cả NavBar.

Soi được front-end rồi, phần sau ta quay xuống tầng server nhưng vẫn phía "web": cách Odoo nhận và trả lời một request HTTP. Phần sau mổ xẻ http.Controller và @route — cách khai một URL, các kiểu type, và request đi qua đâu trước khi tới hàm của bạn.