全書地址前端
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zhlinux
Chromium是一個巨大而複雜的跨平臺產品。咱們試圖在不一樣平臺間共享盡量多的代碼,同時爲每一個平臺用最合適的方式實現UI和操做系統集成。這提供了一個更好的用戶體驗,但它給代碼增長了額外的複雜度。這個文檔描述了保持這種跨平臺代碼簡潔性的推薦實踐。ios
咱們使用大量不一樣帶後綴的文件來表示一個文件應該被使用的時機:git
獨立的瀏覽器後端文件放在他們本身的目錄裏:github
編碼風格 頁面列出一些風格上影響平臺相關定義的規則。chrome
當你有一個有着許多共享函數或數據成員和些許不一樣之處的類,在平臺相關的部分使用#ifdefs。若是沒有顯著的差別,這會讓每一個人將每件事隔離開更加容易。後端
可能有這樣的狀況,頭文件幾乎沒有差異,部分實現有巨大的實現差別。例如base/waitable_event.h定義了一個通用的有着大量平臺差別的API。瀏覽器
有着顯著的實現差別,實現文件能夠被隔離出來。這能夠避免你陷入一個必須在include必要文件中爲每一個平臺寫一大堆#ifdef,而且使得追蹤源碼更容易(三個版本的函數集的代碼放在同一個文件裏可能使人困惑)。每一個平臺能夠有不一樣的.cc文件,正如base/waitable_event_posix.cc中實現posix相關函數。若是在這個類裏有跨平臺的函數,他們應該被丟到一個名爲base/waitable_event.cc的函數。框架
當抽象層面沒有東西實現,就要在每一個單獨的文件裏分別實現類。
若是全部的實現都在跨平臺目錄中,好比base,他們應該用平臺的名字命名,好比base/foo_bar_win.h中的FooBarWin。這種例子一般不多,由於這些跨平臺的文件一般設計用於跨平臺代碼,獨立的頭文件使得這種例子變得不可能。在一些地方,咱們已經在不一樣的文件裏定義了一個普通命名的類,因此PlatformDevice定義在skia/ext/platform_device_win.h, skia/ext/platform_device_linux.h, and skia/ext/platform_device_mac.h。若是你真的須要在跨平臺代碼裏引用這個類,這是OK的。但一般,這種例子會變得遵循下面的這種規則。
若是實現存在於平臺相關目錄,好比chrome/browser/ui/cocoa或chrome/browser/ui/views,這個類就沒有機會用於跨平臺代碼了。這種狀況下,這個類和文件名應該忽略平臺的名字,由於它是多餘的。因此FooBar是在chrome/browser/ui/cocoa/foo_bar.h中實現的。
不要爲每一個平臺建立不一樣的類,又把它們用typedef定義爲同一個名字。咱們曾經在PlatformCanvas上使用這種套路,根據平臺,它被typedef爲PlatformCanvasMac, PlatformCanvasLinux, 或 PlatformCanvasWin。這樣就不可能提早聲明這個類,而這是一個減少依賴的重要工具。
一般,抽象接口與工廠不該該做爲隔離平臺差別的惟一目的。相反的,它應該用於將接口與優化代碼設計的實現隔離開來。這最常常出如今從model中抽離view的實現中,好比TabContentsView或者RenderWidgetHostView。在這些例子裏,model不依賴view的實現是有必要的。在許多狀況下,多個平臺的view只有一個實現,但爲未來的開發提供了更乾淨的隔離與更多的可擴展性。
在有些地方,像TabContentsView,抽象層沒有非抽象的、在平臺間共享的函數。避免這種寫法。若是不一樣view之間的代碼老是同樣,它可能首先就不該該在view中。
一般,從已有的平臺相關的用戶界面元素構建其餘平臺相關的用戶界面元素。例如,view相關的類BrowserView負責構建許多瀏覽器對話框盒子。一種方法是,在一個平臺無關的接口裏包裝UI元素,而後經過一個工廠,從一個model構造出它來。這是至關沒必要要的,由於它讓迷亂了歸屬關係:大多數工廠構造的例子裏,UI元素最後歸屬於建立它的model。然而在許多例子裏,UI元素最容易由它所屬的UI框架管理。例如,一個views::View歸屬於它的view層級,而且在包含它的window被銷燬時,會自動被銷燬。若是有一個對話框 views::View實現了一個平臺無關的接口,而後被另外一個對象擁有,那麼views::View實例如今須要顯式地告訴它的view層級不要去幹涉它的生命週期。
e.g. 推薦這種寫法:
// browser.cc: Browser::ExecuteCommand(..) { ... case IDC_COMMAND_EDIT_FOO: window()->ShowFooDialog(); break; ... } // browser_window.h: class BrowserWindow { ... virtual void ShowFooDialog() = 0; ... }; // browser_view.cc: BrowserView::ShowFooDialog() { views::Widget::CreateWindow(new FooDialogView)->Show(); } // foo_dialog_view.cc: // FooDialogView和FooDialogController在window被關閉的時候會被自動清理 class FooDialogView : public views::View { ... private: scoped_ptr<FooDialogController> controller_; // 跨平臺狀態控制邏輯 ... }
不推薦這種
// browser.cc: Browser::ExecuteCommand(..) { ... case IDC_COMMAND_EDIT_FOO: { FooDialogController::instance()->ShowUI(); break; } ... } // foo_dialog_controller.h: class FooDialog { public: static FooDialog* CreateFooDialog(FooDialogController* controller); virtual void Show() = 0; virtual void Bar() = 0; }; class FooDialogController { public: ... static FooDialogController* instance() { static FooDialogController* instance = NULL; if (!instance) instance = Singleton<FooDialogController>::get(); return instance; } ... private: ... void ShowUI() { if (!dialog_.get()) dialog_.reset(FooDialog::CreateFooDialog(this)); dialog_->Show(); } // 爲何要把FooDialog或者甚至FooDialogController放在外面? // 大多數dialog起始不多被用到 scoped_ptr<FooDialog> dialog_; }; // foo_dialog_win.cc: class FooDialogView : public views::View, public FooDialogController { public: ... explicit FooDialogView(FooDialogController* controller) { set_parent_owned(false); // Now necessary due to scoped_ptr in FooDialogController. } ... }; FooDialog* FooDialog::CreateFooDialog(FooDialogController* controller) { return new FooDialogView(controller); }
有時候後一種模式是必要的,但這些狀況很稀少,而且很是容易被前端的團隊所理解。移植的時候,若是UI元素有時候像dialog box同樣簡單的話,考慮把後一種模式轉爲前一種。