OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // Implements the Chrome Extensions Tab Capture API. | 5 // Implements the Chrome Extensions Tab Capture API. |
6 | 6 |
7 #include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" | 7 #include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" |
8 | 8 |
9 #include <algorithm> | |
9 #include <set> | 10 #include <set> |
10 #include <string> | 11 #include <string> |
11 #include <vector> | 12 #include <vector> |
12 | 13 |
13 #include "base/command_line.h" | 14 #include "base/command_line.h" |
14 #include "base/strings/stringprintf.h" | 15 #include "base/strings/stringprintf.h" |
15 #include "base/values.h" | 16 #include "base/values.h" |
17 #include "chrome/browser/extensions/api/tab_capture/offscreen_presentation.h" | |
16 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" | 18 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" |
17 #include "chrome/browser/extensions/extension_renderer_state.h" | 19 #include "chrome/browser/extensions/extension_renderer_state.h" |
18 #include "chrome/browser/profiles/profile.h" | 20 #include "chrome/browser/profiles/profile.h" |
19 #include "chrome/browser/sessions/session_tab_helper.h" | 21 #include "chrome/browser/sessions/session_tab_helper.h" |
20 #include "chrome/browser/ui/browser.h" | 22 #include "chrome/browser/ui/browser.h" |
21 #include "chrome/browser/ui/browser_finder.h" | 23 #include "chrome/browser/ui/browser_finder.h" |
22 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 24 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
25 #include "chrome/common/chrome_switches.h" | |
23 #include "content/public/browser/render_frame_host.h" | 26 #include "content/public/browser/render_frame_host.h" |
24 #include "content/public/browser/render_process_host.h" | 27 #include "content/public/browser/render_process_host.h" |
28 #include "content/public/browser/web_contents.h" | |
25 #include "extensions/common/features/feature.h" | 29 #include "extensions/common/features/feature.h" |
26 #include "extensions/common/features/feature_provider.h" | 30 #include "extensions/common/features/feature_provider.h" |
27 #include "extensions/common/features/simple_feature.h" | 31 #include "extensions/common/features/simple_feature.h" |
28 #include "extensions/common/permissions/permissions_data.h" | 32 #include "extensions/common/permissions/permissions_data.h" |
29 #include "extensions/common/switches.h" | 33 #include "extensions/common/switches.h" |
30 | 34 |
31 using extensions::api::tab_capture::MediaStreamConstraint; | 35 using extensions::api::tab_capture::MediaStreamConstraint; |
32 | 36 |
33 namespace TabCapture = extensions::api::tab_capture; | 37 namespace TabCapture = extensions::api::tab_capture; |
34 namespace GetCapturedTabs = TabCapture::GetCapturedTabs; | 38 namespace GetCapturedTabs = TabCapture::GetCapturedTabs; |
35 | 39 |
36 namespace extensions { | 40 namespace extensions { |
37 namespace { | 41 namespace { |
38 | 42 |
39 const char kCapturingSameTab[] = "Cannot capture a tab with an active stream."; | 43 const char kCapturingSameTab[] = "Cannot capture a tab with an active stream."; |
40 const char kFindingTabError[] = "Error finding tab to capture."; | 44 const char kFindingTabError[] = "Error finding tab to capture."; |
41 const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested."; | 45 const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested."; |
42 const char kGrantError[] = | 46 const char kGrantError[] = |
43 "Extension has not been invoked for the current page (see activeTab " | 47 "Extension has not been invoked for the current page (see activeTab " |
44 "permission). Chrome pages cannot be captured."; | 48 "permission). Chrome pages cannot be captured."; |
45 | 49 |
50 const char kNotWhitelistedForOffscreenTabApi[] = | |
51 "Extension is not whitelisted for use of the unstable, in-development " | |
52 "chrome.tabCapture.captureOffscreenTab API."; | |
53 const char kInvalidStartUrl[] = | |
54 "Invalid/Missing/Malformatted starting URL for off-screen tab."; | |
55 const char kMissingExtensionPage[] = | |
56 "tabCapture API was not invoked from an extension background page."; | |
57 const char kTooManyOffscreenTabs[] = | |
58 "Extension has already started too many off-screen tabs."; | |
59 const char kCapturingSameOffscreenTab[] = | |
60 "Cannot capture the same off-screen tab more than once."; | |
61 | |
46 // Keys/values for media stream constraints. | 62 // Keys/values for media stream constraints. |
47 const char kMediaStreamSource[] = "chromeMediaSource"; | 63 const char kMediaStreamSource[] = "chromeMediaSource"; |
48 const char kMediaStreamSourceId[] = "chromeMediaSourceId"; | 64 const char kMediaStreamSourceId[] = "chromeMediaSourceId"; |
49 const char kMediaStreamSourceTab[] = "tab"; | 65 const char kMediaStreamSourceTab[] = "tab"; |
50 | 66 |
51 // Tab Capture-specific video constraint to enable automatic resolution/rate | 67 // Tab Capture-specific video constraint to enable automatic resolution/rate |
52 // throttling mode in the capture pipeline. | 68 // throttling mode in the capture pipeline. |
53 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling"; | 69 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling"; |
54 | 70 |
71 bool OptionsSpecifyAudioOrVideo(const TabCapture::CaptureOptions& options) { | |
72 return (options.audio && *options.audio) || (options.video && *options.video); | |
73 } | |
74 | |
75 bool IsAcceptableOffscreenTabUrl(const GURL& url) { | |
76 return url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data")); | |
77 } | |
78 | |
79 // Add Chrome-specific source identifiers to the MediaStreamConstraints objects | |
80 // in |options| to provide references to the |target_contents| to be captured. | |
81 void AddMediaStreamSourceConstraints(content::WebContents* target_contents, | |
82 TabCapture::CaptureOptions* options) { | |
83 DCHECK(options); | |
84 DCHECK(target_contents); | |
85 | |
86 MediaStreamConstraint* constraints_to_modify[2] = { nullptr, nullptr }; | |
87 | |
88 if (options->audio && *options->audio) { | |
89 if (!options->audio_constraints) | |
90 options->audio_constraints.reset(new MediaStreamConstraint); | |
91 constraints_to_modify[0] = options->audio_constraints.get(); | |
92 } | |
93 | |
94 bool enable_auto_throttling = false; | |
95 if (options->video && *options->video) { | |
96 if (options->video_constraints) { | |
97 // Check for the Tab Capture-specific video constraint for enabling | |
98 // automatic resolution/rate throttling mode in the capture pipeline. See | |
99 // implementation comments for content::WebContentsVideoCaptureDevice. | |
100 base::DictionaryValue& props = | |
101 options->video_constraints->mandatory.additional_properties; | |
102 if (!props.GetBooleanWithoutPathExpansion( | |
103 kEnableAutoThrottlingKey, &enable_auto_throttling)) { | |
104 enable_auto_throttling = false; | |
105 } | |
106 // Remove the key from the properties to avoid an "unrecognized | |
107 // constraint" error in the renderer. | |
108 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr); | |
109 } else { | |
110 options->video_constraints.reset(new MediaStreamConstraint); | |
111 } | |
112 constraints_to_modify[1] = options->video_constraints.get(); | |
113 } | |
114 | |
115 // Format the device ID that references the target tab. | |
116 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame(); | |
117 // TODO(miu): We should instead use a "randomly generated device ID" scheme, | |
118 // like that employed by the desktop capture API. http://crbug.com/163100 | |
119 const std::string device_id = base::StringPrintf( | |
120 "web-contents-media-stream://%i:%i%s", | |
121 main_frame->GetProcess()->GetID(), | |
122 main_frame->GetRoutingID(), | |
123 enable_auto_throttling ? "?throttling=auto" : ""); | |
124 | |
125 // Append chrome specific tab constraints. | |
126 for (MediaStreamConstraint* msc : constraints_to_modify) { | |
127 if (!msc) | |
128 continue; | |
129 base::DictionaryValue* constraint = &msc->mandatory.additional_properties; | |
130 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab); | |
131 constraint->SetString(kMediaStreamSourceId, device_id); | |
132 } | |
133 } | |
134 | |
55 } // namespace | 135 } // namespace |
56 | 136 |
57 // Whitelisted extensions that do not check for a browser action grant because | 137 // Whitelisted extensions that do not check for a browser action grant because |
58 // they provide API's. If there are additional extension ids that need | 138 // they provide API's. If there are additional extension ids that need |
59 // whitelisting and are *not* the Chromecast extension, add them to a new | 139 // whitelisting and are *not* the Chromecast extension, add them to a new |
60 // kWhitelist array. | 140 // kWhitelist array. |
61 // | 141 // |
62 // This list is also used by CastConfigDelegateChromeos to find official Cast | 142 // This list is also used by CastConfigDelegateChromeos to find official Cast |
63 // extensions. | 143 // extensions. |
64 const char* const kChromecastExtensionIds[] = { | 144 const char* const kChromecastExtensionIds[] = { |
65 "enhhojjnijigcajfphajepfemndkmdlo", // Dev | 145 "enhhojjnijigcajfphajepfemndkmdlo", // Dev |
66 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging | 146 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging |
67 "hfaagokkkhdbgiakmmlclaapfelnkoah", // Canary | 147 "hfaagokkkhdbgiakmmlclaapfelnkoah", // Canary |
68 "dliochdbjfkdbacpmhlcpmleaejidimm", // Google Cast Beta | 148 "dliochdbjfkdbacpmhlcpmleaejidimm", // Google Cast Beta |
69 "boadgeojelhgndaghljhdicfkmllpafd", // Google Cast Stable | 149 "boadgeojelhgndaghljhdicfkmllpafd", // Google Cast Stable |
70 "hlgmmjhlnlapooncikdpiiokdjcdpjme", // Test cast extension | 150 "hlgmmjhlnlapooncikdpiiokdjcdpjme", // Test cast extension |
71 }; | 151 }; |
72 | 152 |
73 const char* const kMediaRouterExtensionIds[] = { | 153 const char* const kMediaRouterExtensionIds[] = { |
74 "fjhoaacokmgbjemoflkofnenfaiekifl", // Stable | 154 "fjhoaacokmgbjemoflkofnenfaiekifl", // Stable |
75 "ekpaaapppgpmolpcldedioblbkmijaca", // Beta | 155 "ekpaaapppgpmolpcldedioblbkmijaca", // Beta |
76 }; | 156 }; |
77 | 157 |
78 bool TabCaptureCaptureFunction::RunSync() { | 158 bool TabCaptureCaptureFunction::RunSync() { |
79 scoped_ptr<api::tab_capture::Capture::Params> params = | 159 scoped_ptr<api::tab_capture::Capture::Params> params = |
80 TabCapture::Capture::Params::Create(*args_); | 160 TabCapture::Capture::Params::Create(*args_); |
81 EXTENSION_FUNCTION_VALIDATE(params.get()); | 161 EXTENSION_FUNCTION_VALIDATE(params); |
82 | 162 |
83 // Figure out the active WebContents and retrieve the needed ids. | 163 // Figure out the active WebContents and retrieve the needed ids. |
84 Browser* target_browser = chrome::FindAnyBrowser( | 164 Browser* target_browser = chrome::FindAnyBrowser( |
85 GetProfile(), include_incognito(), chrome::GetActiveDesktop()); | 165 GetProfile(), include_incognito(), chrome::GetActiveDesktop()); |
86 if (!target_browser) { | 166 if (!target_browser) { |
87 error_ = kFindingTabError; | 167 error_ = kFindingTabError; |
88 return false; | 168 return false; |
89 } | 169 } |
90 | 170 |
91 content::WebContents* target_contents = | 171 content::WebContents* target_contents = |
(...skipping 13 matching lines...) Expand all Loading... | |
105 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | 185 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
106 switches::kWhitelistedExtensionID) != extension_id && | 186 switches::kWhitelistedExtensionID) != extension_id && |
107 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds, | 187 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds, |
108 arraysize(kChromecastExtensionIds)) && | 188 arraysize(kChromecastExtensionIds)) && |
109 !SimpleFeature::IsIdInArray(extension_id, kMediaRouterExtensionIds, | 189 !SimpleFeature::IsIdInArray(extension_id, kMediaRouterExtensionIds, |
110 arraysize(kMediaRouterExtensionIds))) { | 190 arraysize(kMediaRouterExtensionIds))) { |
111 error_ = kGrantError; | 191 error_ = kGrantError; |
112 return false; | 192 return false; |
113 } | 193 } |
114 | 194 |
115 // Create a constraints vector. We will modify all the constraints in this | 195 if (!OptionsSpecifyAudioOrVideo(params->options)) { |
116 // vector to append our chrome specific constraints. | |
117 std::vector<MediaStreamConstraint*> constraints; | |
118 bool has_audio = params->options.audio.get() && *params->options.audio.get(); | |
119 bool has_video = params->options.video.get() && *params->options.video.get(); | |
120 | |
121 if (!has_audio && !has_video) { | |
122 error_ = kNoAudioOrVideo; | 196 error_ = kNoAudioOrVideo; |
123 return false; | 197 return false; |
124 } | 198 } |
125 | 199 |
126 if (has_audio) { | |
127 if (!params->options.audio_constraints.get()) | |
128 params->options.audio_constraints.reset(new MediaStreamConstraint); | |
129 | |
130 constraints.push_back(params->options.audio_constraints.get()); | |
131 } | |
132 | |
133 bool enable_auto_throttling = false; | |
134 if (has_video) { | |
135 if (params->options.video_constraints.get()) { | |
136 // Check for the Tab Capture-specific video constraint for enabling | |
137 // automatic resolution/rate throttling mode in the capture pipeline. See | |
138 // implementation comments for content::WebContentsVideoCaptureDevice. | |
139 base::DictionaryValue& props = | |
140 params->options.video_constraints->mandatory.additional_properties; | |
141 if (!props.GetBooleanWithoutPathExpansion( | |
142 kEnableAutoThrottlingKey, &enable_auto_throttling)) { | |
143 enable_auto_throttling = false; | |
144 } | |
145 // Remove the key from the properties to avoid an "unrecognized | |
146 // constraint" error in the renderer. | |
147 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr); | |
148 } else { | |
149 params->options.video_constraints.reset(new MediaStreamConstraint); | |
150 } | |
151 | |
152 constraints.push_back(params->options.video_constraints.get()); | |
153 } | |
154 | |
155 // Device id we use for Tab Capture. | |
156 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame(); | |
157 // TODO(miu): We should instead use a "randomly generated device ID" scheme, | |
158 // like that employed by the desktop capture API. http://crbug.com/163100 | |
159 const std::string device_id = base::StringPrintf( | |
160 "web-contents-media-stream://%i:%i%s", | |
161 main_frame->GetProcess()->GetID(), | |
162 main_frame->GetRoutingID(), | |
163 enable_auto_throttling ? "?throttling=auto" : ""); | |
164 | |
165 // Append chrome specific tab constraints. | |
166 for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin(); | |
167 it != constraints.end(); ++it) { | |
168 base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties; | |
169 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab); | |
170 constraint->SetString(kMediaStreamSourceId, device_id); | |
171 } | |
172 | |
173 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); | 200 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); |
174 if (!registry->AddRequest(target_contents, extension_id)) { | 201 if (!registry->AddRequest(target_contents, extension_id, false)) { |
202 // TODO(miu): Allow multiple consumers of single tab capture. | |
203 // http://crbug.com/535336 | |
175 error_ = kCapturingSameTab; | 204 error_ = kCapturingSameTab; |
176 return false; | 205 return false; |
177 } | 206 } |
207 AddMediaStreamSourceConstraints(target_contents, ¶ms->options); | |
178 | 208 |
179 // Copy the result from our modified input parameters. This will be | 209 // At this point, everything is set up in the browser process. It's now up to |
180 // intercepted by custom bindings which will build and send the special | 210 // the custom JS bindings in the extension's render process to request a |
181 // WebRTC user media request. | 211 // MediaStream using navigator.webkitGetUserMedia(). The result dictionary, |
212 // passed to SetResult() here, contains the extra "hidden options" that will | |
213 // allow the Chrome platform implementation for getUserMedia() to start the | |
214 // virtual audio/video capture devices and set up all the data flows. The | |
215 // custom JS bindings can be found here: | |
216 // chrome/renderer/resources/extensions/tab_capture_custom_bindings.js | |
182 base::DictionaryValue* result = new base::DictionaryValue(); | 217 base::DictionaryValue* result = new base::DictionaryValue(); |
183 result->MergeDictionary(params->options.ToValue().get()); | 218 result->MergeDictionary(params->options.ToValue().get()); |
184 | |
185 SetResult(result); | 219 SetResult(result); |
186 return true; | 220 return true; |
187 } | 221 } |
188 | 222 |
189 bool TabCaptureGetCapturedTabsFunction::RunSync() { | 223 bool TabCaptureGetCapturedTabsFunction::RunSync() { |
190 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); | 224 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); |
191 base::ListValue* const list = new base::ListValue(); | 225 base::ListValue* const list = new base::ListValue(); |
192 if (registry) | 226 if (registry) |
193 registry->GetCapturedTabs(extension()->id(), list); | 227 registry->GetCapturedTabs(extension()->id(), list); |
194 SetResult(list); | 228 SetResult(list); |
195 return true; | 229 return true; |
196 } | 230 } |
197 | 231 |
232 bool TabCaptureCaptureOffscreenTabFunction::RunSync() { | |
233 scoped_ptr<TabCapture::CaptureOffscreenTab::Params> params = | |
234 TabCapture::CaptureOffscreenTab::Params::Create(*args_); | |
235 EXTENSION_FUNCTION_VALIDATE(params); | |
236 | |
237 // Make sure the extension is whitelisted for using this API, or this is a | |
238 // Canary/Dev-channel build of Chrome. | |
239 // | |
240 // TODO(miu): Use _api_features.json and extensions::Feature library instead. | |
241 // http://crbug.com/537732 | |
not at google - send to devlin
2015/10/01 00:09:14
I don't understand your comment. This is new code?
miu
2015/10/01 22:34:46
The whitelist arrays themselves (kChromecastExtens
not at google - send to devlin
2015/10/02 01:17:25
I mean to just duplicate the IDs in the feature fi
miu
2015/10/02 18:58:21
Oh! I didn't realize they had been duplicated the
| |
242 const bool is_whitelisted_extension = | |
243 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
244 switches::kWhitelistedExtensionID) == extension()->id() || | |
245 SimpleFeature::IsIdInArray(extension()->id(), kChromecastExtensionIds, | |
246 arraysize(kChromecastExtensionIds)) || | |
247 SimpleFeature::IsIdInArray(extension()->id(), kMediaRouterExtensionIds, | |
248 arraysize(kMediaRouterExtensionIds)); | |
249 if (!is_whitelisted_extension) { | |
250 error_ = kNotWhitelistedForOffscreenTabApi; | |
251 return false; | |
252 } | |
253 | |
254 const GURL start_url(params->start_url); | |
255 if (!IsAcceptableOffscreenTabUrl(start_url)) { | |
256 SetError(kInvalidStartUrl); | |
257 return false; | |
258 } | |
259 | |
260 if (!OptionsSpecifyAudioOrVideo(params->options)) { | |
261 SetError(kNoAudioOrVideo); | |
262 return false; | |
263 } | |
264 | |
265 content::WebContents* const extension_web_contents = GetSenderWebContents(); | |
266 if (!extension_web_contents) { | |
267 SetError(kMissingExtensionPage); | |
268 return false; | |
269 } | |
270 | |
271 OffscreenPresentation* const offscreen_tab = | |
272 OffscreenPresentationsOwner::Get(extension_web_contents) | |
273 ->StartPresentation( | |
274 start_url, | |
275 (is_whitelisted_extension && params->options.presentation_id) ? | |
276 *params->options.presentation_id : std::string(), | |
not at google - send to devlin
2015/10/01 00:09:14
Huh, guess I didn't save the comment.
It was:
- W
miu
2015/10/01 22:34:46
Two ways to answer this:
1. It means the off-scre
| |
277 DetermineInitialSize(params->options)); | |
278 if (!offscreen_tab) { | |
279 SetError(kTooManyOffscreenTabs); | |
280 return false; | |
281 } | |
282 | |
283 if (!TabCaptureRegistry::Get(browser_context())->AddRequest( | |
284 offscreen_tab->web_contents(), extension()->id(), true)) { | |
285 // TODO(miu): Allow multiple consumers of single tab capture. | |
286 // http://crbug.com/535336 | |
287 SetError(kCapturingSameOffscreenTab); | |
288 return false; | |
289 } | |
290 AddMediaStreamSourceConstraints(offscreen_tab->web_contents(), | |
291 ¶ms->options); | |
292 | |
293 // At this point, everything is set up in the browser process. It's now up to | |
294 // the custom JS bindings in the extension's render process to complete the | |
295 // request. See the comment at end of TabCaptureCaptureFunction::RunSync() | |
296 // for more details. | |
297 base::DictionaryValue* const result = new base::DictionaryValue(); | |
298 result->MergeDictionary(params->options.ToValue().get()); | |
299 SetResult(result); | |
300 return true; | |
301 } | |
302 | |
303 // static | |
304 gfx::Size TabCaptureCaptureOffscreenTabFunction::DetermineInitialSize( | |
305 const TabCapture::CaptureOptions& options) { | |
306 static const int kDefaultWidth = 1280; | |
307 static const int kDefaultHeight = 720; | |
308 | |
309 if (!options.video_constraints) | |
310 return gfx::Size(kDefaultWidth, kDefaultHeight); | |
311 | |
312 gfx::Size min_size; | |
313 int width = -1; | |
314 int height = -1; | |
315 const base::DictionaryValue& mandatory_properties = | |
316 options.video_constraints->mandatory.additional_properties; | |
317 if (mandatory_properties.GetInteger("maxWidth", &width) && width >= 0 && | |
318 mandatory_properties.GetInteger("maxHeight", &height) && height >= 0) { | |
319 return gfx::Size(width, height); | |
320 } | |
321 if (mandatory_properties.GetInteger("minWidth", &width) && width >= 0 && | |
322 mandatory_properties.GetInteger("minHeight", &height) && height >= 0) { | |
323 min_size.SetSize(width, height); | |
324 } | |
325 | |
326 // Use optional size constraints if no mandatory ones were provided. | |
327 if (options.video_constraints->optional) { | |
328 const base::DictionaryValue& optional_properties = | |
329 options.video_constraints->optional->additional_properties; | |
330 if (optional_properties.GetInteger("maxWidth", &width) && width >= 0 && | |
331 optional_properties.GetInteger("maxHeight", &height) && height >= 0) { | |
332 if (min_size.IsEmpty()) { | |
333 return gfx::Size(width, height); | |
334 } else { | |
335 return gfx::Size(std::max(width, min_size.width()), | |
336 std::max(height, min_size.height())); | |
337 } | |
338 } | |
339 if (min_size.IsEmpty() && | |
340 optional_properties.GetInteger("minWidth", &width) && width >= 0 && | |
341 optional_properties.GetInteger("minHeight", &height) && height >= 0) { | |
342 min_size.SetSize(width, height); | |
343 } | |
344 } | |
345 | |
346 // No maximum size was provided, so just return the default size bounded by | |
347 // the minimum size. | |
348 return gfx::Size(std::max(kDefaultWidth, min_size.width()), | |
349 std::max(kDefaultHeight, min_size.height())); | |
350 } | |
351 | |
198 } // namespace extensions | 352 } // namespace extensions |
OLD | NEW |