Chromium ليس متصفحًا فحسب ، بل إطارًا جيدًا أيضًا



اعتاد معظم الناس على حقيقة أن Chromium هو متصفح وأساس للمتصفحات الأخرى. حتى وقت قريب ، كنت أعتقد ذلك أيضًا ، لكن بدراسة هذا الموضوع لمدة شهرين ، بدأت في اكتشاف عالم رائع آخر. Chromium هو نظام بيئي ضخم يوجد فيه كل شيء: نظام التبعية ، ونظام بناء متعدد المنصات ، ومكونات لجميع المناسبات تقريبًا. فلماذا لا تحاول إنشاء تطبيقاتك الخاصة باستخدام كل هذه القوة؟

تحت كات دليل صغير حول كيفية البدء في القيام بذلك.

إعداد البيئة


في المقالة سأستخدم Ubuntu 18.04 ، يمكن العثور على الإجراء الخاص بأنظمة التشغيل الأخرى في الوثائق:


الخطوات التالية تتطلب جيت وبيثون. إذا لم يتم تثبيتها ، فيجب تثبيتها باستخدام الأمر:

sudo apt install git python 

وضع depot_tools


depot_tools عبارة عن مجموعة أدوات لتطوير Chromium. لتثبيته ، يجب إجراء:

 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 

وأضف المسار إلى متغير بيئة PATH:

 export PATH="$PATH:/path/to/depot_tools" 

هام: إذا تم تنزيل depot_tools إلى مجلد منزلك ، فلا تستخدم ~ في متغير PATH ، وإلا فقد تحدث مشكلات. يجب عليك استخدام المتغير $HOME :

 export PATH="$PATH:${HOME}/depot_tools" 

استرجاع الكود


تحتاج أولاً إلى إنشاء مجلد للمصدر. على سبيل المثال ، في الدليل الرئيسي (يلزم حوالي 30 جيجابايت من المساحة الحرة):

 mkdir ~/chromium && cd ~/chromium 

بعد ذلك ، يمكنك تنزيل المصادر باستخدام أداة fetch المساعدة من depot_tools :

 fetch --nohooks --no-history chromium 

الآن يمكنك الذهاب لتناول الشاي / القهوة ، لأن الإجراء ليس سريعًا. بالنسبة للتجارب ، لا يلزم وجود محفوظات ، لذلك يتم استخدام علامة --no-history . ستكون القصة أطول.

تركيب التبعية


جميع المصادر موجودة في مجلد src ، انتقل إليه:

 cd src 

تحتاج الآن إلى وضع جميع التبعيات باستخدام البرنامج النصي:

 ./build/install-build-deps.sh 

وتشغيل السنانير:

 gclient runhooks 

هذا يكمل إعداد البيئة.

بناء النظام


يستخدم Ninja كنظام تجميع رئيسي لـ Chromium ، ويتم استخدام الأداة المساعدة GN لإنشاء ملفات .ninja .

لفهم كيفية استخدام هذه الأدوات ، أقترح إنشاء مثال أداة اختبار مساعدة. للقيام بذلك ، قم بإنشاء مجلد فرعي example في مجلد src :

 mkdir example 

ثم ، في مجلد src/example ، قم بإنشاء ملف BUILD.gn ، والذي يحتوي على:

 executable("example") { sources = [ "example.cc", ] } 

يتكون BUILD.gn من هدف ( example قابل للتنفيذ) وقائمة بالملفات اللازمة لإنشاء الهدف.

الخطوة التالية هي إنشاء ملف example.cc نفسه. للبدء ، أقترح تقديم تطبيق كلاسيكي "Hello world":

 #include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; } 

شفرة المصدر يمكن العثور عليها على جيثب .

