summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrob <rob@robwu.nl>2016-02-07 09:28:57 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-07 17:30:20 +0000
commit52277c872b368db45bc6d2f2bd9a4b7a0eb2ce8d (patch)
tree98016ba6f92be725a32ab1d8b773781441548753
parent057f009b6b02d1e94409a6d88702f37a07598de0 (diff)
downloadchromium_src-52277c872b368db45bc6d2f2bd9a4b7a0eb2ce8d.zip
chromium_src-52277c872b368db45bc6d2f2bd9a4b7a0eb2ce8d.tar.gz
chromium_src-52277c872b368db45bc6d2f2bd9a4b7a0eb2ce8d.tar.bz2
Add frameId to chrome.tabs.executeScript/insertCSS
- Re-implemented https://codereview.chromium.org/952473002/, with all checks at the browser side instead of just the renderer. - Use the last committed URL for checking permissions instead of the visible URL. As a result, executeScript/insertCSS will now succeed in frames where the currently committed page is scriptable by extensions, but the target of the pending navigation is is not. - ExecuteScript: Do not send IPC to non-live frames (they're not going to reply anyway). - Include URL in the error if the extension has the tabs permission (follow-up to TODO from https://codereview.chromium.org/1414223005). BUG=63979,551626 TEST=./browser_tests --gtest_filter=ExecuteScriptApiTest.* CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_site_isolation Review URL: https://codereview.chromium.org/1628423002 Cr-Commit-Position: refs/heads/master@{#374057}
-rw-r--r--chrome/browser/chromeos/accessibility/accessibility_manager.cc30
-rw-r--r--chrome/browser/extensions/api/tabs/tabs_api.cc49
-rw-r--r--chrome/browser/extensions/api/tabs/tabs_constants.cc1
-rw-r--r--chrome/browser/extensions/api/tabs/tabs_constants.h1
-rw-r--r--chrome/browser/extensions/execute_script_apitest.cc6
-rw-r--r--chrome/test/data/extensions/api_test/executescript/basic/test.js11
-rw-r--r--chrome/test/data/extensions/api_test/executescript/frame_id/frame.html3
-rw-r--r--chrome/test/data/extensions/api_test/executescript/frame_id/frames.html11
-rw-r--r--chrome/test/data/extensions/api_test/executescript/frame_id/manifest.json9
-rw-r--r--chrome/test/data/extensions/api_test/executescript/frame_id/nested.html1
-rw-r--r--chrome/test/data/extensions/api_test/executescript/frame_id/test.js383
-rw-r--r--chrome/test/data/extensions/api_test/executescript/navigation_race/test.js7
-rw-r--r--extensions/browser/api/execute_code_function.cc21
-rw-r--r--extensions/browser/extension_api_frame_id_map.cc5
-rw-r--r--extensions/browser/extension_api_frame_id_map.h3
-rw-r--r--extensions/browser/script_executor.cc82
-rw-r--r--extensions/browser/script_executor.h9
-rw-r--r--extensions/common/api/extension_types.json12
-rw-r--r--extensions/renderer/programmatic_script_injector.cc33
-rw-r--r--extensions/renderer/programmatic_script_injector.h10
20 files changed, 608 insertions, 79 deletions
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index b0a769c3..6da2f94 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -67,6 +67,7 @@
#include "content/public/browser/web_ui.h"
#include "content/public/common/content_switches.h"
#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/file_reader.h"
@@ -127,20 +128,21 @@ void ExecuteScriptHelper(
return;
if (!extensions::TabHelper::FromWebContents(web_contents))
extensions::TabHelper::CreateForWebContents(web_contents);
- extensions::TabHelper::FromWebContents(web_contents)->script_executor()->
- ExecuteScript(HostID(HostID::EXTENSIONS, extension_id),
- extensions::ScriptExecutor::JAVASCRIPT,
- code,
- extensions::ScriptExecutor::ALL_FRAMES,
- extensions::ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
- extensions::UserScript::DOCUMENT_IDLE,
- extensions::ScriptExecutor::ISOLATED_WORLD,
- extensions::ScriptExecutor::DEFAULT_PROCESS,
- GURL(), // No webview src.
- GURL(), // No file url.
- false, // Not user gesture.
- extensions::ScriptExecutor::NO_RESULT,
- extensions::ScriptExecutor::ExecuteScriptCallback());
+ extensions::TabHelper::FromWebContents(web_contents)
+ ->script_executor()
+ ->ExecuteScript(HostID(HostID::EXTENSIONS, extension_id),
+ extensions::ScriptExecutor::JAVASCRIPT, code,
+ extensions::ScriptExecutor::INCLUDE_SUB_FRAMES,
+ extensions::ExtensionApiFrameIdMap::kTopFrameId,
+ extensions::ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
+ extensions::UserScript::DOCUMENT_IDLE,
+ extensions::ScriptExecutor::ISOLATED_WORLD,
+ extensions::ScriptExecutor::DEFAULT_PROCESS,
+ GURL(), // No webview src.
+ GURL(), // No file url.
+ false, // Not user gesture.
+ extensions::ScriptExecutor::NO_RESULT,
+ extensions::ScriptExecutor::ExecuteScriptCallback());
}
// Helper class that directly loads an extension's content scripts into
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index f7a1103..c9b67e1 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -71,6 +71,7 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_function_util.h"
#include "extensions/browser/extension_host.h"
@@ -1359,10 +1360,10 @@ bool TabsUpdateFunction::UpdateURL(const std::string &url_string,
net::UnescapeURLComponent(url.GetContent(),
net::UnescapeRule::URL_SPECIAL_CHARS |
net::UnescapeRule::SPACES),
- ScriptExecutor::TOP_FRAME, ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
- UserScript::DOCUMENT_IDLE, ScriptExecutor::MAIN_WORLD,
- ScriptExecutor::DEFAULT_PROCESS, GURL(), GURL(), user_gesture_,
- ScriptExecutor::NO_RESULT,
+ ScriptExecutor::SINGLE_FRAME, ExtensionApiFrameIdMap::kTopFrameId,
+ ScriptExecutor::DONT_MATCH_ABOUT_BLANK, UserScript::DOCUMENT_IDLE,
+ ScriptExecutor::MAIN_WORLD, ScriptExecutor::DEFAULT_PROCESS, GURL(),
+ GURL(), user_gesture_, ScriptExecutor::NO_RESULT,
base::Bind(&TabsUpdateFunction::OnExecuteCodeFinished, this));
*is_async = true;
@@ -1898,13 +1899,45 @@ bool ExecuteCodeInTabFunction::CanExecuteScriptOnPage() {
CHECK(contents);
+ int frame_id = details_->frame_id ? *details_->frame_id
+ : ExtensionApiFrameIdMap::kTopFrameId;
+ content::RenderFrameHost* rfh =
+ ExtensionApiFrameIdMap::GetRenderFrameHostById(contents, frame_id);
+ if (!rfh) {
+ error_ = ErrorUtils::FormatErrorMessage(keys::kFrameNotFoundError,
+ base::IntToString(frame_id),
+ base::IntToString(execute_tab_id_));
+ return false;
+ }
+
+ // Content scripts declared in manifest.json can access frames at about:-URLs
+ // if the extension has permission to access the frame's origin, so also allow
+ // programmatic content scripts at about:-URLs for allowed origins.
+ GURL effective_document_url(rfh->GetLastCommittedURL());
+ bool is_about_url = effective_document_url.SchemeIs(url::kAboutScheme);
+ if (is_about_url && details_->match_about_blank &&
+ *details_->match_about_blank) {
+ effective_document_url = GURL(rfh->GetLastCommittedOrigin().Serialize());
+ }
+
+ if (!effective_document_url.is_valid()) {
+ // Unknown URL, e.g. because no load was committed yet. Allow for now, the
+ // renderer will check again and fail the injection if needed.
+ return true;
+ }
+
// NOTE: This can give the wrong answer due to race conditions, but it is OK,
// we check again in the renderer.
if (!extension()->permissions_data()->CanAccessPage(
- extension(),
- contents->GetURL(),
- execute_tab_id_,
- &error_)) {
+ extension(), effective_document_url, execute_tab_id_, &error_)) {
+ if (is_about_url &&
+ extension()->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab)) {
+ error_ = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessAboutUrl,
+ rfh->GetLastCommittedURL().spec(),
+ rfh->GetLastCommittedOrigin().Serialize());
+ }
return false;
}
diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.cc b/chrome/browser/extensions/api/tabs/tabs_constants.cc
index 5a2a1c9..2903ee9 100644
--- a/chrome/browser/extensions/api/tabs/tabs_constants.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_constants.cc
@@ -75,6 +75,7 @@ const char kCanOnlyMoveTabsWithinNormalWindowsError[] = "Tabs can only be "
"moved to and from normal windows.";
const char kCanOnlyMoveTabsWithinSameProfileError[] = "Tabs can only be moved "
"between windows in the same profile.";
+const char kFrameNotFoundError[] = "No frame with id * in tab *.";
const char kNoCrashBrowserError[] =
"I'm sorry. I'm afraid I can't do that.";
const char kNoCurrentWindowError[] = "No current window";
diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.h b/chrome/browser/extensions/api/tabs/tabs_constants.h
index df0064d..628f5ae 100644
--- a/chrome/browser/extensions/api/tabs/tabs_constants.h
+++ b/chrome/browser/extensions/api/tabs/tabs_constants.h
@@ -77,6 +77,7 @@ extern const char kWindowTypeValueDevTools[];
// Error messages.
extern const char kCannotZoomDisabledTabError[];
+extern const char kFrameNotFoundError[];
extern const char kNoCrashBrowserError[];
extern const char kNoCurrentWindowError[];
extern const char kNoLastFocusedWindowError[];
diff --git a/chrome/browser/extensions/execute_script_apitest.cc b/chrome/browser/extensions/execute_script_apitest.cc
index 82f3eab..7f81895 100644
--- a/chrome/browser/extensions/execute_script_apitest.cc
+++ b/chrome/browser/extensions/execute_script_apitest.cc
@@ -37,6 +37,12 @@ IN_PROC_BROWSER_TEST_F(ExecuteScriptApiTest, ExecuteScriptInFrame) {
ASSERT_TRUE(RunExtensionTest("executescript/in_frame")) << message_;
}
+IN_PROC_BROWSER_TEST_F(ExecuteScriptApiTest, ExecuteScriptByFrameId) {
+ SetupDelayedHostResolver();
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(RunExtensionTest("executescript/frame_id")) << message_;
+}
+
// Fails often on Windows.
// http://crbug.com/174715
#if defined(OS_WIN)
diff --git a/chrome/test/data/extensions/api_test/executescript/basic/test.js b/chrome/test/data/extensions/api_test/executescript/basic/test.js
index 5937596..7185964 100644
--- a/chrome/test/data/extensions/api_test/executescript/basic/test.js
+++ b/chrome/test/data/extensions/api_test/executescript/basic/test.js
@@ -88,7 +88,13 @@ chrome.test.getConfig(function(config) {
},
function executeJavaScriptCodeShouldFail() {
- chrome.tabs.update(tabId, { url: testFailureUrl }, function() {
+ var doneListening =
+ chrome.test.listenForever(chrome.tabs.onUpdated, onUpdated);
+ chrome.tabs.update(tabId, {url: testFailureUrl});
+
+ function onUpdated(updatedTabId, changeInfo) {
+ if (updatedTabId !== tabId || changeInfo.url === testFailureUrl)
+ return;
var script_file = {};
script_file.code = "document.title = 'executeScript';";
// The error message should contain the URL of the site for which it
@@ -97,7 +103,8 @@ chrome.test.getConfig(function(config) {
'Cannot access contents of url "' + testFailureUrl +
'". Extension manifest must request permission to access this ' +
'host.'));
- });
+ doneListening();
+ }
},
function executeJavaScriptWithNoneValueShouldFail() {
diff --git a/chrome/test/data/extensions/api_test/executescript/frame_id/frame.html b/chrome/test/data/extensions/api_test/executescript/frame_id/frame.html
new file mode 100644
index 0000000..75a3b13
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/frame_id/frame.html
@@ -0,0 +1,3 @@
+Same-origin frame.
+
+<iframe src="nested.html"></iframe>
diff --git a/chrome/test/data/extensions/api_test/executescript/frame_id/frames.html b/chrome/test/data/extensions/api_test/executescript/frame_id/frames.html
new file mode 100644
index 0000000..886abb4
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/frame_id/frames.html
@@ -0,0 +1,11 @@
+<iframe srcdoc="same-origin srcdoc frame"></iframe>
+
+<!-- Unique origin about:blank -->
+<iframe sandbox="" src="about:blank"></iframe>
+
+<iframe src="frame.html"></iframe>
+
+<script>
+document.write('<iframe src="http://c.com:' + location.port +
+ '/empty.html"></iframe>');
+</script>
diff --git a/chrome/test/data/extensions/api_test/executescript/frame_id/manifest.json b/chrome/test/data/extensions/api_test/executescript/frame_id/manifest.json
new file mode 100644
index 0000000..7b46d7b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/frame_id/manifest.json
@@ -0,0 +1,9 @@
+{
+ "version": "1",
+ "manifest_version": 2,
+ "name": "executeScript / insertCSS test by frameId",
+ "background": {
+ "scripts": ["test.js"]
+ },
+ "permissions": ["webNavigation", "tabs", "http://a.com/"]
+}
diff --git a/chrome/test/data/extensions/api_test/executescript/frame_id/nested.html b/chrome/test/data/extensions/api_test/executescript/frame_id/nested.html
new file mode 100644
index 0000000..b73bc1d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/frame_id/nested.html
@@ -0,0 +1 @@
+Deeply nested frame.
diff --git a/chrome/test/data/extensions/api_test/executescript/frame_id/test.js b/chrome/test/data/extensions/api_test/executescript/frame_id/test.js
new file mode 100644
index 0000000..cfd38c7
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/executescript/frame_id/test.js
@@ -0,0 +1,383 @@
+// Copyright 2016 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.
+
+var pass = chrome.test.callbackPass;
+var fail = chrome.test.callbackFail;
+var assertEq = chrome.test.assertEq;
+var assertTrue = chrome.test.assertTrue;
+var relativePath = '/extensions/api_test/executescript/frame_id/frames.html';
+var testOrigin = 'http://a.com:PORT';
+var testUrl = 'http://a.com:PORT' + relativePath;
+
+var tabId;
+
+// Frame ID of every frame in this test, and the patterns of the frame URLs.
+// Frame IDs are lazily initialized (and constant thereafter).
+// All patterns are mutually exclusive.
+
+// Main frame.
+var ID_FRAME_TOP = 0;
+var R_FRAME_TOP = /frames\.html/;
+// Frame with (same-origin) about:srcdoc.
+var ID_FRAME_SRCDOC;
+var R_FRAME_SRCDOC = /about:srcdoc/;
+// Frame with (unique-origin) sandboxed about:blank.
+var ID_FRAME_UNREACHABLE;
+var R_FRAME_UNREACHABLE = /about:blank/;
+// Frame with same-origin page.
+var ID_FRAME_SECOND;
+var R_FRAME_SECOND = /frame\.html/;
+// Same-origin child frame of |frame_second|.
+var ID_FRAME_THIRD;
+var R_FRAME_THIRD = /nested\.html/;
+// Frame for which the extension does not have the right permissions.
+var ID_FRAME_NOPERMISSION;
+var R_FRAME_NOPERMISSION = /empty\.html/;
+
+function matchesAny(urls, regex) {
+ return urls.some(function(url) { return regex.test(url); });
+}
+
+var gCssCounter = 0;
+
+// Calls chrome.tabs.insertCSS and invokes the callback with a list of affected
+// URLs. This function assumes that the tab identified by |tabId| exists, and
+// that |injectDetails| is a valid argument for insertCSS.
+function insertCSS(tabId, injectDetails, callback) {
+ var marker = (++gCssCounter) + 'px';
+ injectDetails.code = 'body { min-width: ' + marker + ';}';
+ chrome.tabs.insertCSS(tabId, injectDetails, function() {
+ chrome.test.assertNoLastError();
+ chrome.tabs.executeScript(
+ tabId, {
+ code: '[getComputedStyle(document.body).minWidth, document.URL];',
+ allFrames: true,
+ matchAboutBlank: true
+ },
+ function(results) {
+ chrome.test.assertNoLastError();
+ results = getAffectedUrls(results);
+ callback(results);
+ });
+ });
+
+ // Selects the results from the frames whose CSS was changed by the insertCSS
+ // call, and returns the URLs of these frames.
+ function getAffectedUrls(results) {
+ return results.filter(function(result) {
+ return result && result[0] === marker;
+ }).map(function(result) {
+ return result[1]; // "document.URL"
+ });
+ }
+}
+
+chrome.test.getConfig(function(config) {
+ testOrigin = testOrigin.replace(/PORT/, config.testServer.port);
+ testUrl = testUrl.replace(/PORT/, config.testServer.port);
+ chrome.tabs.onUpdated.addListener(function(_, changeInfo, tab) {
+ if (changeInfo.status != 'complete' || tab.id !== tabId) {
+ return;
+ }
+
+ chrome.webNavigation.getAllFrames({tabId: tabId}, function(frames) {
+ function getFrameId(urlRegex) {
+ var filtered =
+ frames.filter(function(frame) { return urlRegex.test(frame.url); });
+ // Sanity check.
+ chrome.test.assertEq(1, filtered.length);
+ chrome.test.assertTrue(filtered[0].frameId > 0);
+ return filtered[0].frameId;
+ }
+
+ ID_FRAME_SRCDOC = getFrameId(R_FRAME_SRCDOC);
+ ID_FRAME_UNREACHABLE = getFrameId(R_FRAME_UNREACHABLE);
+ ID_FRAME_SECOND = getFrameId(R_FRAME_SECOND);
+ ID_FRAME_THIRD = getFrameId(R_FRAME_THIRD);
+ ID_FRAME_NOPERMISSION = getFrameId(R_FRAME_NOPERMISSION);
+
+ runTests(config);
+ });
+ });
+
+ chrome.tabs.create({url: testUrl}, function(tab) { tabId = tab.id; });
+});
+
+function runTests(config) {
+ // All of the following tests set the frameId parameter in the injection
+ // details.
+ chrome.test.runTests([
+ function executeScriptInTopFrame() {
+ chrome.tabs.executeScript(
+ tabId, {frameId: 0, code: 'document.URL'}, pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_TOP));
+ }));
+ },
+
+ function executeScriptInTopFrameIncludingAllFrames() {
+ chrome.tabs.executeScript(
+ tabId, {
+ frameId: 0,
+ matchAboutBlank: true,
+ allFrames: true,
+ code: 'document.URL'
+ },
+ pass(function(results) {
+ assertEq(4, results.length);
+ assertTrue(matchesAny(results, R_FRAME_TOP));
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function executeScriptInSrcdocFrame() {
+ chrome.tabs.executeScript(
+ tabId, {
+ frameId: ID_FRAME_SRCDOC,
+ matchAboutBlank: true,
+ code: 'document.URL'
+ },
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ }));
+ },
+
+ function executeScriptInSrcdocFrameWithoutMatchAboutBlank() {
+ // TODO(robwu): Why is the origin serialized as "about:blank" instead of
+ // "about:srcdoc"?
+ chrome.tabs.executeScript(
+ tabId, {frameId: ID_FRAME_SRCDOC, code: 'document.URL'},
+ fail(
+ 'Cannot access "about:blank" at origin "' + testOrigin + '". ' +
+ 'Extension must have permission to access the frame\'s origin, ' +
+ 'and matchAboutBlank must be true.'));
+ },
+
+ function executeScriptInSrcdocFrameIncludingAllFrames() {
+ chrome.tabs.executeScript(
+ tabId, {
+ frameId: ID_FRAME_SRCDOC,
+ matchAboutBlank: true,
+ allFrames: true,
+ code: 'document.URL'
+ },
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ }));
+ },
+
+ function executeScriptInSandboxedFrame() {
+ chrome.tabs.executeScript(
+ tabId, {
+ frameId: ID_FRAME_UNREACHABLE,
+ matchAboutBlank: true,
+ code: 'document.URL'
+ },
+ fail(
+ 'Cannot access "about:blank" at origin "null". Extension must ' +
+ 'have permission to access the frame\'s origin, and ' +
+ 'matchAboutBlank must be true.'));
+ },
+
+ function executeScriptInSubFrame() {
+ chrome.tabs.executeScript(
+ tabId, {frameId: ID_FRAME_SECOND, code: 'document.URL'},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ }));
+ },
+
+ function executeScriptInSubFrameIncludingAllFrames() {
+ chrome.tabs.executeScript(
+ tabId,
+ {frameId: ID_FRAME_SECOND, allFrames: true, code: 'document.URL'},
+ pass(function(results) {
+ assertEq(2, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function executeScriptInNestedFrame() {
+ chrome.tabs.executeScript(
+ tabId, {frameId: ID_FRAME_THIRD, code: 'document.URL'},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function executeScriptInNestedFrameIncludingAllFrames() {
+ chrome.tabs.executeScript(
+ tabId,
+ {frameId: ID_FRAME_THIRD, allFrames: true, code: 'document.URL'},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function executeScriptInFrameWithoutPermission() {
+ chrome.tabs.executeScript(
+ tabId, {frameId: ID_FRAME_NOPERMISSION, code: 'document.URL'},
+ fail(
+ 'Cannot access contents of url "http://c.com:' +
+ config.testServer.port + '/empty.html". Extension manifest ' +
+ 'must request permission to access this host.'));
+ },
+
+ function executeScriptWithNonExistentFrameId() {
+ chrome.tabs.executeScript(
+ tabId, {frameId: 999999999, code: 'document.URL'},
+ fail('No frame with id 999999999 in tab ' + tabId + '.'));
+ },
+
+ function executeScriptWithNegativeFrameId() {
+ try {
+ chrome.tabs.executeScript(
+ tabId, {frameId: -1, code: 'document.URL'}, function() {
+ chrome.test.fail(
+ 'executeScript should never have been executed!');
+ });
+ } catch (e) {
+ assertEq(
+ 'Invalid value for argument 2. Property \'frameId\': ' +
+ 'Value must not be less than 0.',
+ e.message);
+ chrome.test.succeed();
+ }
+ },
+
+ function insertCSSInTopFrame() {
+ insertCSS(tabId, {frameId: 0}, pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_TOP));
+ }));
+ },
+
+ function insertCSSInTopFrameIncludingAllFrames() {
+ insertCSS(
+ tabId, {frameId: 0, matchAboutBlank: true, allFrames: true},
+ pass(function(results) {
+ assertEq(4, results.length);
+ assertTrue(matchesAny(results, R_FRAME_TOP));
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function insertCSSInSrcdocFrame() {
+ insertCSS(
+ tabId, {frameId: ID_FRAME_SRCDOC, matchAboutBlank: true},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ }));
+ },
+
+ function insertCSSInSrcdocFrameWithoutMatchAboutBlank() {
+ // TODO(robwu): Why is the origin serialized as "about:blank" instead of
+ // "about:srcdoc"?
+ chrome.tabs.insertCSS(
+ tabId, {frameId: ID_FRAME_SRCDOC, code: 'body{color:red;}'},
+ fail(
+ 'Cannot access "about:blank" at origin "' + testOrigin + '". ' +
+ 'Extension must have permission to access the frame\'s origin, ' +
+ 'and matchAboutBlank must be true.'));
+ },
+
+ function insertCSSInSrcdocFrameIncludingAllFrames() {
+ insertCSS(
+ tabId,
+ {frameId: ID_FRAME_SRCDOC, matchAboutBlank: true, allFrames: true},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SRCDOC));
+ }));
+ },
+
+ function insertCSSInSandboxedFrame() {
+ chrome.tabs.insertCSS(
+ tabId, {
+ frameId: ID_FRAME_UNREACHABLE,
+ matchAboutBlank: true,
+ code: 'body{color:red}'
+ },
+ fail(
+ 'Cannot access "about:blank" at origin "null". Extension must ' +
+ 'have permission to access the frame\'s origin, and ' +
+ 'matchAboutBlank must be true.'));
+ },
+
+ function insertCSSInSubFrame() {
+ insertCSS(tabId, {frameId: ID_FRAME_SECOND}, pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ }));
+ },
+
+ function insertCSSInSubFrameIncludingAllFrames() {
+ insertCSS(
+ tabId, {frameId: ID_FRAME_SECOND, allFrames: true},
+ pass(function(results) {
+ assertEq(2, results.length);
+ assertTrue(matchesAny(results, R_FRAME_SECOND));
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function insertCSSInNestedFrame() {
+ insertCSS(tabId, {frameId: ID_FRAME_THIRD}, pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function insertCSSInNestedFrameIncludingAllFrames() {
+ insertCSS(
+ tabId, {frameId: ID_FRAME_THIRD, allFrames: true},
+ pass(function(results) {
+ assertEq(1, results.length);
+ assertTrue(matchesAny(results, R_FRAME_THIRD));
+ }));
+ },
+
+ function insertCSSInFrameWithoutPermission() {
+ chrome.tabs.insertCSS(
+ tabId, {frameId: ID_FRAME_NOPERMISSION, code: 'body{color:red}'},
+ fail(
+ 'Cannot access contents of url "http://c.com:' +
+ config.testServer.port + '/empty.html". Extension manifest ' +
+ 'must request permission to access this host.'));
+ },
+
+ function insertCSSWithNonExistentFrameId() {
+ chrome.tabs.insertCSS(
+ tabId, {frameId: 999999999, code: 'body{color:red}'},
+ fail('No frame with id 999999999 in tab ' + tabId + '.'));
+ },
+
+ function insertCSSWithNegativeFrameId() {
+ try {
+ chrome.tabs.insertCSS(
+ tabId, {frameId: -1, code: 'body{color:red}'}, function() {
+ chrome.test.fail('insertCSS should never have been executed!');
+ });
+ } catch (e) {
+ assertEq(
+ 'Invalid value for argument 2. Property \'frameId\': ' +
+ 'Value must not be less than 0.',
+ e.message);
+ chrome.test.succeed();
+ }
+ },
+
+ ]);
+}
diff --git a/chrome/test/data/extensions/api_test/executescript/navigation_race/test.js b/chrome/test/data/extensions/api_test/executescript/navigation_race/test.js
index 02bd14b..fb4f7e0 100644
--- a/chrome/test/data/extensions/api_test/executescript/navigation_race/test.js
+++ b/chrome/test/data/extensions/api_test/executescript/navigation_race/test.js
@@ -17,9 +17,10 @@ chrome.test.getConfig(function(config) {
// the script, so it's still showing a.com, where we don't have
// permission to run it.
if (chrome.runtime.lastError) {
- chrome.test.assertTrue(
- chrome.runtime.lastError.message.indexOf(
- 'Cannot access contents of the page.') == 0);
+ chrome.test.assertLastError(
+ 'Cannot access contents of url "' + urlA +
+ '". Extension manifest must request permission to access this ' +
+ 'host.');
chrome.test.notifyPass();
} else {
// If there were no errors, then the script did run, but it should
diff --git a/extensions/browser/api/execute_code_function.cc b/extensions/browser/api/execute_code_function.cc
index 0f98df3..53fcb44 100644
--- a/extensions/browser/api/execute_code_function.cc
+++ b/extensions/browser/api/execute_code_function.cc
@@ -8,6 +8,7 @@
#include "extensions/browser/api/execute_code_function.h"
#include "extensions/browser/component_extension_resource_manager.h"
+#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/file_reader.h"
#include "extensions/common/error_utils.h"
@@ -139,8 +140,11 @@ bool ExecuteCodeFunction::Execute(const std::string& code_string) {
ScriptExecutor::FrameScope frame_scope =
details_->all_frames.get() && *details_->all_frames
- ? ScriptExecutor::ALL_FRAMES
- : ScriptExecutor::TOP_FRAME;
+ ? ScriptExecutor::INCLUDE_SUB_FRAMES
+ : ScriptExecutor::SINGLE_FRAME;
+
+ int frame_id = details_->frame_id.get() ? *details_->frame_id
+ : ExtensionApiFrameIdMap::kTopFrameId;
ScriptExecutor::MatchAboutBlank match_about_blank =
details_->match_about_blank.get() && *details_->match_about_blank
@@ -163,18 +167,11 @@ bool ExecuteCodeFunction::Execute(const std::string& code_string) {
CHECK_NE(UserScript::UNDEFINED, run_at);
executor->ExecuteScript(
- host_id_,
- script_type,
- code_string,
- frame_scope,
- match_about_blank,
- run_at,
- ScriptExecutor::ISOLATED_WORLD,
+ host_id_, script_type, code_string, frame_scope, frame_id,
+ match_about_blank, run_at, ScriptExecutor::ISOLATED_WORLD,
IsWebView() ? ScriptExecutor::WEB_VIEW_PROCESS
: ScriptExecutor::DEFAULT_PROCESS,
- GetWebViewSrc(),
- file_url_,
- user_gesture_,
+ GetWebViewSrc(), file_url_, user_gesture_,
has_callback() ? ScriptExecutor::JSON_SERIALIZED_RESULT
: ScriptExecutor::NO_RESULT,
base::Bind(&ExecuteCodeFunction::OnExecuteCodeFinished, this));
diff --git a/extensions/browser/extension_api_frame_id_map.cc b/extensions/browser/extension_api_frame_id_map.cc
index 72949be..e844fac 100644
--- a/extensions/browser/extension_api_frame_id_map.cc
+++ b/extensions/browser/extension_api_frame_id_map.cc
@@ -24,6 +24,7 @@ base::LazyInstance<ExtensionApiFrameIdMap>::Leaky g_map_instance =
} // namespace
const int ExtensionApiFrameIdMap::kInvalidFrameId = -1;
+const int ExtensionApiFrameIdMap::kTopFrameId = 0;
ExtensionApiFrameIdMap::CachedFrameIdPair::CachedFrameIdPair()
: frame_id(kInvalidFrameId), parent_frame_id(kInvalidFrameId) {}
@@ -75,7 +76,7 @@ int ExtensionApiFrameIdMap::GetFrameId(content::RenderFrameHost* rfh) {
return kInvalidFrameId;
if (rfh->GetParent())
return rfh->GetFrameTreeNodeId();
- return 0; // Main frame.
+ return kTopFrameId;
}
int ExtensionApiFrameIdMap::GetParentFrameId(content::RenderFrameHost* rfh) {
@@ -96,7 +97,7 @@ content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostById(
if (frame_id == kInvalidFrameId)
return nullptr;
- if (frame_id == 0)
+ if (frame_id == kTopFrameId)
return web_contents->GetMainFrame();
DCHECK_GE(frame_id, 1);
diff --git a/extensions/browser/extension_api_frame_id_map.h b/extensions/browser/extension_api_frame_id_map.h
index c3af8b3..8bb1422 100644
--- a/extensions/browser/extension_api_frame_id_map.h
+++ b/extensions/browser/extension_api_frame_id_map.h
@@ -48,6 +48,9 @@ class ExtensionApiFrameIdMap {
// An invalid extension API frame ID.
static const int kInvalidFrameId;
+ // Extension API frame ID of the top-level frame.
+ static const int kTopFrameId;
+
static ExtensionApiFrameIdMap* Get();
// Get the extension API frame ID for |rfh|.
diff --git a/extensions/browser/script_executor.cc b/extensions/browser/script_executor.cc
index 687be48..230bca3 100644
--- a/extensions/browser/script_executor.cc
+++ b/extensions/browser/script_executor.cc
@@ -13,6 +13,7 @@
#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/script_execution_observer.h"
#include "extensions/common/extension_messages.h"
@@ -28,6 +29,7 @@ namespace extensions {
namespace {
const char* kRendererDestroyed = "The tab was closed.";
+const char* kFrameRemoved = "The frame was removed.";
// 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
@@ -38,18 +40,28 @@ class Handler : public content::WebContentsObserver {
content::WebContents* web_contents,
const ExtensionMsg_ExecuteCode_Params& params,
ScriptExecutor::FrameScope scope,
+ int frame_id,
const ScriptExecutor::ExecuteScriptCallback& callback)
: content::WebContentsObserver(web_contents),
script_observers_(AsWeakPtr(script_observers)),
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 (scope == ScriptExecutor::ALL_FRAMES) {
- web_contents->ForEachFrame(base::Bind(&Handler::SendExecuteCode,
- base::Unretained(this), params));
- } else {
- SendExecuteCode(params, web_contents->GetMainFrame());
+ if (root_rfh_) {
+ if (include_sub_frames_) {
+ web_contents->ForEachFrame(base::Bind(&Handler::SendExecuteCode,
+ base::Unretained(this), params));
+ } else {
+ SendExecuteCode(params, root_rfh_);
+ }
}
+
+ if (pending_render_frames_.empty())
+ Finish();
}
private:
@@ -92,10 +104,25 @@ class Handler : public content::WebContentsObserver {
// 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);
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,
@@ -106,22 +133,22 @@ class Handler : public content::WebContentsObserver {
DCHECK(!pending_render_frames_.empty());
bool erased = pending_render_frames_.erase(render_frame_host) == 1;
DCHECK(erased);
- bool is_main_frame = web_contents()->GetMainFrame() == render_frame_host;
+ 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_main_frame && !results_.empty())
+ if (is_root_frame && !results_.empty())
CHECK(results_.Insert(0u, script_value->DeepCopy()));
else
results_.Append(script_value->DeepCopy());
}
- if (is_main_frame) { // Only use the main frame's error and url.
- main_frame_error_ = error;
- main_frame_url_ = on_url;
+ 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.
@@ -130,23 +157,24 @@ class Handler : public content::WebContentsObserver {
}
void Finish() {
- if (main_frame_url_.is_empty()) {
- // We never finished the main frame injection.
- main_frame_error_ = kRendererDestroyed;
+ 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 (script_observers_.get() && main_frame_error_.empty() &&
+ if (script_observers_.get() && root_frame_error_.empty() &&
host_id_.type() == HostID::EXTENSIONS) {
ScriptExecutionObserver::ExecutingScriptsMap id_map;
id_map[host_id_.id()] = std::set<std::string>();
FOR_EACH_OBSERVER(
ScriptExecutionObserver, *script_observers_,
- OnScriptsExecuted(web_contents(), id_map, main_frame_url_));
+ OnScriptsExecuted(web_contents(), id_map, root_frame_url_));
}
if (!callback_.is_null())
- callback_.Run(main_frame_error_, main_frame_url_, results_);
+ callback_.Run(root_frame_error_, root_frame_url_, results_);
delete this;
}
@@ -158,17 +186,27 @@ class Handler : public content::WebContentsObserver {
// 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 main frame.
- std::string main_frame_error_;
+ // The error from injecting into the root frame.
+ std::string root_frame_error_;
- // The url of the main frame.
- GURL main_frame_url_;
+ // The url of the root frame.
+ GURL root_frame_url_;
// The callback to run after all injections complete.
ScriptExecutor::ExecuteScriptCallback callback_;
@@ -197,6 +235,7 @@ 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::WorldType world_type,
@@ -232,7 +271,8 @@ void ScriptExecutor::ExecuteScript(const HostID& host_id,
params.user_gesture = user_gesture;
// Handler handles IPCs and deletes itself on completion.
- new Handler(script_observers_, web_contents_, params, frame_scope, callback);
+ new Handler(script_observers_, web_contents_, params, frame_scope, frame_id,
+ callback);
}
} // namespace extensions
diff --git a/extensions/browser/script_executor.h b/extensions/browser/script_executor.h
index 4f81b78..25e6c54 100644
--- a/extensions/browser/script_executor.h
+++ b/extensions/browser/script_executor.h
@@ -44,8 +44,8 @@ class ScriptExecutor {
// The scope of the script injection across the frames.
enum FrameScope {
- TOP_FRAME,
- ALL_FRAMES,
+ SINGLE_FRAME,
+ INCLUDE_SUB_FRAMES,
};
// Whether to insert the script in about: frames when its origin matches
@@ -82,6 +82,10 @@ class ScriptExecutor {
// Executes a script. The arguments match ExtensionMsg_ExecuteCode_Params in
// extension_messages.h (request_id is populated automatically).
//
+ // The script will be executed in the frame identified by |frame_id| (which is
+ // an extension API frame ID). If |frame_scope| is INCLUDE_SUB_FRAMES, then
+ // the script will also be executed in all descendants of the frame.
+ //
// |callback| will always be called even if the IPC'd renderer is destroyed
// before a response is received (in this case the callback will be with a
// failure and appropriate error message).
@@ -89,6 +93,7 @@ class ScriptExecutor {
ScriptType script_type,
const std::string& code,
FrameScope frame_scope,
+ int frame_id,
MatchAboutBlank match_about_blank,
UserScript::RunLocation run_at,
WorldType world_type,
diff --git a/extensions/common/api/extension_types.json b/extensions/common/api/extension_types.json
index 29562ee..9f27590 100644
--- a/extensions/common/api/extension_types.json
+++ b/extensions/common/api/extension_types.json
@@ -45,7 +45,17 @@
"properties": {
"code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject.<br><br><b>Warning:</b><br>Be careful using the <code>code</code> parameter. Incorrect use of it may open your extension to <a href=\"https://en.wikipedia.org/wiki/Cross-site_scripting\">cross site scripting</a> attacks."},
"file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."},
- "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."},
+ "allFrames": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame. If <code>true</code> and <code>frameId</code> is set, then the code is inserted in the selected frame and all of its child frames."
+ },
+ "frameId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "The <a href='webNavigation#frame_ids'>frame</a> where the script or CSS should be injected. Defaults to 0 (the top-level frame)."
+ },
"matchAboutBlank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."},
"runAt": {
"$ref": "RunAt",
diff --git a/extensions/renderer/programmatic_script_injector.cc b/extensions/renderer/programmatic_script_injector.cc
index 13f0a20..e19a4c9 100644
--- a/extensions/renderer/programmatic_script_injector.cc
+++ b/extensions/renderer/programmatic_script_injector.cc
@@ -13,14 +13,15 @@
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/manifest_constants.h"
+#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/injection_host.h"
+#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/script_context.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
-#include "url/origin.h"
namespace extensions {
@@ -31,8 +32,10 @@ ProgrammaticScriptInjector::ProgrammaticScriptInjector(
url_(
ScriptContext::GetDataSourceURLForFrame(render_frame->GetWebFrame())),
finished_(false) {
- effective_url_ = ScriptContext::GetEffectiveDocumentURL(
- render_frame->GetWebFrame(), url_, params.match_about_blank);
+ if (url_.SchemeIs(url::kAboutScheme)) {
+ origin_for_about_error_ =
+ render_frame->GetWebFrame()->securityOrigin().toString().utf8();
+ }
}
ProgrammaticScriptInjector::~ProgrammaticScriptInjector() {
@@ -130,16 +133,15 @@ void ProgrammaticScriptInjector::OnWillNotInject(
std::string error;
switch (reason) {
case NOT_ALLOWED:
- if (url_.SchemeIs(url::kAboutScheme)) {
+ if (!CanShowUrlInError()) {
+ error = manifest_errors::kCannotAccessPage;
+ } else if (!origin_for_about_error_.empty()) {
error = ErrorUtils::FormatErrorMessage(
manifest_errors::kCannotAccessAboutUrl, url_.spec(),
- url::Origin(effective_url_).Serialize());
+ origin_for_about_error_);
} else {
- // TODO(?) It would be nice to show kCannotAccessPageWithUrl here if
- // this is triggered by an extension with tabs permission. See
- // https://codereview.chromium.org/1414223005/diff/1/extensions/
- // common/manifest_constants.cc#newcode269
- error = manifest_errors::kCannotAccessPage;
+ error = ErrorUtils::FormatErrorMessage(
+ manifest_errors::kCannotAccessPageWithUrl, url_.spec());
}
break;
case EXTENSION_REMOVED: // no special error here.
@@ -149,6 +151,17 @@ void ProgrammaticScriptInjector::OnWillNotInject(
Finish(error, render_frame);
}
+bool ProgrammaticScriptInjector::CanShowUrlInError() const {
+ if (params_->host_id.type() != HostID::EXTENSIONS)
+ return false;
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(params_->host_id.id());
+ if (!extension)
+ return false;
+ return extension->permissions_data()->active_permissions().HasAPIPermission(
+ APIPermission::kTab);
+}
+
UserScript::RunLocation ProgrammaticScriptInjector::GetRunLocation() const {
return static_cast<UserScript::RunLocation>(params_->run_at);
}
diff --git a/extensions/renderer/programmatic_script_injector.h b/extensions/renderer/programmatic_script_injector.h
index 159ff1c..a7af9ff 100644
--- a/extensions/renderer/programmatic_script_injector.h
+++ b/extensions/renderer/programmatic_script_injector.h
@@ -50,6 +50,9 @@ class ProgrammaticScriptInjector : public ScriptInjector {
void OnWillNotInject(InjectFailureReason reason,
content::RenderFrame* render_frame) override;
+ // Whether it is safe to include information about the URL in error messages.
+ bool CanShowUrlInError() const;
+
// Return the run location for this injector.
UserScript::RunLocation GetRunLocation() const;
@@ -63,10 +66,9 @@ class ProgrammaticScriptInjector : public ScriptInjector {
// The url of the frame into which we are injecting.
GURL url_;
- // The URL of the frame's origin. This is usually identical to |url_|, but
- // could be different for e.g. about:blank URLs. Do not use this value to make
- // security decisions, to avoid race conditions (e.g. due to navigation).
- GURL effective_url_;
+ // The serialization of the frame's origin if the frame is an about:-URL. This
+ // is used to provide user-friendly messages.
+ std::string origin_for_about_error_;
// The results of the script execution.
base::ListValue results_;