This repository contains the source code modifications and additions I made to the Chromium browser as part of the Perplexity Sonar API Hackathon. The goal was to natively integrate Perplexity's Sonar API to enhance browsing capabilities.
"Important Note on Repository Structure": Due to very large size of the full Chromium source code (approx. 155GB) and GitHub's repository size limitations, this repository does not contain the entire Chromium source tree. Instead, it includes only the specific files I created or modified for the hackathon features. I could have uploaded my whole patch, to google gerrit, but that would amke the source code public, which would have defeated the purpose and rules of the hackathon. The directory structure within this repository mirrors the structure found within a standard chromium/src checkout.
This detailed README provides a walkthrough of the implemented features, the key code changes, and how one can conceptually integrate these changes into a full Chromium checkout.
- Sonar-Powered Omnibox Answers: Users can type
sonarfollowed by a query in the Omnibox (address bar) to get direct answers from the Sonar API displayed as an autocomplete suggestion. - Context Menu: "Summarize with Sonar": Users can select text on any webpage, right-click, and choose "Summarize with Sonar." The selected text is sent to the Sonar API, and the summary is displayed in a new browser tab.
To build and run these features within Chromium, you would generally need:
- A Chromium Development Environment: I have built the chromium using wsl2 linux ubuntu 22.04 Follow the official Chromium instructions to check out and build the browser:
- Get the Code and Build: Checking out and building Chromium on Linux
- Perplexity Sonar API Key: You will need a valid API key from Perplexity AI.
- Check official docs: SONAR by Perplexity
All paths mentioned below are relative to the chromium/src/ (very important) directory of a standard Chromium checkout. The files in this repository should be placed into their corresponding locations within your own Chromium checkout. I am using chromium Version 138.0.7160.0 (Developer Build) (64-bit)
Key directories you'll find in this repository containing my changes:
components/omnibox/browser/chrome/browser/renderer_context_menu/chrome/app/chrome/browser/ui/actionstools/metrics/histograms/metadata/ui/
To provide instant answers directly in the Omnibox (address bar) suggestions when a user types a query prefixed with the sonar keyword.
components/omnibox/browser/sonar_autocomplete_provider.hcomponents/omnibox/browser/sonar_autocomplete_provider.cccomponents/omnibox/browser/autocomplete_controller.cccomponents/omnibox/browser/BUILD.gn
-
components/omnibox/browser/sonar_autocomplete_provider.h:- Declares the
SonarAutocompleteProviderclass, inheriting fromAutocompleteProvider. - Member variables:
client_andlistener_: Standard for providers.loader_: Astd::unique_ptr<network::SimpleURLLoader>for the API call.debounce_timer_: Abase::OneShotTimerto delay API calls.current_input_,current_query_: Stores input between debounces.weak_ptr_factory_: For safe asynchronous callbacks.
- Declares the
-
components/omnibox/browser/sonar_autocomplete_provider.cc:- Constants:
kSonarKeyword (u"sonar"): The trigger keyword.kSonarDebounceDelay (base::Milliseconds(300)): Delay before sending API request.kMinQueryLength = 3: Minimum query length after the keyword.
- Constructor
SonarAutocompleteProvider(...):- Initializes the base class with
Type::TYPE_SEARCH. - Stores
clientandlistener.
- Initializes the base class with
Start(const AutocompleteInput& input, bool minimal_changes)Method:- Logs input and checks
input.omit_asynchronous_matches(). - Uses
AutocompleteInput::ExtractKeywordFromInput()to check forkSonarKeywordand extract theremaining_input_text. - If not a valid sonar query, clears matches and sets
done_ = true. - If valid, sets
done_ = false, stops and restartsdebounce_timer_ifremaining_input_text.length() >= kMinQueryLength. - Stores
inputand processedremaining_input_text(whitespace trimmed, trailing '?' removed) incurrent_input_andcurrent_query_.
- Logs input and checks
ExecuteQuery()Method (called bydebounce_timer_):- Resets
loader_and invalidates weak pointers. - Defines
net::NetworkTrafficAnnotationTag. - Creates
network::ResourceRequestforhttps://api.perplexity.ai/chat/completions(POST). - Sets
Authorizationheader with myAPI keyandContent-Type. - Constructs the JSON request body:
{ "model": "sonar-pro", "messages": [ { "role": "system", "content": "Be precise and concise." }, { "role": "user", "content": "<user's query>" } ] } - Creates
network::SimpleURLLoader, attaches upload string, sets timeout, and retry options. - Calls
loader_->DownloadToString()bindingOnApiReplywithoriginal_input_text(full "sonar query") andquery_text(only just the part after "sonar").
- Resets
OnApiReply(const std::u16string& original_input_text, ...)Method:- Sets
done_ = true. - Performs error checking on the network response (
loader_->NetError(), HTTP status code). - Parses the JSON response:
base::JSONReader::Read(*response_body). - Extracts the answer text from
answer_text = *content. - Cleans the answer text:
base::CollapseWhitespace,AutocompleteMatch::SanitizeString, and custom removal of markdown like**and citations[...]. - Creates an
AutocompleteMatch:relevance = 1460(high).type = AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER.contents= cleaned Sonar answer.fill_into_edit=original_input_text(e.g., "sonar what is X").destination_url= Search URL forquery_text(e.g., for "what is X").description = u"Sonar Answer".keyword = kSonarKeyword.from_keyword = true.transition = ui::PAGE_TRANSITION_KEYWORD.
- Adds the match to
matches_. - Calls
listener_->OnProviderUpdate(true, this);.
- Sets
Stop(AutocompleteStopReason stop_reason)Method:- Invalidates weak pointers, resets
loader_, stopsdebounce_timer_, and calls baseStop().
- Invalidates weak pointers, resets
- Constants:
-
Integration with
components/omnibox/browser/autocomplete_controller.cc:- The
SonarAutocompleteProvideris instantiated and added to theproviders_list, typically within theAutocompleteController::InitializeAsyncProviders()method, ensuring it participates in omnibox suggestions:// In AutocompleteController::InitializeAsyncProviders(...) if (provider_types & AutocompleteProvider::TYPE_SEARCH) { // Or a similar condition // ... other providers ... providers_.push_back( new SonarAutocompleteProvider(provider_client_.get(), this)); }
- The
-
Build System (
components/omnibox/browser/BUILD.gn):sonar_autocomplete_provider.ccandsonar_autocomplete_provider.hare added to thesourceslist of the appropriatesource set(seestatic_library("browser")).
- Create Files: Place
sonar_autocomplete_provider.handsonar_autocomplete_provider.ccincomponents/omnibox/browser/. - Modify
BUILD.gn: Add the new source files and any missing dependencies tocomponents/omnibox/browser/BUILD.gn. - Modify
autocomplete_controller.cc: Include thesonar_autocomplete_provider.hheader and add the instantiation line as shown above inInitializeAsyncProviders(). - API Key: Replace your API key in
sonar_autocomplete_provider.ccwith a valid Perplexity API key. - Build Chromium.
- Test: Type
"sonar [your query]"in the omnibox.
Allow users to select large & lengthy texts on a webpage, right-click, and select "Summarize with Sonar." The API-generated summary, along with the original text, is displayed in a new browser tab using a data: URL.
chrome/browser/renderer_context_menu/render_view_context_menu.hchrome/browser/renderer_context_menu/render_view_context_menu.ccchrome/app/chrome_command_ids.hchrome/app/generated_resources.grdtools/metrics/histograms/metadata/ui/enums.xml
-
Define Command ID (
chrome/app/chrome_command_ids.h):- A new command ID,
IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR, is defined atLINE 413with#define IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR 50180
- A new command ID,
-
Define Menu Item String (
chrome/app/generated_resources.grd):- A string like
IDS_CONTENT_CONTEXT_SUMMARIZE_WITH_SONARwith the value "Summarize with Sonar" has been added.
- A string like
-
Add Members to
RenderViewContextMenuClass (render_view_context_menu.h):- Declaration of the API callback method:
void OnSonarApiResponseForSummarization( const std::string& original_text_utf8, std::unique_ptr<std::string> response_body);
- Member variables for the API call:
std::unique_ptr<network::SimpleURLLoader> sonnar_api_loader_; base::WeakPtrFactory<RenderViewContextMenu> summarize_weak_ptr_factory_{this};
- Declaration of the API callback method:
-
Modify
RenderViewContextMenu::InitMenu()(render_view_context_menu.cc):-
The item is added conditionally when text is selected, often within the
ITEM_GROUP_SEARCH_PROVIDERblock:// Add "Summarize with Sonar" item if text is selected if (!params_.selection_text.empty()) { menu_model_.AddSeparator(ui::NORMAL_SEPARATOR); menu_model_.AddItem(IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR, u"Summarize with Sonar"); }
-
-
Modify
RenderViewContextMenu::IsCommandIdEnabled()(render_view_context_menu.cc):- Ensures the item is only enabled if text is selected:
case IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR: return !params_.selection_text.empty();
- Ensures the item is only enabled if text is selected:
-
Modify
RenderViewContextMenu::ExecuteCommand()(render_view_context_menu.cc):- A
caseforIDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONARcalls the newSummarizeWithSonar()method:case IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR: SummarizeWithSonar(); break;
- A
-
Implement
RenderViewContextMenu::SummarizeWithSonar()(render_view_context_menu.cc):- Retrieves
params_.selection_text. - Resets any previous
sonnar_api_loader_and invalidatessummarize_weak_ptr_factory_. - Sets up
net::NetworkTrafficAnnotationTag. - Creates a
network::ResourceRequestforhttps://api.perplexity.ai/chat/completions(POST). - Sets
Authorizationheader with API key andContent-Type. - Constructs the JSON request body with a system prompt like "Please summarize the following text concisely." and the selected user text.
- Creates
network::SimpleURLLoader, attaches the upload string, sets timeout. - Calls
loader_->DownloadToString(), bindingRenderViewContextMenu::OnSonarApiResponseForSummarizationand passing theselected_text_utf8to it for later display.
- Retrieves
-
Implement
RenderViewContextMenu::OnSonarApiResponseForSummarization(...)(render_view_context_menu.cc):- This is the callback executed after the Sonar API call completes.
- It checks for network or HTTP errors.
- Parses the JSON response from Sonar to extract the summary from
response.choices[0].message.content. - If parsing fails or API call errors occur, it sets
summary_text_utf8to an error message. - Dynamically constructs an HTML string for a
data:URL. This HTML includes:- A title "Sonar Summary".
- Basic CSS styling for cards and text.
- A section for "Source Text" where
original_text_utf8is displayed. - A section for "Summary" where
summary_text_utf8(the actual Sonar summary or error) is displayed. - JavaScript to set the text content of these elements.
- URL-encodes the generated HTML string.
- Creates a
data:text/html;charset=utf8,...URL. - The code opens this
data_urlin aNEW_FOREGROUND_TABusingbrowser->OpenURL().
-
UMA Logging (
tools/metrics/histograms/metadata/ui/enums.xml):- An entry for
IDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR(with value156) is added to theRenderViewContextMenuItemenum to allow tracking its usage. - An entry for
Summarize with Sonar(with value31) is added to theContextMenuOptionDesktopenum.SEE : <enum name="ContextMenuOptionDesktop">
- An entry for
- Modify
chrome_command_ids.h: DefineIDC_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR. - Modify String Resources (
.grdfile): AddIDS_CONTENT_CONTEXT_SUMMARIZE_WITH_SONAR. - Modify
render_view_context_menu.h: Add theOnSonarApiResponseForSummarizationdeclaration and thesonnar_api_loader_,summarize_weak_ptr_factory_members. - Modify
render_view_context_menu.cc:- Include necessary headers (
base/json/json_reader.h, network headers, etc.). - Implement the changes in
InitMenu(),IsCommandIdEnabled(),ExecuteCommand(). - Implement the new
SummarizeWithSonar()andOnSonarApiResponseForSummarization()methods as detailed above.
- Include necessary headers (
- API Key: Insert a valid Perplexity API key in
SummarizeWithSonar(). - Modify
enums.xml: Add the UMA entries. - Build Chromium.
- Test: Select text on a webpage, right-click, and choose "Summarize with Sonar."
- After making the code changes as described, follow the standard Chromium build instructions for your platform.
- Crucially, replace placeholder API keys in both
sonar_autocomplete_provider.ccandrender_view_context_menu.ccwith your valid Perplexity Sonar API key before building.
- API Keys: API keys are currently hardcoded for this hackathon submission. In a production environment, these would be managed securely.
- Error Handling: Error handling for API calls is basic (logs errors, may show a generic error in the summary tab). More user-friendly UI for errors could be implemented.
- Context Menu Summary Display: The current context menu summary feature displays the result in a new
data:URL tab. This is a straightforward approach for the hackathon. A more integrated solution might involve creating a dedicated WebUI calledchrome://sonar-summary/which is easy. - Omnibox Match Type: The omnibox feature uses
AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWERfor display, which might have specific rendering characteristics. This could be changed to a more generic or custom answer type if needed.
- Integrate the context menu summary feature with a native Chromium side panel using the WebUI files (
chrome/browser/ui/webui/side_panel/sonar_summary/etc.) - Provide user-facing settings for these features (e.g., to enable/disable, manage API keys securely).
- More sophisticated UI for displaying loading states and errors.
Thank you to the Perplexity AI team for organizing this hackathon and providing access to the Sonar API.