لمعرفة GN عن المشروع الجديد ، في ملف BUILD.gn الموجود في src ، أضف السطر "//example" في قسم deps :

 ... group("gn_all") { testonly = true deps = [ ":gn_visibility", "//base:base_perftests", "//base:base_unittests", "//base/util:base_util_unittests", "//chrome/installer", "//chrome/updater", "//net:net_unittests", "//services:services_unittests", "//services/service_manager/public/cpp", "//skia:skia_unittests", "//sql:sql_unittests", "//third_party/flatbuffers:flatbuffers_unittests", "//tools/binary_size:binary_size_trybot_py", "//tools/ipc_fuzzer:ipc_fuzzer_all", "//tools/metrics:metrics_metadata", "//ui/base:ui_base_unittests", "//ui/gfx:gfx_unittests", "//url:url_unittests", # ↓↓↓↓↓↓↓↓ "//example", ] ... 

أنت الآن بحاجة إلى العودة إلى مجلد src وإنشاء المشروع باستخدام الأمر:

 gn gen out/Default 

يسمح لك GN أيضًا بإعداد مشروع لأحد IDEs المدعومين:

  • كسوف
  • مقابل
  • vs2013
  • vs2015
  • vs2017
  • vs2019
  • كسكودي
  • qtcreator
  • سلمان

يمكن الحصول على مزيد من المعلومات باستخدام الأمر:

 gn help gen 

على سبيل المثال ، للعمل مع مشروع example في QtCreator ، تحتاج إلى تشغيل الأمر:

 gn gen --ide=qtcreator --root-target=example out/Default 

بعد ذلك ، يمكنك فتح المشروع في QtCreator:

 qtcreator out/Default/qtcreator_project/all.creator 

الخطوة الأخيرة هي بناء المشروع باستخدام Ninja:

 autoninja -C out/Default example 

يمكن استكمال هذه المقدمة الموجزة لنظام التجميع.

يمكن تشغيل التطبيق باستخدام الأمر:

 ./out/Default/example 

وانظر مرحبا العالم. في الواقع ، يمكنك كتابة مقال منفصل عن نظام التجميع في Chromium. ربما ليست واحدة.

العمل مع سطر الأوامر


كمثال أول على استخدام قاعدة كود Chromium كإطار عمل ، أقترح التمرين باستخدام سطر الأوامر.

المهمة: عرض جميع الوسائط التي تم تمريرها إلى التطبيق بنمط Chromium.
للعمل مع سطر الأوامر ، تحتاج إلى تضمين ملف الرأس في example.cc:

 #include "base/command_line.h" 

وكذلك يجب ألا ننسى إضافة تبعية على المشروع base في BUILD.gn . يجب أن يبدو BUILD.gn :

 executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] } 

الآن سيتم توصيل كل ما تحتاجه على example .

للعمل مع سطر الأوامر ، يوفر Chromium base::CommandLine مفردة base::CommandLine . للحصول على رابط إليها ، تحتاج إلى استخدام base::CommandLine::ForCurrentProcess الأسلوب الثابتة base::CommandLine::ForCurrentProcess ، ولكن عليك أولاً تهيئة ذلك باستخدام الأسلوب base::CommandLine::Init :

 base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess(); 

- إرجاع كافة الوسائط التي تم تمريرها إلى التطبيق في سطر الأوامر والبدء بـ - كـ base::SwitchMap (بشكل أساسي map<string, string> ) باستخدام أسلوب GetSwitches . يتم إرجاع كافة الوسائط الأخرى base::StringVector (بشكل أساسي vectr<strig> ). هذه المعرفة كافية لتنفيذ رمز المهمة:

 for (const auto &sw : cmd_line->GetSwitches()) { std::cout << "Switch " << sw.first << ": " << sw.second << std::endl; } for (const auto &arg: cmd_line->GetArgs()) { std::cout << "Arg " << arg << std::endl; } 

النسخة الكاملة يمكن العثور عليها على جيثب .

لإنشاء التطبيق وتشغيله تحتاج إلى تشغيل:

 autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2 

سيتم عرض الشاشة:

 Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2 

الشبكات


كمثال ثانٍ وآخر لهذا اليوم ، أقترح العمل مع جزء الشبكة من Chromium.

