| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/script_executor.h" |
| |
| #include <set> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/hash/hash.h" |
| #include "base/logging.h" |
| #include "base/pickle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "extensions/browser/extension_api_frame_id_map.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/url_loader_factory_manager.h" |
| #include "extensions/common/extension_messages.h" |
| #include "ipc/ipc_message.h" |
| #include "ipc/ipc_message_macros.h" |
| |
| namespace base { |
| class ListValue; |
| } // namespace base |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kRendererDestroyed[] = "The tab was closed."; |
| const char kFrameRemoved[] = "The frame was removed."; |
| |
| // Generates an injection key based on the host ID and either the file URL, if |
| // available, or the code string. The format of the key is |
| // "<type><host_id><digest>", where <type> is one of "F" (file) and "C" (code), |
| // <host_id> is the host ID, and <digest> is an unspecified hash digest of the |
| // file URL or the code string, respectively. |
| const std::string GenerateInjectionKey(const HostID& host_id, |
| const GURL& script_url, |
| const std::string& code) { |
| const std::string& source = script_url.is_valid() ? script_url.spec() : code; |
| return base::StringPrintf("%c%s%zu", script_url.is_valid() ? 'F' : 'C', |
| host_id.id().c_str(), base::FastHash(source)); |
| } |
| |
| // A handler for a single injection request. On creation this will send the |
| // injection request to the renderer, and it will be destroyed after either the |
| // corresponding response comes from the renderer, or the renderer is destroyed. |
| class Handler : public content::WebContentsObserver { |
| public: |
| Handler(ScriptsExecutedNotification observer, |
| content::WebContents* web_contents, |
| const ExtensionMsg_ExecuteCode_Params& params, |
| ScriptExecutor::FrameScope scope, |
| int frame_id, |
| const ScriptExecutor::ScriptFinishedCallback& callback) |
| : content::WebContentsObserver(web_contents), |
| observer_(std::move(observer)), |
| host_id_(params.host_id), |
| request_id_(params.request_id), |
| include_sub_frames_(scope == ScriptExecutor::INCLUDE_SUB_FRAMES), |
| root_rfh_(ExtensionApiFrameIdMap::GetRenderFrameHostById(web_contents, |
| frame_id)), |
| root_is_main_frame_(root_rfh_ ? !root_rfh_->GetParent() : false), |
| callback_(callback) { |
| if (root_rfh_) { |
| if (include_sub_frames_) { |
| web_contents->ForEachFrame(base::BindRepeating( |
| &Handler::SendExecuteCode, base::Unretained(this), params)); |
| } else { |
| SendExecuteCode(params, root_rfh_); |
| } |
| } |
| |
| if (pending_render_frames_.empty()) |
| Finish(); |
| } |
| |
| private: |
| // This class manages its own lifetime. |
| ~Handler() override {} |
| |
| // content::WebContentsObserver: |
| void WebContentsDestroyed() override { Finish(); } |
| |
| bool OnMessageReceived(const IPC::Message& message, |
| content::RenderFrameHost* render_frame_host) override { |
| // Unpack by hand to check the request_id, since there may be multiple |
| // requests in flight but only one is for this. |
| if (message.type() != ExtensionHostMsg_ExecuteCodeFinished::ID) |
| return false; |
| |
| int message_request_id; |
| base::PickleIterator iter(message); |
| CHECK(iter.ReadInt(&message_request_id)); |
| |
| if (message_request_id != request_id_) |
| return false; |
| |
| IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(Handler, message, render_frame_host) |
| IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExecuteCodeFinished, |
| OnExecuteCodeFinished) |
| IPC_END_MESSAGE_MAP() |
| return true; |
| } |
| |
| void RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) override { |
| if (pending_render_frames_.erase(render_frame_host) == 1 && |
| pending_render_frames_.empty()) { |
| Finish(); |
| } |
| } |
| |
| // Sends an ExecuteCode message to the given frame host, and increments |
| // the number of pending messages. |
| void SendExecuteCode(const ExtensionMsg_ExecuteCode_Params& params, |
| content::RenderFrameHost* frame) { |
| if (!frame->IsRenderFrameLive()) |
| return; |
| DCHECK(!root_is_main_frame_ || ShouldIncludeFrame(frame)); |
| if (!root_is_main_frame_ && !ShouldIncludeFrame(frame)) |
| return; |
| pending_render_frames_.insert(frame); |
| URLLoaderFactoryManager::WillExecuteCode(frame, host_id_); |
| frame->Send(new ExtensionMsg_ExecuteCode(frame->GetRoutingID(), params)); |
| } |
| |
| // Returns whether a frame is the root frame or a descendant of it. |
| bool ShouldIncludeFrame(content::RenderFrameHost* frame) { |
| while (frame) { |
| if (frame == root_rfh_) |
| return true; |
| frame = frame->GetParent(); |
| } |
| return false; |
| } |
| |
| // Handles the ExecuteCodeFinished message. |
| void OnExecuteCodeFinished(content::RenderFrameHost* render_frame_host, |
| int request_id, |
| const std::string& error, |
| const GURL& on_url, |
| const base::ListValue& result_list) { |
| DCHECK_EQ(request_id_, request_id); |
| DCHECK(!pending_render_frames_.empty()); |
| bool erased = pending_render_frames_.erase(render_frame_host) == 1; |
| DCHECK(erased); |
| bool is_root_frame = root_rfh_ == render_frame_host; |
| |
| // Set the result, if there is one. |
| const base::Value* script_value = nullptr; |
| if (result_list.Get(0u, &script_value)) { |
| // If this is the main result, we put it at index 0. Otherwise, we just |
| // append it at the end. |
| if (is_root_frame && !results_.empty()) |
| CHECK(results_.Insert(0u, script_value->CreateDeepCopy())); |
| else |
| results_.Append(script_value->CreateDeepCopy()); |
| } |
| |
| if (is_root_frame) { // Only use the root frame's error and url. |
| root_frame_error_ = error; |
| root_frame_url_ = on_url; |
| } |
| |
| // Wait until the final request finishes before reporting back. |
| if (pending_render_frames_.empty()) |
| Finish(); |
| } |
| |
| void Finish() { |
| if (root_frame_url_.is_empty()) { |
| // We never finished the root frame injection. |
| root_frame_error_ = |
| root_is_main_frame_ ? kRendererDestroyed : kFrameRemoved; |
| results_.Clear(); |
| } |
| |
| if (!observer_.is_null() && root_frame_error_.empty() && |
| host_id_.type() == HostID::EXTENSIONS) { |
| observer_.Run(web_contents(), {{host_id_.id(), {}}}, root_frame_url_); |
| } |
| |
| if (!callback_.is_null()) |
| callback_.Run(root_frame_error_, root_frame_url_, results_); |
| delete this; |
| } |
| |
| ScriptsExecutedNotification observer_; |
| |
| // The id of the host (the extension or the webui) doing the injection. |
| HostID host_id_; |
| |
| // The request id of the injection. |
| int request_id_; |
| |
| // Whether to inject in |root_rfh_| and all of its descendant frames. |
| bool include_sub_frames_; |
| |
| // The frame (and optionally its descendant frames) where the injection will |
| // occur. |
| content::RenderFrameHost* root_rfh_; |
| |
| // Whether |root_rfh_| is the main frame of a tab. |
| bool root_is_main_frame_; |
| |
| // The hosts of the still-running injections. |
| std::set<content::RenderFrameHost*> pending_render_frames_; |
| |
| // The results of the injection. |
| base::ListValue results_; |
| |
| // The error from injecting into the root frame. |
| std::string root_frame_error_; |
| |
| // The url of the root frame. |
| GURL root_frame_url_; |
| |
| // The callback to run after all injections complete. |
| ScriptExecutor::ScriptFinishedCallback callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Handler); |
| }; |
| |
| } // namespace |
| |
| ScriptExecutor::ScriptExecutor(content::WebContents* web_contents) |
| : web_contents_(web_contents) { |
| CHECK(web_contents_); |
| } |
| |
| ScriptExecutor::~ScriptExecutor() {} |
| |
| void ScriptExecutor::ExecuteScript(const HostID& host_id, |
| ScriptExecutor::ScriptType script_type, |
| const std::string& code, |
| ScriptExecutor::FrameScope frame_scope, |
| int frame_id, |
| ScriptExecutor::MatchAboutBlank about_blank, |
| UserScript::RunLocation run_at, |
| ScriptExecutor::ProcessType process_type, |
| const GURL& webview_src, |
| const GURL& script_url, |
| bool user_gesture, |
| base::Optional<CSSOrigin> css_origin, |
| ScriptExecutor::ResultType result_type, |
| const ScriptFinishedCallback& callback) { |
| if (host_id.type() == HostID::EXTENSIONS) { |
| // Don't execute if the extension has been unloaded. |
| const Extension* extension = |
| ExtensionRegistry::Get(web_contents_->GetBrowserContext()) |
| ->enabled_extensions().GetByID(host_id.id()); |
| if (!extension) |
| return; |
| } else { |
| CHECK(process_type == WEB_VIEW_PROCESS); |
| } |
| |
| ExtensionMsg_ExecuteCode_Params params; |
| params.request_id = next_request_id_++; |
| params.host_id = host_id; |
| params.is_javascript = (script_type == JAVASCRIPT); |
| params.code = code; |
| params.match_about_blank = (about_blank == MATCH_ABOUT_BLANK); |
| params.run_at = run_at; |
| params.is_web_view = (process_type == WEB_VIEW_PROCESS); |
| params.webview_src = webview_src; |
| params.script_url = script_url; |
| params.wants_result = (result_type == JSON_SERIALIZED_RESULT); |
| params.user_gesture = user_gesture; |
| params.css_origin = css_origin; |
| |
| // Generate an injection key if this is a CSS injection from an extension |
| // (i.e. tabs.insertCSS). |
| if (host_id.type() == HostID::EXTENSIONS && script_type == CSS) |
| params.injection_key = GenerateInjectionKey(host_id, script_url, code); |
| |
| // Handler handles IPCs and deletes itself on completion. |
| new Handler(observer_, web_contents_, params, frame_scope, frame_id, |
| callback); |
| } |
| |
| } // namespace extensions |