المهمة: عرض محتويات عنوان URL الذي تم تمريره كوسيطة .

نظام شبكة Chromium الفرعي


النظام الفرعي للشبكة كبير ومعقد للغاية. نقطة إدخال طلبات HTTP و HTTPS و FTP وموارد البيانات الأخرى هي URLRequest ، والتي تحدد بالفعل أي عميل سيستخدم. الشكل المبسط يبدو كالتالي:



النسخة الكاملة يمكن العثور عليها في الوثائق .

لإنشاء URLRequest يجب عليك استخدام URLRequestContext . إنشاء سياق عملية معقدة إلى حد ما ، لذلك يوصى باستخدام URLRequestContextBuilder . ستعمل على تهيئة جميع المتغيرات الضرورية باستخدام القيم الافتراضية ، ولكن ، إذا رغبت في ذلك ، يمكن تغييرها إلى متغيراتها الخاصة ، على سبيل المثال:

 net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true /* http2 */, false /* quic */); context_builder.SetCookieStore(nullptr); 

خاصية تعدد


تم تصميم مكدس شبكة Chromium للعمل في بيئة متعددة الخيوط ، بحيث لا يمكنك تخطي هذا الموضوع. الكائنات الأساسية للعمل مع multithreading في Chromium هي:

  • المهمة - مهمة يجب تنفيذها ، في Chromium هي وظيفة type base::Callback ، والتي يمكن إنشاؤها باستخدام base::Bind .
  • قائمة انتظار المهام - قائمة انتظار المهمة للتنفيذ.
  • مؤشر الترابط الفعلي - برنامج التفاف عبر النظام الأساسي عبر مؤشر ترابط نظام التشغيل ( pthread على POSIX أو CreateThread() على Windows). نفذت في الفئة base::PlatformThread class ، لا تستخدم مباشرة.
  • base :: Thread - خيط حقيقي يعالج الرسائل من قائمة انتظار مهمة لا نهاية لها ؛ لا ينصح بإنشائها مباشرة.
  • تجمع مؤشر الترابط - تجمع مؤشر ترابط مع قائمة انتظار مهمة شائعة. تنفذ في base::ThreadPool class. كقاعدة عامة ، قم بإنشاء مثيل واحد. يتم إرسال المهام إليها باستخدام وظائف من base/task/post_task.h .
  • سلسلة متسلسلة أو افتراضية - سلسلة مؤشرات افتراضية تستخدم سلاسل حقيقية ويمكنها التبديل بينها.
  • عداء المهام - واجهة لإعداد المهام ، تنفذ في base::TaskRunner .
  • عداء المهام المتسلسل - واجهة لإعداد المهام ، مما يضمن تنفيذ المهام بنفس الترتيب الذي وصلت به. نفذت في الفئة base::SequencedTaskRunner .
  • Run-task runner - تشبه السابقة ، لكنها تضمن أن جميع المهام سيتم تنفيذها في مؤشر ترابط OS واحد. يتم تنفيذه في base::SingleThreadTaskRunner .

تطبيق


تتطلب بعض مكونات Chromium وجود base::AtExitManager - هذه فئة تتيح لك تسجيل العمليات التي يجب إجراؤها عند إنهاء التطبيق. استخدامه بسيط للغاية ، تحتاج إلى إنشاء كائن على المكدس:

 base::AtExitManager exit_manager; 

عندما يخرج exit_manager عن نطاقه ، سيتم تنفيذ جميع عمليات الاسترجاعات المسجلة.

أنت الآن بحاجة إلى العناية بتوفر جميع مكونات تعدد مؤشرات الترابط الضرورية للنظام الفرعي للشبكة. للقيام بذلك ، قم بإنشاء Thread pool TYPE_IO ، Message loop بنوع TYPE_IO لمعالجة رسائل الشبكة ، Run loop - حلقة البرنامج الرئيسية:

 base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop; 

بعد ذلك ، استخدم أداة إنشاء Context لإنشاء Context :

 auto ctx = net::URLRequestContextBuilder().Build(); 

لإرسال طلب ، يجب عليك إنشاء كائن URLRequest باستخدام الأسلوب CreateRequest للكائن ctx . يتم تمرير المعلمات التالية:

  • URL ، سلسلة مع نوع GURL ؛
  • الأولوية؛
  • مندوب أن يعالج الأحداث.

المفوض هو فئة تطبق واجهة net::URLRequest::Delegate . لهذه المهمة ، قد يبدو مثل هذا:

 class MyDelegate : public net::URLRequest::Delegate { public: explicit MyDelegate(base::Closure quit_closure) : quit_closure_(std::move(quit_closure)), buf_(base::MakeRefCounted<net::IOBuffer>(BUF_SZ)) {} void OnReceivedRedirect(net::URLRequest *request, const net::RedirectInfo &redirect_info, bool *defer_redirect) override { std::cerr << "redirect to " << redirect_info.new_url << std::endl; } void OnAuthRequired(net::URLRequest* request, const net::AuthChallengeInfo& auth_info) override { std::cerr << "auth req" << std::endl; } void OnCertificateRequested(net::URLRequest *request, net::SSLCertRequestInfo *cert_request_info) override { std::cerr << "cert req" << std::endl; } void OnSSLCertificateError(net::URLRequest* request, int net_error, const net::SSLInfo& ssl_info, bool fatal) override { std::cerr << "cert err" << std::endl; } void OnResponseStarted(net::URLRequest *request, int net_error) override { std::cerr << "resp started" << std::endl; while (true) { auto n = request->Read(buf_.get(), BUF_SZ); std::cerr << "resp read " << n << std::endl; if (n == net::ERR_IO_PENDING) return; if (n <= 0) { OnReadCompleted(request, n); return; } std::cout << std::string(buf_->data(), n) << std::endl; } } void OnReadCompleted(net::URLRequest *request, int bytes_read) override { std::cerr << "completed" << std::endl; quit_closure_.Run(); } private: base::Closure quit_closure_; scoped_refptr<net::IOBuffer> buf_; }; 

كل المنطق الرئيسي موجود في OnResponseStarted الأحداث OnResponseStarted : يتم طرح محتويات الاستجابة حتى يحدث خطأ أو لا يوجد شيء للقراءة. نظرًا لأنه بعد قراءة الاستجابة التي تحتاجها لإكمال التطبيق ، يجب أن يكون للمفوض حق الوصول إلى الوظيفة التي ستقاطع Run loop الرئيسية ، وفي هذه الحالة ، يتم استخدام معاودة الاتصال بنوع base::Closure .

كل شيء جاهز الآن لإرسال الطلب:

 MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start(); 

لطلب بدء المعالجة ، تحتاج إلى Run loop :

 run_loop.Run(); 

النسخة الكاملة يمكن العثور عليها على جيثب .

لإنشاء التطبيق وتشغيله تحتاج إلى تشغيل:

 autoninja -C out/Default example out/Default/example "https://example.com/" 

خاتمة


في الواقع ، في Chromium ، يمكنك العثور على العديد من المكعبات والطوب المفيدة التي يمكنك من خلالها إنشاء تطبيقات. إنها تتطور باستمرار ، والتي تعد من ناحية ميزة إضافية ، ومن ناحية أخرى ، فإن التغييرات المعتادة في واجهة برمجة التطبيقات لا تسمح لك بالاسترخاء. على سبيل المثال ، في أحدث إصدار ، تحول base::TaskScheduler إلى base::ThreadPool ، لحسن الحظ ، دون تغيير API.

ملاحظة: نحن نبحث عن مبرمج C ++ رائد في فريقنا! إذا كنت تشعر بالقوة في نفسك ، فسيتم وصف رغباتنا هنا: team.mail.ru/vacancy/4641/ . هناك أيضًا زر "استجابة".

Source: https://habr.com/ru/post/ar455956/


All Articles